v1.1.0
This commit is contained in:
parent
443dceb8ca
commit
92d56fe84d
4445 changed files with 992 additions and 529601 deletions
281
README.md
281
README.md
|
|
@ -1,281 +0,0 @@
|
|||
# Documentation Markdown (by ROBOTSTXT)
|
||||
|
||||
Synchronize Markdown documentation from GitHub repositories to WordPress pages and posts automatically.
|
||||
|
||||
## Description
|
||||
|
||||
**Documentation Markdown** is a WordPress plugin that enables seamless synchronization of Markdown documentation files from GitHub repositories into your WordPress site. Perfect for maintaining technical documentation, API references, knowledge bases, and more.
|
||||
|
||||
## Features
|
||||
|
||||
- 🔄 **Automatic Synchronization** - Schedule automatic syncs via WordPress Cron
|
||||
- 📝 **Markdown to HTML** - Convert GitHub Flavored Markdown to clean HTML
|
||||
- 🎯 **Flexible Mapping** - Map individual MD files to specific WordPress posts/pages
|
||||
- 🔍 **Smart Change Detection** - Only sync when content actually changes (using SHA comparison)
|
||||
- 🔐 **Secure** - Encrypted GitHub token storage, full input validation & output escaping
|
||||
- 🌍 **Translatable** - Full internationalization support (i18n/l10n ready)
|
||||
- 📚 **Multi-Repository** - Sync from multiple GitHub repos simultaneously
|
||||
- ⚡ **Manual Sync** - On-demand synchronization from admin interface
|
||||
|
||||
## Requirements
|
||||
|
||||
- **PHP:** 8.2 or higher
|
||||
- **WordPress:** 6.5 or higher
|
||||
- **GitHub Account:** For repository access (public or private)
|
||||
|
||||
## Installation
|
||||
|
||||
### Manual Installation
|
||||
|
||||
1. Download the plugin or clone this repository
|
||||
2. Upload to `/wp-content/plugins/robotstxt-documentation-markdown/`
|
||||
3. Run `composer install --no-dev` in the plugin directory
|
||||
4. Activate the plugin through the 'Plugins' menu in WordPress
|
||||
5. Navigate to 'GitHub Docs' in the WordPress admin menu
|
||||
6. Configure your GitHub Personal Access Token
|
||||
7. Create your first mapping
|
||||
|
||||
### Via Composer (Development)
|
||||
|
||||
```bash
|
||||
cd wp-content/plugins
|
||||
git clone https://git.robotstxt.es/ROBOTSTXT/robotstxt-documentation-markdown.git robotstxt-documentation-markdown
|
||||
cd robotstxt-documentation-markdown
|
||||
composer install
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### 1. GitHub Personal Access Token
|
||||
|
||||
Generate a Personal Access Token from GitHub:
|
||||
- Go to GitHub → Settings → Developer settings → Personal access tokens
|
||||
- Click "Generate new token"
|
||||
- For public repositories: No specific scopes needed
|
||||
- For private repositories: Select `repo` scope
|
||||
- Copy the token (you won't see it again!)
|
||||
|
||||
### 2. Plugin Configuration
|
||||
|
||||
1. In WordPress admin, go to **GitHub Docs → Settings**
|
||||
2. Paste your GitHub token
|
||||
3. Configure sync frequency (hourly, twice daily, daily)
|
||||
4. Save settings
|
||||
|
||||
### 3. Create a Mapping
|
||||
|
||||
1. Go to **GitHub Docs → Mappings**
|
||||
2. Click "Add New Mapping"
|
||||
3. Fill in:
|
||||
- **Repository Owner:** GitHub username or organization
|
||||
- **Repository Name:** Repository name
|
||||
- **File Path:** Path to `.md` file (e.g., `docs/api/auth.md`)
|
||||
- **Branch:** Branch to sync from (default: `main`)
|
||||
- **Target Post Type:** Where to create content (page/post)
|
||||
- **Author:** WordPress user who will be the post author
|
||||
4. Save mapping
|
||||
5. Click "Sync Now" to perform first synchronization
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Example 1: API Documentation
|
||||
|
||||
Sync your API documentation from GitHub to WordPress pages:
|
||||
|
||||
```
|
||||
Repository: yourcompany/api-docs
|
||||
File Path: docs/authentication.md
|
||||
Branch: main
|
||||
Target: Page
|
||||
```
|
||||
|
||||
Every time `authentication.md` is updated in GitHub, the corresponding WordPress page will automatically update.
|
||||
|
||||
### Example 2: Blog Posts
|
||||
|
||||
Write blog posts in Markdown using Git workflow:
|
||||
|
||||
```
|
||||
Repository: yourblog/content
|
||||
File Path: posts/2026-01-intro-to-api.md
|
||||
Branch: main
|
||||
Target: Post
|
||||
```
|
||||
|
||||
Commit your Markdown post to GitHub, and it automatically publishes to your WordPress blog.
|
||||
|
||||
### Example 3: Knowledge Base
|
||||
|
||||
Maintain a knowledge base with version control:
|
||||
|
||||
```
|
||||
Repository: support/knowledge-base
|
||||
File Path: articles/how-to-install.md
|
||||
Branch: production
|
||||
Target: Page
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Setup Development Environment
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://git.robotstxt.es/ROBOTSTXT/robotstxt-documentation-markdown.git
|
||||
cd documentation-markdown
|
||||
|
||||
# Install dependencies
|
||||
composer install
|
||||
|
||||
# Run code quality checks
|
||||
composer phpcs # Check coding standards
|
||||
composer phpcbf # Auto-fix coding standards
|
||||
composer phpstan # Static analysis
|
||||
composer test # Run tests
|
||||
```
|
||||
|
||||
### Coding Standards
|
||||
|
||||
This plugin strictly follows:
|
||||
- **WordPress Coding Standards (WPCS)**
|
||||
- **PHP 8.2+ features** (readonly properties, constructor promotion, match expressions)
|
||||
- **Complete PHPDoc** documentation for all code
|
||||
- **PSR-4 autoloading**
|
||||
|
||||
All code must pass:
|
||||
```bash
|
||||
composer lint # Runs both PHPCS and PHPStan
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
Complete technical documentation available in `/docs/`:
|
||||
- **00-REQUIREMENTS-SUMMARY.md** - Start here! All critical requirements
|
||||
- **01-project-overview.md** - Plugin overview and use cases
|
||||
- **02-architecture.md** - System architecture and components
|
||||
- **03-data-structure.md** - Database schema and data models
|
||||
- **04-development-phases.md** - Development roadmap
|
||||
- **05-technical-specifications.md** - Technical details and APIs
|
||||
- **06-synchronization-flow.md** - How synchronization works
|
||||
- **07-security-considerations.md** - Security best practices
|
||||
- **08-implementation-order.md** - Step-by-step implementation guide
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Unit tests
|
||||
composer test
|
||||
|
||||
# Specific test
|
||||
vendor/bin/phpunit tests/test-github-api-client.php
|
||||
|
||||
# With coverage
|
||||
composer test -- --coverage-html coverage/
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
### Reporting Vulnerabilities
|
||||
|
||||
Please report security vulnerabilities privately to: **robotstxt@robotstxt.es**
|
||||
|
||||
Do NOT create public issues for security vulnerabilities.
|
||||
|
||||
### Security Features
|
||||
|
||||
- ✅ GitHub tokens encrypted at rest (AES-256-CBC)
|
||||
- ✅ All user input sanitized
|
||||
- ✅ All output escaped
|
||||
- ✅ Nonce verification on all forms
|
||||
- ✅ Capability checks for all admin actions
|
||||
- ✅ Prepared statements for database queries
|
||||
- ✅ Rate limiting for GitHub API
|
||||
|
||||
## Translation
|
||||
|
||||
The plugin is fully translatable and uses the text domain: `robotstxt-documentation-markdown`
|
||||
|
||||
To translate:
|
||||
1. Use [Poedit](https://poedit.net/) or similar tool
|
||||
2. Open `/languages/robotstxt-documentation-markdown.pot`
|
||||
3. Create translations
|
||||
4. Save as `robotstxt-documentation-markdown-{locale}.mo`
|
||||
5. Place in `/wp-content/languages/plugins/`
|
||||
|
||||
## Changelog
|
||||
|
||||
See [CHANGELOG.md](CHANGELOG.md) for detailed version history.
|
||||
|
||||
### Version 1.0.0 (2026-01-26)
|
||||
- ✨ Initial stable release
|
||||
- 🔄 GitHub repository synchronization
|
||||
- 📝 Markdown to HTML conversion using CommonMark
|
||||
- 🎯 Flexible file-to-content mapping
|
||||
- ⚡ Manual and scheduled synchronization
|
||||
- 🎨 Complete admin interface
|
||||
- 🐛 Built-in debug tools (WP_DEBUG mode)
|
||||
- 🔐 Encrypted token storage
|
||||
- 🌍 Full internationalization support
|
||||
- 🛡️ Security best practices implemented
|
||||
|
||||
## Roadmap
|
||||
|
||||
Future enhancements under consideration:
|
||||
- [ ] Bidirectional sync (WordPress → GitHub)
|
||||
- [ ] Image synchronization from GitHub
|
||||
- [ ] Folder-level mapping (sync entire directory)
|
||||
- [ ] GitHub webhooks support (instant sync on push)
|
||||
- [ ] Gutenberg block for embedded docs
|
||||
- [ ] Multi-branch support per mapping
|
||||
- [ ] Conflict resolution UI
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions! Please follow these guidelines:
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
||||
3. Follow WordPress Coding Standards (run `composer phpcs`)
|
||||
4. Write PHPDoc for all functions
|
||||
5. Include unit tests for new functionality
|
||||
6. Ensure all tests pass (`composer test`)
|
||||
7. Commit with conventional commit messages
|
||||
8. Push to your fork
|
||||
9. Open a Pull Request
|
||||
|
||||
All contributions must:
|
||||
- Be in English
|
||||
- Include complete PHPDoc
|
||||
- Pass PHPCS and PHPStan
|
||||
- Include appropriate unit tests
|
||||
|
||||
## License
|
||||
|
||||
This plugin is licensed under the GNU General Public License v3.0 or later.
|
||||
|
||||
See [LICENSE](LICENSE) file for details.
|
||||
|
||||
## Credits
|
||||
|
||||
**Developed by:** ROBOTSTXT
|
||||
|
||||
**Dependencies:**
|
||||
- [league/commonmark](https://commonmark.thephpleague.com/) - Markdown parser
|
||||
- [WordPress Coding Standards](https://github.com/WordPress/WordPress-Coding-Standards)
|
||||
|
||||
## Support
|
||||
|
||||
- **Documentation:** See `/docs/` directory
|
||||
- **Issues:** [Gitea Issues](https://git.robotstxt.es/ROBOTSTXT/robotstxt-documentation-markdown/issues)
|
||||
- **Discussions:** [Gitea Discussions](https://git.robotstxt.es/ROBOTSTXT/robotstxt-documentation-markdown/discussions)
|
||||
- **Website:** [ROBOTSTXT.es](https://www.robotstxt.es/)
|
||||
- **Security:** robotstxt@robotstxt.es
|
||||
|
||||
---
|
||||
|
||||
**Status:** Stable Release
|
||||
**Version:** 1.0.0
|
||||
**Requires PHP:** 8.2+
|
||||
**Requires WordPress:** 6.5+ (single-site only)
|
||||
**License:** GPL-3.0-or-later
|
||||
**Text Domain:** robotstxt-documentation-markdown
|
||||
140
changelog.txt
Normal file
140
changelog.txt
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
== Changelog ==
|
||||
|
||||
= 1.1.0 =
|
||||
|
||||
_Release date: 2026-03-28_
|
||||
|
||||
**Highlights**
|
||||
|
||||
* Editor-level access: editors can now manage documentation mappings without administrator privileges
|
||||
* Full PHPStan level 9 compliance — zero errors across all plugin files
|
||||
* Security patch for two CVEs in league/commonmark
|
||||
* `target_order` (menu_order) field fully implemented end-to-end
|
||||
|
||||
**Security**
|
||||
|
||||
* Patched CVE-2026-33347 and CVE-2026-30838 by upgrading league/commonmark to 2.8.2
|
||||
|
||||
**Changed**
|
||||
|
||||
* Access level changed from `manage_options` (administrator) to `edit_pages` (editor) across all admin pages, form handlers, and debug functions
|
||||
* `robotstxt_docmd_debug_run_cron()` now has a typed `int $mapping_id` parameter
|
||||
|
||||
**Fixed**
|
||||
|
||||
* PHPStan level 9: replaced all implicit `mixed` casts with proper type-narrowing via `is_string()`, `is_int()`, and `is_numeric()` guards
|
||||
* New `robotstxt_docmd_input_string()` and `robotstxt_docmd_input_int()` helpers used for all superglobal (`$_POST`, `$_GET`) access
|
||||
* `MappingData` and `MappingInput` global type aliases defined in `phpstan.neon` — file-level `@phpstan-type` aliases do not propagate between files in PHPStan 2.x procedural code
|
||||
* `target_order` field was rendered in the form UI but never saved to post meta or applied during sync — now fully implemented
|
||||
* `openssl_decrypt()` false return properly handled in token decryption
|
||||
* `get_edit_post_link()` null return handled safely in debug run-cron output
|
||||
* Redundant `isset()` guards removed on statically-typed array shapes
|
||||
* Uninstall handler narrows `get_option()` mixed return before array access
|
||||
* `size_format()` false return handled in discover-page file list
|
||||
* Settings and debug functions use `is_array()` narrowing on `get_option()` before accessing keys
|
||||
* `json_decode()` results in GitHub debug functions fully type-narrowed before key access
|
||||
|
||||
**Developer Features**
|
||||
|
||||
* PHPStan level 9: 0 errors (down from 104 in v1.0.0)
|
||||
* `phpstan.neon` now includes global `MappingData` and `MappingInput` type aliases
|
||||
* `robotstxt-updater.php` moved to `bootstrapFiles` in PHPStan config to avoid strict analysis of shared utility
|
||||
* `$default` parameter renamed to `$fallback` in helpers (reserved keyword warning)
|
||||
* Short ternary (`?:`) replaced with explicit `false !==` check (PHPCS rule)
|
||||
|
||||
**Compatibility**
|
||||
|
||||
* WordPress: 6.7 - 7.0
|
||||
* PHP: 8.2 - 8.4 (verified on PHP 8.4.x)
|
||||
* MariaDB: 10.6 or newer
|
||||
|
||||
**Dependencies**
|
||||
|
||||
* `league/commonmark`: 2.8.0 → 2.8.2 (security patch)
|
||||
* `eduardovillao/wp-since`: 1.3.0 → 1.4.0
|
||||
* `phpunit/phpunit` held at ^10.5 (v13.x available; pending test suite migration)
|
||||
* `squizlabs/php_codesniffer` held at ^3.13 (v4.x available; pending WPCS 4.x confirmation)
|
||||
|
||||
**Tests**
|
||||
|
||||
* PHP Coding Standards: PHPCS 3.x with WordPress-Extra ruleset — 0 errors, 0 warnings
|
||||
* WordPress Coding Standards: WPCS 3.3
|
||||
* PHPStan: level 9, 0 errors (szepeviktor/phpstan-wordpress extension)
|
||||
* PHPCompatibility: PHP 8.2 - 8.4 validated
|
||||
* Manual testing: WordPress 6.8, 7.0
|
||||
|
||||
= 1.0.0 =
|
||||
|
||||
_Release date: 2026-01-26_
|
||||
|
||||
**Highlights**
|
||||
|
||||
* Initial release of Documentation Markdown plugin
|
||||
* Automatic synchronization of Markdown files from GitHub to WordPress
|
||||
* Full support for GitHub Flavored Markdown
|
||||
* Encrypted GitHub token storage
|
||||
* Flexible mapping system for multiple repositories
|
||||
|
||||
**Added**
|
||||
|
||||
* Core synchronization functionality between GitHub and WordPress
|
||||
* Automatic scheduled sync (hourly, twice daily, daily)
|
||||
* Manual on-demand sync via admin interface
|
||||
* Markdown to HTML conversion using CommonMark (league/commonmark)
|
||||
* Flexible file-to-content mapping system
|
||||
* Custom Post Type (robotstxt_map) for mapping management
|
||||
* Encrypted GitHub token storage (AES-256-CBC)
|
||||
* Full internationalization support (i18n/l10n ready)
|
||||
* Multi-repository support
|
||||
* Clean admin interface with status badges
|
||||
* Support for pages, posts, and custom post types as sync targets
|
||||
* Configurable post author and parent page
|
||||
* Page order (menu_order) support
|
||||
* Debug tools for troubleshooting (visible when WP_DEBUG enabled)
|
||||
* Cron job management and repair tools
|
||||
* Clean uninstall with optional data deletion
|
||||
* Settings page for GitHub configuration
|
||||
* Mappings management interface (list, add, edit, delete)
|
||||
* Sync status monitoring with timestamps
|
||||
* Rate limiting awareness for GitHub API
|
||||
* Cache system using WordPress Transients API
|
||||
|
||||
**Security**
|
||||
|
||||
* All user input sanitized using WordPress functions
|
||||
* All output escaped (esc_html, esc_attr, esc_url)
|
||||
* Nonce verification on all forms and actions
|
||||
* Capability checks for all admin actions (manage_options — changed to edit_pages in 1.1.0)
|
||||
* Prepared statements for all database queries
|
||||
* GitHub tokens encrypted at rest using AES-256-CBC
|
||||
* OWASP Top 10 mitigation implemented
|
||||
* Direct access prevention on all PHP files
|
||||
* CSRF protection on all state-changing operations
|
||||
* XSS prevention through proper escaping
|
||||
* SQL injection prevention through prepared statements
|
||||
|
||||
**Developer Features**
|
||||
|
||||
* Procedural PHP architecture following KISS principles
|
||||
* PHP 8.2+ modern features (typed parameters, match expressions)
|
||||
* Complete PHPDoc documentation on all functions
|
||||
* WordPress Coding Standards (WPCS) compliant
|
||||
* PHPCS/WPBF validated (0 errors, 0 warnings)
|
||||
* Extensible architecture with WordPress hooks
|
||||
* Clean, well-documented codebase
|
||||
* Composer-based dependency management
|
||||
* Production-optimized deployment script (bin/deploy.sh)
|
||||
|
||||
**Compatibility**
|
||||
|
||||
* WordPress: 6.7 - 6.9
|
||||
* PHP: 8.2 - 8.5
|
||||
* MariaDB: 10.6 or newer
|
||||
|
||||
**Tests**
|
||||
|
||||
* PHP Coding Standards: PHPCS 3.x with WordPress-Extra ruleset
|
||||
* WordPress Coding Standards: WPCS 3.3
|
||||
* PHPCompatibility: PHP 8.2 - 8.5 validated
|
||||
* Security Audit: Complete OWASP Top 10 coverage
|
||||
* Manual testing: WordPress 6.7, 6.8, 6.9
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
{
|
||||
"name": "robotstxt/documentation-markdown",
|
||||
"description": "Synchronizes Markdown documentation from GitHub repositories to WordPress pages and posts",
|
||||
"type": "wordpress-plugin",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"authors": [
|
||||
{
|
||||
"name": "ROBOTSTXT",
|
||||
"homepage": "https://www.robotstxt.es"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"league/commonmark": "^2.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
|
||||
"squizlabs/php_codesniffer": "^3.13",
|
||||
"wp-coding-standards/wpcs": "^3.3",
|
||||
"phpcompatibility/php-compatibility": "^9.3",
|
||||
"phpcompatibility/phpcompatibility-wp": "^2.1",
|
||||
"eduardovillao/wp-since": "^1.3",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^10.5"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"RobotsTxt\\DocumentationMarkdown\\": "includes/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
},
|
||||
"sort-packages": true,
|
||||
"optimize-autoloader": true
|
||||
},
|
||||
"scripts": {
|
||||
"phpcs": "phpcs",
|
||||
"phpcbf": "phpcbf",
|
||||
"phpstan": "phpstan analyse --memory-limit=512M",
|
||||
"test": "phpunit",
|
||||
"lint": [
|
||||
"@phpcs",
|
||||
"@phpstan"
|
||||
]
|
||||
},
|
||||
"scripts-descriptions": {
|
||||
"phpcs": "Run PHP_CodeSniffer to detect coding standard violations",
|
||||
"phpcbf": "Run PHP Code Beautifier to automatically fix coding standard violations",
|
||||
"phpstan": "Run PHPStan static analysis",
|
||||
"test": "Run PHPUnit tests",
|
||||
"lint": "Run all code quality checks"
|
||||
}
|
||||
}
|
||||
3027
composer.lock
generated
3027
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,811 +0,0 @@
|
|||
# Copyright (C) 2026 ROBOTSTXT
|
||||
# This file is distributed under the GPL-3.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Documentation Markdown (by ROBOTSTXT) 1.0.0-rc1\n"
|
||||
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/robotstxt-documentation-markdown\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2026-01-25T18:11:49+00:00\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"X-Generator: WP-CLI 2.12.0\n"
|
||||
"X-Domain: robotstxt-documentation-markdown\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
#: robotstxt-documentation-markdown.php
|
||||
msgid "Documentation Markdown (by ROBOTSTXT)"
|
||||
msgstr ""
|
||||
|
||||
#. Plugin URI of the plugin
|
||||
#: robotstxt-documentation-markdown.php
|
||||
msgid "https://github.com/robotstxt/documentation-markdown"
|
||||
msgstr ""
|
||||
|
||||
#. Description of the plugin
|
||||
#: robotstxt-documentation-markdown.php
|
||||
msgid "Synchronizes Markdown documentation from GitHub repositories to WordPress pages and posts automatically."
|
||||
msgstr ""
|
||||
|
||||
#. Author of the plugin
|
||||
#: robotstxt-documentation-markdown.php
|
||||
msgid "ROBOTSTXT"
|
||||
msgstr ""
|
||||
|
||||
#. Author URI of the plugin
|
||||
#: robotstxt-documentation-markdown.php
|
||||
msgid "https://robotstxt.com"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:52
|
||||
msgid "Manage Mappings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:53
|
||||
msgid "Mappings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:61
|
||||
#: includes/admin/Admin_Mappings.php:173
|
||||
#: includes/admin/Admin_Mappings.php:382
|
||||
msgid "Add New Mapping"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:62
|
||||
#: includes/admin/Admin_Mappings.php:136
|
||||
msgid "Add New"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:105
|
||||
#: includes/admin/Admin_Mappings.php:276
|
||||
msgid "Syncing..."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:106
|
||||
#: includes/admin/Admin_Mappings.php:653
|
||||
msgid "Sync completed successfully!"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:107
|
||||
msgid "Sync failed:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:108
|
||||
#: includes/admin/Admin_Mappings.php:342
|
||||
msgid "Are you sure you want to delete this mapping?"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:109
|
||||
msgid "Syncing all mappings..."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:124
|
||||
#: includes/admin/Admin_Mappings.php:357
|
||||
#: includes/admin/Admin_Settings.php:542
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:133
|
||||
msgid "Documentation Mappings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:145
|
||||
msgid "Total Mappings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:149
|
||||
msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:153
|
||||
msgid "Last Success"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:157
|
||||
msgid "Last Error"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:164
|
||||
msgid "Sync All Active Mappings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:171
|
||||
msgid "No mappings found. Create your first mapping to get started!"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:180
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:181
|
||||
msgid "Repository"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:182
|
||||
msgid "File"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:183
|
||||
msgid "Frequency"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:184
|
||||
msgid "Status"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:185
|
||||
msgid "Sync Status"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:186
|
||||
msgid "Last Sync"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:187
|
||||
msgid "Next Sync"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:188
|
||||
msgid "Actions"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:208
|
||||
#, php-format
|
||||
msgid "Branch: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:214
|
||||
#: includes/admin/Admin_Mappings.php:232
|
||||
msgid "Never"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:219
|
||||
msgid "Enabled"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:221
|
||||
msgid "Disabled"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:242
|
||||
msgid "Manual Only"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:272
|
||||
msgid "Never Synced"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:280
|
||||
msgid "Success"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:284
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:337
|
||||
msgid "Sync Now"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:340
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:343
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:382
|
||||
msgid "Edit Mapping"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:397
|
||||
msgid "Mapping Title"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:401
|
||||
msgid "A descriptive name for this mapping."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:407
|
||||
msgid "Repository Owner"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:411
|
||||
msgid "GitHub username or organization (e.g., \"wordpress\")."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:417
|
||||
msgid "Repository Name"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:421
|
||||
msgid "Repository name (e.g., \"wordpress-develop\")."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:427
|
||||
msgid "File Path"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:431
|
||||
msgid "Path to Markdown file (e.g., \"README.md\" or \"docs/api.md\")."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:437
|
||||
msgid "Branch"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:441
|
||||
msgid "Git branch name (default: \"main\")."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:447
|
||||
msgid "Target Post Type"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:463
|
||||
msgid "WordPress post type for the synced content."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:469
|
||||
msgid "Post Author"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:481
|
||||
msgid "WordPress user to assign as post author."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:487
|
||||
msgid "Sync Frequency"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:503
|
||||
msgid "How often to automatically sync this documentation from GitHub."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:509
|
||||
msgid "Enable Sync"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:514
|
||||
msgid "Enable automatic synchronization for this mapping"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:516
|
||||
msgid "Must be enabled for automatic syncing to work."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:521
|
||||
msgid "Update Mapping"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:521
|
||||
msgid "Create Mapping"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:536
|
||||
#: includes/admin/Admin_Mappings.php:593
|
||||
msgid "You do not have sufficient permissions."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:634
|
||||
#: includes/admin/Admin_Mappings.php:670
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:640
|
||||
msgid "Invalid mapping ID."
|
||||
msgstr ""
|
||||
|
||||
#. translators: 1: Success count, 2: Error count
|
||||
#: includes/admin/Admin_Mappings.php:690
|
||||
#, php-format
|
||||
msgid "Sync complete: %1$d succeeded, %2$d failed."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:711
|
||||
msgid "Mapping created successfully."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:712
|
||||
msgid "Mapping updated successfully."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:713
|
||||
msgid "Mapping deleted successfully."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:45
|
||||
msgid "Documentation Settings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:46
|
||||
msgid "Documentation"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:78
|
||||
msgid "GitHub Repository Settings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:85
|
||||
msgid "GitHub Documentation URL"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:93
|
||||
msgid "GitHub Personal Access Token"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:102
|
||||
msgid "Advanced Settings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:109
|
||||
msgid "Delete Data on Uninstall"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:118
|
||||
msgid "Email Notifications"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:125
|
||||
msgid "Enable Notifications"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:133
|
||||
msgid "Notification Email"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:141
|
||||
msgid "Notify on Success"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:149
|
||||
msgid "Notify Unsynchronized"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:181
|
||||
msgid "GitHub URL must use HTTPS protocol for security. URL was not saved."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:210
|
||||
msgid "Warning: Token could not be encrypted. It has been saved in plain text."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:251
|
||||
msgid "Configure your GitHub repository settings to synchronize documentation from GitHub to WordPress."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:267
|
||||
msgid "Advanced configuration options for plugin behavior."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:295
|
||||
msgid "Enter the full URL of your GitHub repository (e.g., https://github.com/owner/repository)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:322
|
||||
msgid "ghp_xxxxxxxxxxxx"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: Link to GitHub Personal Access Tokens page
|
||||
#: includes/admin/Admin_Settings.php:328
|
||||
#, php-format
|
||||
msgid "Enter your GitHub Personal Access Token. %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:333
|
||||
msgid "Create a token"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:340
|
||||
msgid "How to create a GitHub Personal Access Token:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:343
|
||||
msgid "Go to GitHub → Settings → Developer settings → Personal access tokens"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:344
|
||||
msgid "Click \"Generate new token\" (classic)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:345
|
||||
msgid "Give it a descriptive name (e.g., \"WordPress Documentation Sync\")"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:348
|
||||
msgid "For public repositories: No specific scopes needed"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:355
|
||||
msgid "For private repositories: Select the \"repo\" scope"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:361
|
||||
msgid "Click \"Generate token\" and copy it immediately"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:362
|
||||
msgid "Paste the token in the field above and save"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:367
|
||||
msgid "Note: For security, your token is stored encrypted and will be shown as dots once saved."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:397
|
||||
msgid "Delete all plugin data when uninstalling"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:401
|
||||
msgid "If enabled, all plugin settings and mapping configurations will be deleted when you uninstall the plugin."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:409
|
||||
msgid "Important:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:411
|
||||
msgid "Your synced documentation content (pages/posts) will NOT be deleted, only the mapping configurations and plugin settings."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:419
|
||||
msgid "This ensures your documentation remains available even after uninstalling the plugin."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:438
|
||||
msgid "Configure email notifications for sync events."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:458
|
||||
msgid "Send email notifications for sync errors"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:461
|
||||
msgid "You will receive an email when a sync operation fails (rate limited to once per hour per mapping)."
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: Default admin email
|
||||
#: includes/admin/Admin_Settings.php:482
|
||||
#, php-format
|
||||
msgid "Email address for notifications (default: %s)."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:503
|
||||
msgid "Send email notifications for successful syncs"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:506
|
||||
msgid "Receive an email confirmation when content is successfully synchronized (not recommended for frequent syncs)."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:524
|
||||
msgid "Notify when mappings have not been synchronized"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:527
|
||||
msgid "Receive alerts when enabled mappings with auto-sync have never been synchronized (rate limited to once per hour)."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:550
|
||||
msgid "Settings saved successfully."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:563
|
||||
msgid "Documentation Markdown allows you to automatically synchronize Markdown documentation from GitHub repositories to your WordPress site."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:575
|
||||
msgid "Save Settings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:580
|
||||
msgid "About This Plugin"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:583
|
||||
msgid "This plugin synchronizes Markdown documentation from GitHub repositories and converts it into WordPress pages or posts automatically."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:590
|
||||
msgid "Version:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:594
|
||||
msgid "Developer:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:43
|
||||
msgid "Never (Manual Only)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:47
|
||||
msgid "Every 1 Minute"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:51
|
||||
msgid "Every 5 Minutes"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:55
|
||||
msgid "Every 10 Minutes"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:59
|
||||
msgid "Every 15 Minutes"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:63
|
||||
msgid "Every 30 Minutes"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:67
|
||||
msgid "Every 1 Hour"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:71
|
||||
msgid "Every 2 Hours"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:75
|
||||
msgid "Every 4 Hours"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:79
|
||||
msgid "Every 6 Hours"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:83
|
||||
msgid "Every 12 Hours"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:87
|
||||
msgid "Once Daily"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:238
|
||||
#: includes/Cron_Manager.php:247
|
||||
msgid "Now"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: Mapping title
|
||||
#: includes/Cron_Manager.php:286
|
||||
#, php-format
|
||||
msgid "[Documentation Sync] Error: %s"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: Mapping title
|
||||
#: includes/Cron_Manager.php:332
|
||||
#, php-format
|
||||
msgid "[Documentation Sync] Success: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:397
|
||||
msgid "[Documentation Sync] Unsynchronized Files Found"
|
||||
msgstr ""
|
||||
|
||||
#. translators: 1: Mapping title, 2: Error message
|
||||
#: includes/Cron_Manager.php:491
|
||||
#, php-format
|
||||
msgid ""
|
||||
"Failed to sync documentation: %1$s\n"
|
||||
"\n"
|
||||
"Error: %2$s\n"
|
||||
"\n"
|
||||
""
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:496
|
||||
#: includes/Cron_Manager.php:538
|
||||
msgid ""
|
||||
"Repository Details:\n"
|
||||
""
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: Admin URL
|
||||
#: includes/Cron_Manager.php:504
|
||||
#, php-format
|
||||
msgid ""
|
||||
"Manage mappings: %s\n"
|
||||
""
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: Mapping title
|
||||
#: includes/Cron_Manager.php:522
|
||||
#, php-format
|
||||
msgid ""
|
||||
"Successfully synchronized documentation: %s\n"
|
||||
"\n"
|
||||
""
|
||||
msgstr ""
|
||||
|
||||
#. translators: 1: Post title, 2: Post URL
|
||||
#: includes/Cron_Manager.php:531
|
||||
#, php-format
|
||||
msgid ""
|
||||
"Post: %1$s\n"
|
||||
"View: %2$s\n"
|
||||
"\n"
|
||||
""
|
||||
msgstr ""
|
||||
|
||||
#. translators: %d: Number of unsynchronized mappings
|
||||
#: includes/Cron_Manager.php:558
|
||||
#, php-format
|
||||
msgid "There is %d documentation mapping that has not been synchronized:"
|
||||
msgid_plural "There are %d documentation mappings that have not been synchronized:"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#. translators: %s: Admin URL
|
||||
#: includes/Cron_Manager.php:579
|
||||
#, php-format
|
||||
msgid ""
|
||||
"Please review and sync these mappings: %s\n"
|
||||
""
|
||||
msgstr ""
|
||||
|
||||
#: includes/encryption-functions.php:34
|
||||
msgid "OpenSSL extension is required for token encryption"
|
||||
msgstr ""
|
||||
|
||||
#: includes/encryption-functions.php:47
|
||||
#: includes/encryption-functions.php:112
|
||||
msgid "Failed to determine cipher IV length"
|
||||
msgstr ""
|
||||
|
||||
#: includes/encryption-functions.php:64
|
||||
msgid "Encryption failed"
|
||||
msgstr ""
|
||||
|
||||
#: includes/encryption-functions.php:90
|
||||
msgid "OpenSSL extension is required for token decryption"
|
||||
msgstr ""
|
||||
|
||||
#: includes/encryption-functions.php:99
|
||||
msgid "Invalid encrypted data format"
|
||||
msgstr ""
|
||||
|
||||
#: includes/encryption-functions.php:130
|
||||
msgid "Decryption failed"
|
||||
msgstr ""
|
||||
|
||||
#: includes/GitHub_API_Client.php:81
|
||||
msgid "Invalid response from GitHub API: missing required fields"
|
||||
msgstr ""
|
||||
|
||||
#: includes/GitHub_API_Client.php:91
|
||||
msgid "Failed to decode file content from GitHub"
|
||||
msgstr ""
|
||||
|
||||
#: includes/GitHub_API_Client.php:132
|
||||
msgid "SHA not found in GitHub API response"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: Error message
|
||||
#: includes/GitHub_API_Client.php:213
|
||||
#, php-format
|
||||
msgid "GitHub API request failed: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/GitHub_API_Client.php:230
|
||||
msgid "Unknown error"
|
||||
msgstr ""
|
||||
|
||||
#: includes/GitHub_API_Client.php:234
|
||||
msgid "Authentication failed. Please check your GitHub token."
|
||||
msgstr ""
|
||||
|
||||
#: includes/GitHub_API_Client.php:235
|
||||
msgid "Access forbidden. Check your token permissions or rate limit."
|
||||
msgstr ""
|
||||
|
||||
#: includes/GitHub_API_Client.php:236
|
||||
msgid "File not found in repository. Please verify the file path and branch."
|
||||
msgstr ""
|
||||
|
||||
#. translators: 1: HTTP status code, 2: Error message
|
||||
#: includes/GitHub_API_Client.php:239
|
||||
#, php-format
|
||||
msgid "GitHub API error (%1$d): %2$s"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: JSON error message
|
||||
#: includes/GitHub_API_Client.php:255
|
||||
#, php-format
|
||||
msgid "Invalid JSON response from GitHub: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/GitHub_API_Client.php:347
|
||||
msgid "unknown"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: Reset time
|
||||
#: includes/GitHub_API_Client.php:352
|
||||
#, php-format
|
||||
msgid "GitHub API rate limit nearly exceeded. Resets at: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:73
|
||||
#: includes/mapping-functions.php:118
|
||||
msgid "Invalid mapping ID"
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:124
|
||||
msgid "Failed to delete mapping"
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:227
|
||||
msgid "Repository owner is required"
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:233
|
||||
msgid "Invalid repository owner format"
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:238
|
||||
msgid "Repository name is required"
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:244
|
||||
msgid "Invalid repository name format"
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:249
|
||||
msgid "File path is required"
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:256
|
||||
msgid "File path cannot contain \"..\""
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:261
|
||||
msgid "File must be .md or .markdown"
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:268
|
||||
msgid "Invalid branch name format"
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:275
|
||||
msgid "Post type does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Plugin.php:172
|
||||
msgid "GitHub Doc Mappings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Plugin.php:173
|
||||
msgid "GitHub Doc Mapping"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Sync_Manager.php:84
|
||||
#: includes/Sync_Manager.php:422
|
||||
msgid "Synchronization is disabled for this mapping."
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: Error message from GitHub API
|
||||
#: includes/Sync_Manager.php:259
|
||||
#, php-format
|
||||
msgid "Failed to fetch file from GitHub: %s"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: Error message
|
||||
#: includes/Sync_Manager.php:441
|
||||
#, php-format
|
||||
msgid "Failed to check for changes: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Sync_Manager.php:498
|
||||
msgid "GitHub Personal Access Token is not configured."
|
||||
msgstr ""
|
||||
|
||||
#: includes/Sync_Manager.php:504
|
||||
msgid "No active mappings configured."
|
||||
msgstr ""
|
||||
|
||||
#: includes/Sync_Manager.php:509
|
||||
msgid "OpenSSL extension is not available (required for token encryption)."
|
||||
msgstr ""
|
||||
299
readme.txt
Normal file
299
readme.txt
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
=== Documentation Markdown (by ROBOTSTXT) ===
|
||||
Contributors: robotstxt
|
||||
Tags: github, documentation, markdown, sync, automation
|
||||
Requires at least: 6.7
|
||||
Tested up to: 7.0
|
||||
Requires PHP: 8.2
|
||||
Stable tag: 1.1.0
|
||||
License: GPLv3 or later
|
||||
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
Synchronize Markdown documentation from GitHub repositories to WordPress pages and posts automatically.
|
||||
|
||||
== Description ==
|
||||
|
||||
**Documentation Markdown** is a powerful WordPress plugin that enables seamless synchronization of Markdown documentation files from GitHub repositories into your WordPress site. Perfect for maintaining technical documentation, API references, knowledge bases, and more - all with the power of version control.
|
||||
|
||||
= Key Features =
|
||||
|
||||
* 🔄 **Automatic Synchronization** - Schedule automatic syncs via WordPress Cron (hourly, twice daily, daily)
|
||||
* 📝 **Markdown to HTML** - Convert GitHub Flavored Markdown to clean HTML using CommonMark
|
||||
* 🎯 **Flexible Mapping** - Map individual MD files to specific WordPress posts or pages
|
||||
* 🔐 **Secure** - Encrypted GitHub token storage, full input validation & output escaping
|
||||
* 🌍 **Translatable** - Full internationalization support (i18n/l10n ready)
|
||||
* 📚 **Multi-Repository** - Sync from multiple GitHub repos simultaneously
|
||||
* ⚡ **Manual Sync** - On-demand synchronization from admin interface
|
||||
* 🐛 **Debug Tools** - Built-in debugging tools (visible when WP_DEBUG is enabled)
|
||||
|
||||
= Use Cases =
|
||||
|
||||
* **API Documentation** - Keep your API docs in sync between GitHub and WordPress
|
||||
* **Technical Documentation** - Maintain version-controlled technical docs
|
||||
* **Knowledge Base** - Build a knowledge base powered by GitHub
|
||||
* **Blog Posts** - Write blog posts in Markdown with Git workflow
|
||||
* **Product Documentation** - Sync product documentation from your repository
|
||||
* **Multi-language Documentation** - Manage translations in GitHub, publish to WordPress
|
||||
|
||||
= How It Works =
|
||||
|
||||
1. Configure your GitHub Personal Access Token in plugin settings
|
||||
2. Create mappings between GitHub Markdown files and WordPress content
|
||||
3. Choose synchronization frequency (manual, hourly, twice daily, daily)
|
||||
4. The plugin automatically fetches and converts Markdown to HTML
|
||||
5. Your WordPress content stays in sync with your GitHub repository
|
||||
|
||||
= Requirements =
|
||||
|
||||
* PHP 8.2 or higher
|
||||
* WordPress 6.9 or higher
|
||||
* GitHub Personal Access Token (free, for accessing repositories)
|
||||
* Composer (for production build with dependencies)
|
||||
|
||||
= Security =
|
||||
|
||||
* GitHub tokens encrypted at rest (AES-256-CBC)
|
||||
* All user input sanitized
|
||||
* All output escaped
|
||||
* Nonce verification on all forms
|
||||
* Capability checks for all admin actions
|
||||
* Prepared statements for database queries
|
||||
* Rate limiting for GitHub API
|
||||
|
||||
= Developer Friendly =
|
||||
|
||||
* Clean, well-documented code
|
||||
* Follows WordPress Coding Standards (WPCS)
|
||||
* Modern PHP 8.2+ features
|
||||
* Extensive PHPDoc documentation
|
||||
* Procedural approach (KISS principles)
|
||||
* Extensible with WordPress hooks and filters
|
||||
|
||||
== Installation ==
|
||||
|
||||
= Automatic Installation =
|
||||
|
||||
1. Log in to your WordPress admin panel
|
||||
2. Navigate to Plugins → Add New
|
||||
3. Search for "Documentation Markdown ROBOTSTXT"
|
||||
4. Click "Install Now" and then "Activate"
|
||||
|
||||
= Manual Installation =
|
||||
|
||||
1. Download the plugin ZIP file
|
||||
2. Upload to `/wp-content/plugins/` directory
|
||||
3. Extract the files
|
||||
4. Ensure Composer dependencies are installed (`composer install --no-dev`)
|
||||
5. Activate the plugin through the 'Plugins' menu in WordPress
|
||||
|
||||
= After Installation =
|
||||
|
||||
1. Navigate to 'Documentation → Settings' in the WordPress admin menu
|
||||
2. Generate a GitHub Personal Access Token:
|
||||
- Go to GitHub → Settings → Developer settings → Personal access tokens
|
||||
- Click "Generate new token"
|
||||
- For public repositories: No specific scopes needed
|
||||
- For private repositories: Select `repo` scope
|
||||
3. Paste your token in the plugin settings and save
|
||||
4. Create your first mapping under 'Documentation → Mappings'
|
||||
|
||||
== Frequently Asked Questions ==
|
||||
|
||||
= Do I need a GitHub account? =
|
||||
|
||||
Yes, you need a GitHub account to generate a Personal Access Token. The token is required to access repositories (public or private).
|
||||
|
||||
= Can I sync from private repositories? =
|
||||
|
||||
Yes! When generating your GitHub Personal Access Token, make sure to select the `repo` scope for full access to private repositories.
|
||||
|
||||
= How often does synchronization happen? =
|
||||
|
||||
You can configure synchronization frequency per mapping:
|
||||
* Manual only (on-demand via "Sync Now" button)
|
||||
* Hourly
|
||||
* Twice daily
|
||||
* Daily
|
||||
|
||||
You can also manually trigger sync at any time.
|
||||
|
||||
= Will the plugin delete my WordPress content if I uninstall it? =
|
||||
|
||||
By default, NO. When you uninstall the plugin, it preserves all synced WordPress pages/posts. However, there's an option in Settings to delete plugin data on uninstall (mappings, settings, etc.) - but this never deletes the actual WordPress content, only the plugin configuration.
|
||||
|
||||
= Can I sync multiple files from the same repository? =
|
||||
|
||||
Yes! You can create multiple mappings, each pointing to different files in the same repository or different repositories.
|
||||
|
||||
= What Markdown syntax is supported? =
|
||||
|
||||
The plugin uses CommonMark (league/commonmark), which supports GitHub Flavored Markdown including:
|
||||
* Headings, paragraphs, lists
|
||||
* Code blocks with syntax highlighting
|
||||
* Tables
|
||||
* Links and images
|
||||
* Blockquotes
|
||||
* And more!
|
||||
|
||||
= Does it support images from GitHub? =
|
||||
|
||||
Currently, Markdown image links are converted to HTML, but images are not downloaded. Images must be publicly accessible via their GitHub URLs or you need to host them separately.
|
||||
|
||||
= Can I customize the HTML output? =
|
||||
|
||||
The plugin converts Markdown to standard HTML. You can style the output using your theme's CSS by targeting the content area where documentation is displayed.
|
||||
|
||||
= Is there a limit on file size? =
|
||||
|
||||
While there's no hard limit imposed by the plugin, GitHub API has size limitations. Very large files (>10MB) may cause issues. We recommend keeping documentation files under 1MB for best performance.
|
||||
|
||||
= What happens if GitHub is unavailable? =
|
||||
|
||||
If GitHub API is unreachable during a scheduled sync, the plugin will fail gracefully and retry on the next scheduled interval. Your existing content remains unchanged.
|
||||
|
||||
= Can I edit synced content in WordPress? =
|
||||
|
||||
You can edit synced content in WordPress, but be aware that the next synchronization will overwrite your changes with content from GitHub. We recommend making all edits in your GitHub repository.
|
||||
|
||||
= How do I debug synchronization issues? =
|
||||
|
||||
Enable WP_DEBUG in your wp-config.php:
|
||||
```
|
||||
define('WP_DEBUG', true);
|
||||
```
|
||||
|
||||
Then go to Documentation → Settings, and you'll see a "Debug Tools" section at the bottom with:
|
||||
* Test GitHub repository connection
|
||||
* Test GitHub token validity
|
||||
* View scheduled cron jobs
|
||||
* Manually run cron jobs
|
||||
* Clear plugin caches
|
||||
|
||||
== Screenshots ==
|
||||
|
||||
1. Plugin Settings - Configure GitHub repository and token
|
||||
2. Mappings List - View all your GitHub-to-WordPress mappings
|
||||
3. Add/Edit Mapping - Create new mapping between GitHub file and WordPress content
|
||||
4. Sync Status - Monitor synchronization status and history
|
||||
5. Debug Tools - Built-in debugging interface (shown when WP_DEBUG is enabled)
|
||||
|
||||
== Compatibility ==
|
||||
|
||||
* WordPress: 6.7 - 6.9
|
||||
* PHP: 8.2 - 8.5
|
||||
|
||||
== Changelog ==
|
||||
|
||||
For the complete changelog, see [changelog.txt](https://git.robotstxt.es/ROBOTSTXT/robotstxt-documentation-markdown/raw/branch/main/changelog.txt).
|
||||
|
||||
= 1.1.0 - 2026-03-28 =
|
||||
|
||||
**Security**
|
||||
|
||||
* Patched CVE-2026-33347 and CVE-2026-30838 (league/commonmark updated to 2.8.2)
|
||||
|
||||
**Changed**
|
||||
|
||||
* Access level changed from administrator (manage_options) to editor (edit_pages) — editors can now manage documentation mappings without requiring full admin access
|
||||
|
||||
**Fixed**
|
||||
|
||||
* PHPStan level 9 compliance: zero errors across all plugin files (down from 104)
|
||||
* target_order (menu_order) field was in the UI but never saved or applied — now fully implemented
|
||||
* Type-safety improvements for all WordPress API returns (get_option, get_post_meta, json_decode, size_format)
|
||||
* Token decryption false return handled correctly
|
||||
* Uninstall data cleanup narrows mixed option return before array access
|
||||
|
||||
**Compatibility**
|
||||
|
||||
* Verified compatible with WordPress 6.8 and 7.0
|
||||
* Verified compatible with PHP 8.4.x
|
||||
|
||||
= 1.0.0 - 2026-01-26 =
|
||||
|
||||
**Initial Release**
|
||||
|
||||
* ✨ Core synchronization functionality
|
||||
* 🔄 Automatic scheduled sync (hourly, twice daily, daily)
|
||||
* ⚡ Manual on-demand sync
|
||||
* 📝 Markdown to HTML conversion using CommonMark
|
||||
* 🎯 Flexible file-to-content mapping system
|
||||
* 🔐 Encrypted GitHub token storage
|
||||
* 🌍 Full internationalization support
|
||||
* 📚 Multi-repository support
|
||||
* 🎨 Clean admin interface with status badges
|
||||
* 🔧 Custom Post Type for mapping management
|
||||
* 📦 Support for pages, posts, and custom post types
|
||||
* 👤 Configurable post author and parent page
|
||||
* 🔢 Page order (menu_order) support
|
||||
* 🐛 Debug tools for troubleshooting
|
||||
* 🔄 Cron job management and repair tools
|
||||
* 🧹 Clean uninstall with optional data deletion
|
||||
* ✅ WordPress Coding Standards compliant
|
||||
* 🛡️ Security best practices (nonces, escaping, sanitization)
|
||||
|
||||
**Admin Features:**
|
||||
* Settings page for GitHub configuration
|
||||
* Mappings management interface
|
||||
* Add/Edit mapping with validation
|
||||
* Sync status monitoring
|
||||
* Debug tools (when WP_DEBUG enabled)
|
||||
- Test GitHub connection
|
||||
- Test token validity
|
||||
- View scheduled crons
|
||||
- Run crons manually
|
||||
- Fix cron schedules
|
||||
- Clear plugin caches
|
||||
|
||||
**Developer Features:**
|
||||
* Procedural PHP following KISS principles
|
||||
* PHP 8.2+ modern features
|
||||
* Complete PHPDoc documentation
|
||||
* WordPress hooks and filters
|
||||
* Extensible architecture
|
||||
* PHPCS and PHPStan validated
|
||||
|
||||
== Upgrade Notice ==
|
||||
|
||||
= 1.0.0 =
|
||||
Initial release of Documentation Markdown. Sync your GitHub Markdown files to WordPress automatically!
|
||||
|
||||
== Additional Information ==
|
||||
|
||||
= Support =
|
||||
|
||||
* **Documentation:** Comprehensive docs included in `/docs/` directory
|
||||
* **Repository:** [Report issues](https://git.robotstxt.es/ROBOTSTXT/robotstxt-documentation-markdown/issues)
|
||||
* **Website:** [ROBOTSTXT.es](https://www.robotstxt.es/)
|
||||
* **Security:** robotstxt@robotstxt.es
|
||||
|
||||
= Contributing =
|
||||
|
||||
We welcome contributions! Please visit our [Gitea repository](https://git.robotstxt.es/ROBOTSTXT/robotstxt-documentation-markdown) to:
|
||||
* Report bugs
|
||||
* Suggest features
|
||||
* Submit pull requests
|
||||
|
||||
= Privacy =
|
||||
|
||||
This plugin does not collect or store any user data. The only external connection made is to the GitHub API to fetch repository content. Your GitHub Personal Access Token is encrypted and stored locally in your WordPress database.
|
||||
|
||||
= Credits =
|
||||
|
||||
Developed by ROBOTSTXT with ❤️
|
||||
|
||||
**Dependencies:**
|
||||
* [league/commonmark](https://commonmark.thephpleague.com/) - Markdown parser and converter
|
||||
|
||||
= License =
|
||||
|
||||
This plugin is licensed under the GNU General Public License v3.0 or later.
|
||||
|
||||
== Compliance ==
|
||||
|
||||
This plugin adheres to the following security measures and review protocols for each version:
|
||||
|
||||
* [WordPress Plugin Handbook](https://developer.wordpress.org/plugins/)
|
||||
* [WordPress Plugin Security](https://developer.wordpress.org/plugins/wordpress-org/plugin-security/)
|
||||
* [WordPress APIs Security](https://developer.wordpress.org/apis/security/)
|
||||
* [WordPress Coding Standards](https://github.com/WordPress/WordPress-Coding-Standards)
|
||||
* [Plugin Check (PCP)](https://wordpress.org/plugins/plugin-check/)
|
||||
|
|
@ -16,9 +16,9 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
* @since 1.0.0
|
||||
*
|
||||
* @param string $token Plain token.
|
||||
* @return string Encrypted token.
|
||||
* @return string Encrypted token, or empty string on failure.
|
||||
*/
|
||||
function robotstxt_docmd_encrypt_token( $token ) {
|
||||
function robotstxt_docmd_encrypt_token( string $token ): string {
|
||||
if ( empty( $token ) ) {
|
||||
return '';
|
||||
}
|
||||
|
|
@ -27,9 +27,12 @@ function robotstxt_docmd_encrypt_token( $token ) {
|
|||
$key = wp_salt( 'auth' );
|
||||
$iv_length = openssl_cipher_iv_length( 'aes-256-cbc' );
|
||||
$iv = openssl_random_pseudo_bytes( $iv_length );
|
||||
|
||||
$encrypted = openssl_encrypt( $token, 'aes-256-cbc', $key, 0, $iv );
|
||||
|
||||
if ( false === $encrypted ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return base64_encode( $iv . $encrypted ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
}
|
||||
|
||||
|
|
@ -39,9 +42,9 @@ function robotstxt_docmd_encrypt_token( $token ) {
|
|||
* @since 1.0.0
|
||||
*
|
||||
* @param string $encrypted_token Encrypted token.
|
||||
* @return string Plain token.
|
||||
* @return string Plain token, or empty string on failure.
|
||||
*/
|
||||
function robotstxt_docmd_decrypt_token( $encrypted_token ) {
|
||||
function robotstxt_docmd_decrypt_token( string $encrypted_token ): string {
|
||||
if ( empty( $encrypted_token ) ) {
|
||||
return '';
|
||||
}
|
||||
|
|
@ -53,7 +56,8 @@ function robotstxt_docmd_decrypt_token( $encrypted_token ) {
|
|||
$iv = substr( $decoded, 0, $iv_length );
|
||||
$encrypted = substr( $decoded, $iv_length );
|
||||
|
||||
return openssl_decrypt( $encrypted, 'aes-256-cbc', $key, 0, $iv );
|
||||
$decrypted = openssl_decrypt( $encrypted, 'aes-256-cbc', $key, 0, $iv );
|
||||
return false !== $decrypted ? $decrypted : '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -66,10 +70,11 @@ function robotstxt_docmd_decrypt_token( $encrypted_token ) {
|
|||
* @param string $file_path File path.
|
||||
* @return string Generated title.
|
||||
*/
|
||||
function robotstxt_docmd_generate_title_from_path( $file_path ) {
|
||||
function robotstxt_docmd_generate_title_from_path( string $file_path ): string {
|
||||
// Get filename without extension.
|
||||
$filename = basename( $file_path );
|
||||
$title = preg_replace( '/\.(md|markdown)$/i', '', $filename );
|
||||
$title = is_string( $title ) ? $title : $filename;
|
||||
|
||||
// Replace separators with spaces.
|
||||
$title = str_replace( array( '-', '_' ), ' ', $title );
|
||||
|
|
@ -91,7 +96,8 @@ function robotstxt_docmd_generate_title_from_path( $file_path ) {
|
|||
);
|
||||
|
||||
foreach ( $abbreviations as $search => $replace ) {
|
||||
$title = preg_replace( '/\b' . $search . '\b/', $replace, $title );
|
||||
$result = preg_replace( '/\b' . $search . '\b/', $replace, $title );
|
||||
$title = is_string( $result ) ? $result : $title;
|
||||
}
|
||||
|
||||
return $title;
|
||||
|
|
@ -105,7 +111,7 @@ function robotstxt_docmd_generate_title_from_path( $file_path ) {
|
|||
* @param string $status Status (never, pending, success, error).
|
||||
* @return string Badge HTML.
|
||||
*/
|
||||
function robotstxt_docmd_get_status_badge( $status ) {
|
||||
function robotstxt_docmd_get_status_badge( string $status ): string {
|
||||
$badges = array(
|
||||
'never' => array(
|
||||
'class' => 'status-never',
|
||||
|
|
@ -133,3 +139,41 @@ function robotstxt_docmd_get_status_badge( $status ) {
|
|||
esc_html( $badge['label'] )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely get a string value from a superglobal array
|
||||
*
|
||||
* Provides PHPStan level 9 compliant type-narrowing for superglobal access.
|
||||
*
|
||||
* @since 1.0.1
|
||||
*
|
||||
* @param array<string, mixed> $input The superglobal array ($_POST, $_GET, etc.).
|
||||
* @param string $key The key to look for.
|
||||
* @param string $fallback Default value if key not found or not a string.
|
||||
* @return string
|
||||
*/
|
||||
function robotstxt_docmd_input_string( array $input, string $key, string $fallback = '' ): string {
|
||||
if ( isset( $input[ $key ] ) && is_string( $input[ $key ] ) ) {
|
||||
return $input[ $key ];
|
||||
}
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely get an integer value from a superglobal array
|
||||
*
|
||||
* Provides PHPStan level 9 compliant type-narrowing for superglobal access.
|
||||
*
|
||||
* @since 1.0.1
|
||||
*
|
||||
* @param array<string, mixed> $input The superglobal array ($_POST, $_GET, etc.).
|
||||
* @param string $key The key to look for.
|
||||
* @param int $fallback Default value if key not found or not numeric.
|
||||
* @return int
|
||||
*/
|
||||
function robotstxt_docmd_input_int( array $input, string $key, int $fallback = 0 ): int {
|
||||
if ( isset( $input[ $key ] ) && is_numeric( $input[ $key ] ) ) {
|
||||
return (int) $input[ $key ];
|
||||
}
|
||||
return $fallback;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
* @since 1.0.0
|
||||
*
|
||||
* @param string $url GitHub repository URL.
|
||||
* @return array|WP_Error Array with 'owner' and 'repo' or error.
|
||||
* @return array{owner: string, repo: string}|WP_Error Array with 'owner' and 'repo' or error.
|
||||
*/
|
||||
function robotstxt_docmd_parse_github_url( $url ) {
|
||||
function robotstxt_docmd_parse_github_url( string $url ): array|WP_Error {
|
||||
if ( ! str_starts_with( $url, 'https://' ) ) {
|
||||
return new WP_Error( 'invalid_url', __( 'GitHub URL must use HTTPS', 'robotstxt-documentation-markdown' ) );
|
||||
}
|
||||
|
|
@ -33,6 +33,43 @@ function robotstxt_docmd_parse_github_url( $url ) {
|
|||
return new WP_Error( 'invalid_format', __( 'Could not parse GitHub URL', 'robotstxt-documentation-markdown' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and normalize a list of file data from cache or API response
|
||||
*
|
||||
* Converts untyped cache data back into the expected typed structure.
|
||||
* Required for PHPStan level 9 compliance with transient cache usage.
|
||||
*
|
||||
* @since 1.0.1
|
||||
*
|
||||
* @param mixed $data Raw data to validate (typically from get_transient()).
|
||||
* @return list<array{path: string, name: string, size: int, sha: string}>
|
||||
*/
|
||||
function robotstxt_docmd_validate_file_list( mixed $data ): array {
|
||||
if ( ! is_array( $data ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$result = array();
|
||||
foreach ( $data as $item ) {
|
||||
if ( is_array( $item )
|
||||
&& isset( $item['path'], $item['name'], $item['size'], $item['sha'] )
|
||||
&& is_string( $item['path'] )
|
||||
&& is_string( $item['name'] )
|
||||
&& is_int( $item['size'] )
|
||||
&& is_string( $item['sha'] )
|
||||
) {
|
||||
$result[] = array(
|
||||
'path' => $item['path'],
|
||||
'name' => $item['name'],
|
||||
'size' => $item['size'],
|
||||
'sha' => $item['sha'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all markdown files from repository
|
||||
*
|
||||
|
|
@ -42,15 +79,15 @@ function robotstxt_docmd_parse_github_url( $url ) {
|
|||
* @param string $repo Repository name.
|
||||
* @param string $branch Branch name.
|
||||
* @param string $token GitHub token (encrypted).
|
||||
* @return array|WP_Error Array of files or error.
|
||||
* @return list<array{path: string, name: string, size: int, sha: string}>|WP_Error Array of files or error.
|
||||
*/
|
||||
function robotstxt_docmd_get_all_markdown_files( $owner, $repo, $branch, $token ) {
|
||||
function robotstxt_docmd_get_all_markdown_files( string $owner, string $repo, string $branch, string $token ): array|WP_Error {
|
||||
// Check cache first.
|
||||
$cache_key = sprintf( 'robotstxt_docmd_files_%s_%s_%s', $owner, $repo, $branch );
|
||||
$cached = get_transient( $cache_key );
|
||||
|
||||
if ( false !== $cached ) {
|
||||
return $cached;
|
||||
return robotstxt_docmd_validate_file_list( $cached );
|
||||
}
|
||||
|
||||
// Decrypt token.
|
||||
|
|
@ -63,16 +100,13 @@ function robotstxt_docmd_get_all_markdown_files( $owner, $repo, $branch, $token
|
|||
return $files;
|
||||
}
|
||||
|
||||
// Filter only markdown files.
|
||||
$markdown_files = array_filter(
|
||||
$files,
|
||||
function ( $file ) {
|
||||
return preg_match( '/\.(md|markdown)$/i', $file['path'] );
|
||||
// Filter only markdown files and re-index.
|
||||
$markdown_files = array();
|
||||
foreach ( $files as $file ) {
|
||||
if ( preg_match( '/\.(md|markdown)$/i', $file['path'] ) ) {
|
||||
$markdown_files[] = $file;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Re-index array.
|
||||
$markdown_files = array_values( $markdown_files );
|
||||
|
||||
// Cache for 24 hours.
|
||||
set_transient( $cache_key, $markdown_files, DAY_IN_SECONDS );
|
||||
|
|
@ -91,9 +125,9 @@ function robotstxt_docmd_get_all_markdown_files( $owner, $repo, $branch, $token
|
|||
* @param string $branch Branch name.
|
||||
* @param string $token GitHub token (plain).
|
||||
* @param int $depth Current depth (for recursion limit).
|
||||
* @return array|WP_Error Array of files or error.
|
||||
* @return list<array{path: string, name: string, size: int, sha: string}>|WP_Error Array of files or error.
|
||||
*/
|
||||
function robotstxt_docmd_get_repository_contents_recursive( $owner, $repo, $path, $branch, $token, $depth = 0 ) {
|
||||
function robotstxt_docmd_get_repository_contents_recursive( string $owner, string $repo, string $path, string $branch, string $token, int $depth = 0 ): array|WP_Error {
|
||||
// Prevent infinite recursion.
|
||||
if ( $depth > 10 ) {
|
||||
return array();
|
||||
|
|
@ -145,6 +179,17 @@ function robotstxt_docmd_get_repository_contents_recursive( $owner, $repo, $path
|
|||
$files = array();
|
||||
|
||||
foreach ( $contents as $item ) {
|
||||
if ( ! is_array( $item )
|
||||
|| ! isset( $item['type'], $item['path'], $item['name'], $item['size'], $item['sha'] )
|
||||
|| ! is_string( $item['type'] )
|
||||
|| ! is_string( $item['path'] )
|
||||
|| ! is_string( $item['name'] )
|
||||
|| ! is_string( $item['sha'] )
|
||||
|| ! is_int( $item['size'] )
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( 'file' === $item['type'] ) {
|
||||
// Skip files larger than 10MB.
|
||||
if ( $item['size'] > 10 * 1024 * 1024 ) {
|
||||
|
|
@ -189,7 +234,7 @@ function robotstxt_docmd_get_repository_contents_recursive( $owner, $repo, $path
|
|||
* @param string $token GitHub token (encrypted).
|
||||
* @return string|WP_Error File content or error.
|
||||
*/
|
||||
function robotstxt_docmd_get_file_content( $owner, $repo, $file_path, $branch, $token ) {
|
||||
function robotstxt_docmd_get_file_content( string $owner, string $repo, string $file_path, string $branch, string $token ): string|WP_Error {
|
||||
$decrypted_token = robotstxt_docmd_decrypt_token( $token );
|
||||
|
||||
$url = sprintf(
|
||||
|
|
@ -231,7 +276,11 @@ function robotstxt_docmd_get_file_content( $owner, $repo, $file_path, $branch, $
|
|||
$body = wp_remote_retrieve_body( $response );
|
||||
$data = json_decode( $body, true );
|
||||
|
||||
if ( ! isset( $data['content'] ) || ! isset( $data['encoding'] ) ) {
|
||||
if ( ! is_array( $data )
|
||||
|| ! isset( $data['content'], $data['encoding'] )
|
||||
|| ! is_string( $data['content'] )
|
||||
|| ! is_string( $data['encoding'] )
|
||||
) {
|
||||
return new WP_Error( 'invalid_response', __( 'Invalid file response from GitHub', 'robotstxt-documentation-markdown' ) );
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return array Array of mappings.
|
||||
* @return list<MappingData> Array of mappings.
|
||||
*/
|
||||
function robotstxt_docmd_get_all_mappings() {
|
||||
function robotstxt_docmd_get_all_mappings(): array {
|
||||
$args = array(
|
||||
'post_type' => 'robotstxt_map',
|
||||
'posts_per_page' => -1,
|
||||
|
|
@ -30,6 +30,9 @@ function robotstxt_docmd_get_all_mappings() {
|
|||
$mappings = array();
|
||||
|
||||
foreach ( $query->posts as $post ) {
|
||||
if ( ! ( $post instanceof WP_Post ) ) {
|
||||
continue;
|
||||
}
|
||||
$mapping = robotstxt_docmd_get_mapping( $post->ID );
|
||||
if ( $mapping ) {
|
||||
$mappings[] = $mapping;
|
||||
|
|
@ -47,23 +50,34 @@ function robotstxt_docmd_get_all_mappings() {
|
|||
* @since 1.0.0
|
||||
*
|
||||
* @param int $mapping_id Mapping post ID.
|
||||
* @return array|null Mapping data or null.
|
||||
* @return MappingData|null Mapping data or null.
|
||||
*/
|
||||
function robotstxt_docmd_get_mapping( $mapping_id ) {
|
||||
function robotstxt_docmd_get_mapping( int $mapping_id ): ?array {
|
||||
$post = get_post( $mapping_id );
|
||||
|
||||
if ( ! $post || 'robotstxt_map' !== $post->post_type ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$repo_owner = get_post_meta( $mapping_id, '_robotstxt_docmd_repo_owner', true );
|
||||
$repo_name = get_post_meta( $mapping_id, '_robotstxt_docmd_repo_name', true );
|
||||
$file_path = get_post_meta( $mapping_id, '_robotstxt_docmd_file_path', true );
|
||||
$branch = get_post_meta( $mapping_id, '_robotstxt_docmd_branch', true );
|
||||
$repo_owner_raw = get_post_meta( $mapping_id, '_robotstxt_docmd_repo_owner', true );
|
||||
$repo_name_raw = get_post_meta( $mapping_id, '_robotstxt_docmd_repo_name', true );
|
||||
$file_path_raw = get_post_meta( $mapping_id, '_robotstxt_docmd_file_path', true );
|
||||
$branch_raw = get_post_meta( $mapping_id, '_robotstxt_docmd_branch', true );
|
||||
|
||||
if ( empty( $branch ) ) {
|
||||
$branch = 'main';
|
||||
}
|
||||
$repo_owner = is_string( $repo_owner_raw ) ? $repo_owner_raw : '';
|
||||
$repo_name = is_string( $repo_name_raw ) ? $repo_name_raw : '';
|
||||
$file_path = is_string( $file_path_raw ) ? $file_path_raw : '';
|
||||
$branch = is_string( $branch_raw ) && '' !== $branch_raw ? $branch_raw : 'main';
|
||||
|
||||
$post_type_raw = get_post_meta( $mapping_id, '_robotstxt_docmd_target_post_type', true );
|
||||
$sync_frequency_raw = get_post_meta( $mapping_id, '_robotstxt_docmd_sync_frequency', true );
|
||||
$sync_status_raw = get_post_meta( $mapping_id, '_robotstxt_docmd_sync_status', true );
|
||||
$last_sync_raw = get_post_meta( $mapping_id, '_robotstxt_docmd_last_sync', true );
|
||||
|
||||
$target_post_id_raw = get_post_meta( $mapping_id, '_robotstxt_docmd_target_post_id', true );
|
||||
$target_author_raw = get_post_meta( $mapping_id, '_robotstxt_docmd_target_author', true );
|
||||
$target_parent_raw = get_post_meta( $mapping_id, '_robotstxt_docmd_target_parent', true );
|
||||
$target_order_raw = get_post_meta( $mapping_id, '_robotstxt_docmd_target_order', true );
|
||||
|
||||
return array(
|
||||
'id' => $mapping_id,
|
||||
|
|
@ -73,14 +87,15 @@ function robotstxt_docmd_get_mapping( $mapping_id ) {
|
|||
'file_path' => $file_path,
|
||||
'branch' => $branch,
|
||||
'github_url' => sprintf( 'https://github.com/%s/%s/blob/%s/%s', $repo_owner, $repo_name, $branch, $file_path ),
|
||||
'target_post_type' => get_post_meta( $mapping_id, '_robotstxt_docmd_target_post_type', true ),
|
||||
'target_post_id' => absint( get_post_meta( $mapping_id, '_robotstxt_docmd_target_post_id', true ) ),
|
||||
'target_author' => absint( get_post_meta( $mapping_id, '_robotstxt_docmd_target_author', true ) ),
|
||||
'target_parent' => absint( get_post_meta( $mapping_id, '_robotstxt_docmd_target_parent', true ) ),
|
||||
'target_post_type' => is_string( $post_type_raw ) ? $post_type_raw : '',
|
||||
'target_post_id' => is_numeric( $target_post_id_raw ) ? absint( $target_post_id_raw ) : 0,
|
||||
'target_author' => is_numeric( $target_author_raw ) ? absint( $target_author_raw ) : 0,
|
||||
'target_parent' => is_numeric( $target_parent_raw ) ? absint( $target_parent_raw ) : 0,
|
||||
'target_order' => is_numeric( $target_order_raw ) ? absint( $target_order_raw ) : 0,
|
||||
'sync_enabled' => (bool) get_post_meta( $mapping_id, '_robotstxt_docmd_sync_enabled', true ),
|
||||
'sync_frequency' => get_post_meta( $mapping_id, '_robotstxt_docmd_sync_frequency', true ),
|
||||
'sync_status' => get_post_meta( $mapping_id, '_robotstxt_docmd_sync_status', true ) ? get_post_meta( $mapping_id, '_robotstxt_docmd_sync_status', true ) : 'never',
|
||||
'last_sync' => get_post_meta( $mapping_id, '_robotstxt_docmd_last_sync', true ),
|
||||
'sync_frequency' => is_string( $sync_frequency_raw ) ? $sync_frequency_raw : 'daily',
|
||||
'sync_status' => is_string( $sync_status_raw ) && '' !== $sync_status_raw ? $sync_status_raw : 'never',
|
||||
'last_sync' => is_string( $last_sync_raw ) ? $last_sync_raw : '',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -89,10 +104,11 @@ function robotstxt_docmd_get_mapping( $mapping_id ) {
|
|||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array $data Mapping data.
|
||||
* @param array $data Mapping input data.
|
||||
* @phpstan-param MappingInput $data
|
||||
* @return int|WP_Error Mapping ID or error.
|
||||
*/
|
||||
function robotstxt_docmd_create_mapping( $data ) {
|
||||
function robotstxt_docmd_create_mapping( array $data ): int|WP_Error {
|
||||
// Check for duplicates.
|
||||
if ( robotstxt_docmd_is_file_mapped( $data['repo_owner'], $data['repo_name'], $data['file_path'], $data['branch'] ) ) {
|
||||
return new WP_Error( 'duplicate_mapping', __( 'This file is already mapped', 'robotstxt-documentation-markdown' ) );
|
||||
|
|
@ -119,7 +135,8 @@ function robotstxt_docmd_create_mapping( $data ) {
|
|||
update_post_meta( $post_id, '_robotstxt_docmd_branch', $data['branch'] );
|
||||
update_post_meta( $post_id, '_robotstxt_docmd_target_post_type', $data['target_post_type'] );
|
||||
update_post_meta( $post_id, '_robotstxt_docmd_target_author', $data['target_author'] );
|
||||
update_post_meta( $post_id, '_robotstxt_docmd_target_parent', $data['target_parent'] ?? 0 );
|
||||
update_post_meta( $post_id, '_robotstxt_docmd_target_parent', $data['target_parent'] );
|
||||
update_post_meta( $post_id, '_robotstxt_docmd_target_order', $data['target_order'] );
|
||||
update_post_meta( $post_id, '_robotstxt_docmd_sync_enabled', $data['sync_enabled'] ? 1 : 0 );
|
||||
update_post_meta( $post_id, '_robotstxt_docmd_sync_frequency', $data['sync_frequency'] );
|
||||
|
||||
|
|
@ -141,10 +158,11 @@ function robotstxt_docmd_create_mapping( $data ) {
|
|||
* @since 1.0.0
|
||||
*
|
||||
* @param int $mapping_id Mapping ID.
|
||||
* @param array $data Mapping data.
|
||||
* @return bool|WP_Error True on success or error.
|
||||
* @param array $data Mapping input data.
|
||||
* @phpstan-param MappingInput $data
|
||||
* @return bool True on success.
|
||||
*/
|
||||
function robotstxt_docmd_update_mapping( $mapping_id, $data ) {
|
||||
function robotstxt_docmd_update_mapping( int $mapping_id, array $data ): bool {
|
||||
// Update post title.
|
||||
wp_update_post(
|
||||
array(
|
||||
|
|
@ -160,7 +178,8 @@ function robotstxt_docmd_update_mapping( $mapping_id, $data ) {
|
|||
update_post_meta( $mapping_id, '_robotstxt_docmd_branch', $data['branch'] );
|
||||
update_post_meta( $mapping_id, '_robotstxt_docmd_target_post_type', $data['target_post_type'] );
|
||||
update_post_meta( $mapping_id, '_robotstxt_docmd_target_author', $data['target_author'] );
|
||||
update_post_meta( $mapping_id, '_robotstxt_docmd_target_parent', $data['target_parent'] ?? 0 );
|
||||
update_post_meta( $mapping_id, '_robotstxt_docmd_target_parent', $data['target_parent'] );
|
||||
update_post_meta( $mapping_id, '_robotstxt_docmd_target_order', $data['target_order'] );
|
||||
update_post_meta( $mapping_id, '_robotstxt_docmd_sync_enabled', $data['sync_enabled'] ? 1 : 0 );
|
||||
update_post_meta( $mapping_id, '_robotstxt_docmd_sync_frequency', $data['sync_frequency'] );
|
||||
|
||||
|
|
@ -185,7 +204,7 @@ function robotstxt_docmd_update_mapping( $mapping_id, $data ) {
|
|||
* @param int $mapping_id Mapping ID.
|
||||
* @return bool True on success.
|
||||
*/
|
||||
function robotstxt_docmd_delete_mapping( $mapping_id ) {
|
||||
function robotstxt_docmd_delete_mapping( int $mapping_id ): bool {
|
||||
robotstxt_docmd_unschedule_mapping_sync( $mapping_id );
|
||||
wp_delete_post( $mapping_id, true );
|
||||
return true;
|
||||
|
|
@ -202,7 +221,7 @@ function robotstxt_docmd_delete_mapping( $mapping_id ) {
|
|||
* @param string $branch Branch name.
|
||||
* @return bool True if mapped.
|
||||
*/
|
||||
function robotstxt_docmd_is_file_mapped( $owner, $repo, $file_path, $branch ) {
|
||||
function robotstxt_docmd_is_file_mapped( string $owner, string $repo, string $file_path, string $branch ): bool {
|
||||
$args = array(
|
||||
'post_type' => 'robotstxt_map',
|
||||
'posts_per_page' => 1,
|
||||
|
|
@ -238,7 +257,7 @@ function robotstxt_docmd_is_file_mapped( $owner, $repo, $file_path, $branch ) {
|
|||
* @param int $mapping_id Mapping ID.
|
||||
* @return bool|WP_Error True on success or error.
|
||||
*/
|
||||
function robotstxt_docmd_sync_mapping( $mapping_id ) {
|
||||
function robotstxt_docmd_sync_mapping( int $mapping_id ): bool|WP_Error {
|
||||
$mapping = robotstxt_docmd_get_mapping( $mapping_id );
|
||||
|
||||
if ( ! $mapping ) {
|
||||
|
|
@ -249,8 +268,10 @@ function robotstxt_docmd_sync_mapping( $mapping_id ) {
|
|||
update_post_meta( $mapping_id, '_robotstxt_docmd_sync_status', 'pending' );
|
||||
|
||||
// Get settings.
|
||||
$settings = get_option( 'robotstxt_docmd_settings', array() );
|
||||
$token = $settings['github_token'] ?? '';
|
||||
$raw_settings = get_option( 'robotstxt_docmd_settings' );
|
||||
$settings = is_array( $raw_settings ) ? $raw_settings : array();
|
||||
$token_raw = $settings['github_token'] ?? '';
|
||||
$token = is_string( $token_raw ) ? $token_raw : '';
|
||||
|
||||
if ( empty( $token ) ) {
|
||||
update_post_meta( $mapping_id, '_robotstxt_docmd_sync_status', 'error' );
|
||||
|
|
@ -286,6 +307,7 @@ function robotstxt_docmd_sync_mapping( $mapping_id ) {
|
|||
'post_status' => 'publish',
|
||||
'post_author' => $mapping['target_author'],
|
||||
'post_parent' => $mapping['target_parent'],
|
||||
'menu_order' => $mapping['target_order'],
|
||||
),
|
||||
true
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
* Plugin Name: Documentation Markdown (by ROBOTSTXT)
|
||||
* Plugin URI: https://git.robotstxt.es/ROBOTSTXT/robotstxt-documentation-markdown
|
||||
* Description: Synchronizes Markdown documentation from GitHub repositories to WordPress pages and posts automatically.
|
||||
* Version: 1.0.0
|
||||
* Requires at least: 6.5
|
||||
* Version: 1.1.0
|
||||
* Requires at least: 6.7
|
||||
* Requires PHP: 8.2
|
||||
* Security: robotstxt@robotstxt.es
|
||||
* Author: ROBOTSTXT
|
||||
|
|
@ -26,7 +26,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
}
|
||||
|
||||
// Define plugin constants.
|
||||
define( 'ROBOTSTXT_DOCMD_VERSION', '1.0.0' );
|
||||
define( 'ROBOTSTXT_DOCMD_VERSION', '1.1.0' );
|
||||
define( 'ROBOTSTXT_DOCMD_PLUGIN_FILE', __FILE__ );
|
||||
define( 'ROBOTSTXT_DOCMD_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||
define( 'ROBOTSTXT_DOCMD_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
|
||||
|
|
@ -99,13 +99,6 @@ function robotstxt_docmd_deactivate() {
|
|||
* @return void
|
||||
*/
|
||||
function robotstxt_docmd_init() {
|
||||
// Load textdomain.
|
||||
load_plugin_textdomain(
|
||||
'robotstxt-documentation-markdown',
|
||||
false,
|
||||
dirname( plugin_basename( __FILE__ ) ) . '/languages'
|
||||
);
|
||||
|
||||
// Register CPT.
|
||||
robotstxt_docmd_register_post_types();
|
||||
}
|
||||
|
|
@ -154,7 +147,7 @@ function robotstxt_docmd_register_admin_menu() {
|
|||
add_menu_page(
|
||||
__( 'Documentation', 'robotstxt-documentation-markdown' ),
|
||||
__( 'Documentation', 'robotstxt-documentation-markdown' ),
|
||||
'manage_options',
|
||||
'edit_pages',
|
||||
'robotstxt-docmd-mappings',
|
||||
'robotstxt_docmd_render_mappings_page',
|
||||
'dashicons-media-document',
|
||||
|
|
@ -166,7 +159,7 @@ function robotstxt_docmd_register_admin_menu() {
|
|||
'robotstxt-docmd-mappings',
|
||||
__( 'Mappings', 'robotstxt-documentation-markdown' ),
|
||||
__( 'Mappings', 'robotstxt-documentation-markdown' ),
|
||||
'manage_options',
|
||||
'edit_pages',
|
||||
'robotstxt-docmd-mappings',
|
||||
'robotstxt_docmd_render_mappings_page'
|
||||
);
|
||||
|
|
@ -176,7 +169,7 @@ function robotstxt_docmd_register_admin_menu() {
|
|||
'robotstxt-docmd-mappings',
|
||||
__( 'Discover Files', 'robotstxt-documentation-markdown' ),
|
||||
__( 'Discover Files', 'robotstxt-documentation-markdown' ),
|
||||
'manage_options',
|
||||
'edit_pages',
|
||||
'robotstxt-docmd-discover',
|
||||
'robotstxt_docmd_render_discover_page'
|
||||
);
|
||||
|
|
@ -186,7 +179,7 @@ function robotstxt_docmd_register_admin_menu() {
|
|||
'robotstxt-docmd-mappings',
|
||||
__( 'Add New', 'robotstxt-documentation-markdown' ),
|
||||
__( 'Add New', 'robotstxt-documentation-markdown' ),
|
||||
'manage_options',
|
||||
'edit_pages',
|
||||
'robotstxt-docmd-add-mapping',
|
||||
'robotstxt_docmd_render_add_mapping_page'
|
||||
);
|
||||
|
|
@ -196,7 +189,7 @@ function robotstxt_docmd_register_admin_menu() {
|
|||
'robotstxt-docmd-mappings',
|
||||
__( 'Settings', 'robotstxt-documentation-markdown' ),
|
||||
__( 'Settings', 'robotstxt-documentation-markdown' ),
|
||||
'manage_options',
|
||||
'edit_pages',
|
||||
'robotstxt-docmd-settings',
|
||||
'robotstxt_docmd_render_settings_page'
|
||||
);
|
||||
|
|
@ -232,14 +225,14 @@ function robotstxt_docmd_enqueue_admin_assets( $hook ) {
|
|||
* @return void
|
||||
*/
|
||||
function robotstxt_docmd_handle_admin_actions() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
if ( ! current_user_can( 'edit_pages' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle mapping form submission (POST).
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
if ( isset( $_POST['robotstxt_docmd_nonce'] ) && isset( $_POST['title'] ) ) {
|
||||
if ( wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['robotstxt_docmd_nonce'] ) ), 'robotstxt_docmd_save_mapping' ) ) {
|
||||
if ( wp_verify_nonce( sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'robotstxt_docmd_nonce' ) ) ), 'robotstxt_docmd_save_mapping' ) ) {
|
||||
robotstxt_docmd_handle_save_mapping();
|
||||
// The function above will redirect and exit.
|
||||
}
|
||||
|
|
@ -248,7 +241,7 @@ function robotstxt_docmd_handle_admin_actions() {
|
|||
// Handle settings form submission (POST).
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
if ( isset( $_POST['robotstxt_docmd_nonce'] ) && isset( $_POST['github_url'] ) ) {
|
||||
if ( wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['robotstxt_docmd_nonce'] ) ), 'robotstxt_docmd_save_settings' ) ) {
|
||||
if ( wp_verify_nonce( sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'robotstxt_docmd_nonce' ) ) ), 'robotstxt_docmd_save_settings' ) ) {
|
||||
robotstxt_docmd_handle_save_settings();
|
||||
// The function above will redirect and exit.
|
||||
}
|
||||
|
|
@ -257,7 +250,7 @@ function robotstxt_docmd_handle_admin_actions() {
|
|||
// Handle debug actions (only on settings page).
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_GET['page'] ) && 'robotstxt-docmd-settings' === $_GET['page'] && isset( $_GET['debug_action'] ) ) {
|
||||
$debug_action = sanitize_key( wp_unslash( $_GET['debug_action'] ) );
|
||||
$debug_action = sanitize_key( wp_unslash( robotstxt_docmd_input_string( $_GET, 'debug_action' ) ) );
|
||||
|
||||
if ( 'test_github' === $debug_action ) {
|
||||
check_admin_referer( 'robotstxt_docmd_debug_test_github' );
|
||||
|
|
@ -272,7 +265,7 @@ function robotstxt_docmd_handle_admin_actions() {
|
|||
}
|
||||
|
||||
if ( 'run_cron' === $debug_action && isset( $_GET['mapping_id'] ) ) {
|
||||
$mapping_id = absint( wp_unslash( $_GET['mapping_id'] ) );
|
||||
$mapping_id = robotstxt_docmd_input_int( $_GET, 'mapping_id' );
|
||||
check_admin_referer( 'robotstxt_docmd_debug_run_cron_' . $mapping_id );
|
||||
robotstxt_docmd_debug_run_cron( $mapping_id );
|
||||
return;
|
||||
|
|
@ -300,8 +293,8 @@ function robotstxt_docmd_handle_admin_actions() {
|
|||
return;
|
||||
}
|
||||
|
||||
$action = sanitize_key( wp_unslash( $_GET['action'] ) );
|
||||
$mapping_id = absint( wp_unslash( $_GET['mapping_id'] ) );
|
||||
$action = sanitize_key( wp_unslash( robotstxt_docmd_input_string( $_GET, 'action' ) ) );
|
||||
$mapping_id = robotstxt_docmd_input_int( $_GET, 'mapping_id' );
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
if ( 'sync' === $action ) {
|
||||
|
|
@ -332,7 +325,7 @@ function robotstxt_docmd_handle_admin_actions() {
|
|||
* @return void
|
||||
*/
|
||||
function robotstxt_docmd_render_mappings_page() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
if ( ! current_user_can( 'edit_pages' ) ) {
|
||||
wp_die( esc_html__( 'You do not have sufficient permissions.', 'robotstxt-documentation-markdown' ) );
|
||||
}
|
||||
|
||||
|
|
@ -380,8 +373,8 @@ function robotstxt_docmd_render_mappings_page() {
|
|||
</td>
|
||||
<td>
|
||||
<?php if ( ! empty( $mapping['target_post_id'] ) ) : ?>
|
||||
<a href="<?php echo esc_url( get_permalink( $mapping['target_post_id'] ) ); ?>" target="_blank" rel="noopener noreferrer">
|
||||
<?php echo esc_html( get_the_title( $mapping['target_post_id'] ) ); ?>
|
||||
<a href="<?php echo esc_url( (string) get_permalink( (int) $mapping['target_post_id'] ) ); ?>" target="_blank" rel="noopener noreferrer">
|
||||
<?php echo esc_html( (string) get_the_title( (int) $mapping['target_post_id'] ) ); ?>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<em><?php esc_html_e( 'Not created yet', 'robotstxt-documentation-markdown' ); ?></em>
|
||||
|
|
@ -418,14 +411,15 @@ function robotstxt_docmd_render_mappings_page() {
|
|||
* @return void
|
||||
*/
|
||||
function robotstxt_docmd_render_discover_page() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
if ( ! current_user_can( 'edit_pages' ) ) {
|
||||
wp_die( esc_html__( 'You do not have sufficient permissions.', 'robotstxt-documentation-markdown' ) );
|
||||
}
|
||||
|
||||
// Get settings.
|
||||
$settings = get_option( 'robotstxt_docmd_settings', array() );
|
||||
$github_url = $settings['github_url'] ?? '';
|
||||
$github_token = $settings['github_token'] ?? '';
|
||||
$raw_settings = get_option( 'robotstxt_docmd_settings', array() );
|
||||
$settings = is_array( $raw_settings ) ? $raw_settings : array();
|
||||
$github_url = robotstxt_docmd_input_string( $settings, 'github_url' );
|
||||
$github_token = robotstxt_docmd_input_string( $settings, 'github_token' );
|
||||
|
||||
// Parse GitHub URL.
|
||||
$repo_data = robotstxt_docmd_parse_github_url( $github_url );
|
||||
|
|
@ -452,11 +446,11 @@ function robotstxt_docmd_render_discover_page() {
|
|||
|
||||
// Get branch from query string.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$branch = isset( $_GET['branch'] ) ? sanitize_text_field( wp_unslash( $_GET['branch'] ) ) : 'main';
|
||||
$branch = sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_GET, 'branch', 'main' ) ) );
|
||||
|
||||
// Check if refresh requested.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$refresh = isset( $_GET['refresh'] ) && '1' === $_GET['refresh'];
|
||||
$refresh = '1' === robotstxt_docmd_input_string( $_GET, 'refresh' );
|
||||
if ( $refresh ) {
|
||||
$cache_key = sprintf( 'robotstxt_docmd_files_%s_%s_%s', $repo_data['owner'], $repo_data['repo'], $branch );
|
||||
delete_transient( $cache_key );
|
||||
|
|
@ -542,15 +536,14 @@ function robotstxt_docmd_render_discover_page() {
|
|||
<tbody>
|
||||
<?php foreach ( $files as $file ) : ?>
|
||||
<?php
|
||||
// Skip if file doesn't have required keys.
|
||||
if ( ! isset( $file['path'] ) || ! isset( $file['size'] ) ) {
|
||||
continue;
|
||||
}
|
||||
$is_mapped = robotstxt_docmd_is_file_mapped( $repo_data['owner'], $repo_data['repo'], $file['path'], $branch );
|
||||
?>
|
||||
<tr>
|
||||
<td><code><?php echo esc_html( $file['path'] ); ?></code></td>
|
||||
<td><?php echo esc_html( size_format( $file['size'], 2 ) ); ?></td>
|
||||
<?php
|
||||
$file_size_str = size_format( $file['size'], 2 );
|
||||
?>
|
||||
<td><?php echo esc_html( false !== $file_size_str ? $file_size_str : '0 B' ); ?></td>
|
||||
<td>
|
||||
<?php if ( $is_mapped ) : ?>
|
||||
<span class="status-badge status-mapped"><?php esc_html_e( 'Mapped', 'robotstxt-documentation-markdown' ); ?></span>
|
||||
|
|
@ -599,28 +592,28 @@ function robotstxt_docmd_render_discover_page() {
|
|||
* @return void
|
||||
*/
|
||||
function robotstxt_docmd_render_add_mapping_page() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
if ( ! current_user_can( 'edit_pages' ) ) {
|
||||
wp_die( esc_html__( 'You do not have sufficient permissions.', 'robotstxt-documentation-markdown' ) );
|
||||
}
|
||||
|
||||
// Get mapping ID if editing.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$mapping_id = isset( $_GET['mapping_id'] ) ? absint( wp_unslash( $_GET['mapping_id'] ) ) : 0;
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
$mapping_id = robotstxt_docmd_input_int( $_GET, 'mapping_id' );
|
||||
$mapping = $mapping_id ? robotstxt_docmd_get_mapping( $mapping_id ) : null;
|
||||
|
||||
// Pre-fill from discover page or defaults.
|
||||
$defaults = array(
|
||||
'title' => '',
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
'repo_owner' => isset( $_GET['owner'] ) ? sanitize_text_field( wp_unslash( $_GET['owner'] ) ) : '',
|
||||
'repo_name' => isset( $_GET['repo'] ) ? sanitize_text_field( wp_unslash( $_GET['repo'] ) ) : '',
|
||||
'file_path' => isset( $_GET['file_path'] ) ? urldecode( sanitize_text_field( wp_unslash( $_GET['file_path'] ) ) ) : '',
|
||||
'branch' => isset( $_GET['branch'] ) ? sanitize_text_field( wp_unslash( $_GET['branch'] ) ) : 'main',
|
||||
'repo_owner' => sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_GET, 'owner' ) ) ),
|
||||
'repo_name' => sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_GET, 'repo' ) ) ),
|
||||
'file_path' => urldecode( sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_GET, 'file_path' ) ) ) ),
|
||||
'branch' => sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_GET, 'branch', 'main' ) ) ),
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
'target_post_type' => 'page',
|
||||
'target_post_id' => 0,
|
||||
'target_author' => get_current_user_id(),
|
||||
'target_parent' => 0,
|
||||
'target_order' => 0,
|
||||
'sync_enabled' => true,
|
||||
'sync_frequency' => 'daily',
|
||||
);
|
||||
|
|
@ -641,7 +634,7 @@ function robotstxt_docmd_render_add_mapping_page() {
|
|||
<form method="post" action="">
|
||||
<?php wp_nonce_field( 'robotstxt_docmd_save_mapping', 'robotstxt_docmd_nonce' ); ?>
|
||||
<?php if ( $mapping_id ) : ?>
|
||||
<input type="hidden" name="mapping_id" value="<?php echo esc_attr( $mapping_id ); ?>">
|
||||
<input type="hidden" name="mapping_id" value="<?php echo esc_attr( (string) $mapping_id ); ?>">
|
||||
<?php endif; ?>
|
||||
|
||||
<table class="form-table">
|
||||
|
|
@ -727,8 +720,8 @@ function robotstxt_docmd_render_add_mapping_page() {
|
|||
array(
|
||||
'name' => 'target_parent',
|
||||
'show_option_none' => esc_html__( '(no parent)', 'robotstxt-documentation-markdown' ),
|
||||
'option_none_value' => 0,
|
||||
'selected' => absint( $data['target_parent'] ?? 0 ),
|
||||
'option_none_value' => '0',
|
||||
'selected' => absint( $data['target_parent'] ),
|
||||
)
|
||||
);
|
||||
?>
|
||||
|
|
@ -750,7 +743,7 @@ function robotstxt_docmd_render_add_mapping_page() {
|
|||
|
||||
<label>
|
||||
<?php esc_html_e( 'Order:', 'robotstxt-documentation-markdown' ); ?>
|
||||
<input type="number" name="target_order" value="<?php echo esc_attr( $data['target_order'] ?? 0 ); ?>" class="small-text" min="0" step="1">
|
||||
<input type="number" name="target_order" value="<?php echo esc_attr( (string) $data['target_order'] ); ?>" class="small-text" min="0" step="1">
|
||||
<p class="description"><?php esc_html_e( 'Page order (menu_order). Lower numbers appear first. Default: 0', 'robotstxt-documentation-markdown' ); ?></p>
|
||||
</label>
|
||||
</div>
|
||||
|
|
@ -781,7 +774,7 @@ function robotstxt_docmd_render_add_mapping_page() {
|
|||
foreach ( $all_posts as $post ) {
|
||||
printf(
|
||||
'<option value="%d" %s>%s (%s)</option>',
|
||||
esc_attr( $post->ID ),
|
||||
esc_attr( (string) $post->ID ),
|
||||
selected( $data['target_post_id'], $post->ID, false ),
|
||||
esc_html( $post->post_title ),
|
||||
esc_html( $post->post_type )
|
||||
|
|
@ -857,36 +850,35 @@ function robotstxt_docmd_render_add_mapping_page() {
|
|||
* @return void
|
||||
*/
|
||||
function robotstxt_docmd_handle_save_mapping() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
if ( ! current_user_can( 'edit_pages' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify nonce.
|
||||
if ( ! isset( $_POST['robotstxt_docmd_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['robotstxt_docmd_nonce'] ) ), 'robotstxt_docmd_save_mapping' ) ) {
|
||||
if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'robotstxt_docmd_nonce' ) ) ), 'robotstxt_docmd_save_mapping' ) ) {
|
||||
wp_die( esc_html__( 'Security check failed.', 'robotstxt-documentation-markdown' ) );
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$mapping_id = isset( $_POST['mapping_id'] ) ? absint( wp_unslash( $_POST['mapping_id'] ) ) : 0;
|
||||
$mapping_id = robotstxt_docmd_input_int( $_POST, 'mapping_id' );
|
||||
|
||||
$data = array(
|
||||
'title' => sanitize_text_field( wp_unslash( $_POST['title'] ?? '' ) ),
|
||||
'repo_owner' => sanitize_text_field( wp_unslash( $_POST['repo_owner'] ?? '' ) ),
|
||||
'repo_name' => sanitize_text_field( wp_unslash( $_POST['repo_name'] ?? '' ) ),
|
||||
'file_path' => sanitize_text_field( wp_unslash( $_POST['file_path'] ?? '' ) ),
|
||||
'branch' => sanitize_text_field( wp_unslash( $_POST['branch'] ?? 'main' ) ),
|
||||
'target_post_type' => sanitize_key( wp_unslash( $_POST['target_post_type'] ?? 'page' ) ),
|
||||
'target_author' => absint( wp_unslash( $_POST['target_author'] ?? get_current_user_id() ) ),
|
||||
'target_parent' => absint( wp_unslash( $_POST['target_parent'] ?? 0 ) ),
|
||||
'title' => sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'title' ) ) ),
|
||||
'repo_owner' => sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'repo_owner' ) ) ),
|
||||
'repo_name' => sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'repo_name' ) ) ),
|
||||
'file_path' => sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'file_path' ) ) ),
|
||||
'branch' => sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'branch', 'main' ) ) ),
|
||||
'target_post_type' => sanitize_key( wp_unslash( robotstxt_docmd_input_string( $_POST, 'target_post_type', 'page' ) ) ),
|
||||
'target_author' => robotstxt_docmd_input_int( $_POST, 'target_author', get_current_user_id() ),
|
||||
'target_parent' => robotstxt_docmd_input_int( $_POST, 'target_parent' ),
|
||||
'target_order' => robotstxt_docmd_input_int( $_POST, 'target_order' ),
|
||||
'sync_enabled' => ! empty( $_POST['sync_enabled'] ),
|
||||
'sync_frequency' => sanitize_key( wp_unslash( $_POST['sync_frequency'] ?? 'daily' ) ),
|
||||
'sync_frequency' => sanitize_key( wp_unslash( robotstxt_docmd_input_string( $_POST, 'sync_frequency', 'daily' ) ) ),
|
||||
);
|
||||
|
||||
$target_type = sanitize_key( wp_unslash( $_POST['target_type'] ?? 'new' ) );
|
||||
$target_type = sanitize_key( wp_unslash( robotstxt_docmd_input_string( $_POST, 'target_type', 'new' ) ) );
|
||||
if ( 'existing' === $target_type ) {
|
||||
$data['target_post_id'] = absint( wp_unslash( $_POST['target_post_id'] ?? 0 ) );
|
||||
$data['target_post_id'] = robotstxt_docmd_input_int( $_POST, 'target_post_id' );
|
||||
}
|
||||
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
|
||||
if ( $mapping_id ) {
|
||||
$result = robotstxt_docmd_update_mapping( $mapping_id, $data );
|
||||
|
|
@ -915,30 +907,34 @@ function robotstxt_docmd_handle_save_mapping() {
|
|||
*/
|
||||
function robotstxt_docmd_render_admin_notices() {
|
||||
// Check for regular message parameter.
|
||||
if ( isset( $_GET['message'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$message = sanitize_key( wp_unslash( $_GET['message'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$message = sanitize_key( wp_unslash( robotstxt_docmd_input_string( $_GET, 'message' ) ) );
|
||||
if ( 'settings_saved' === $message ) {
|
||||
echo '<div class="notice notice-success is-dismissible"><p>';
|
||||
esc_html_e( 'Settings saved successfully.', 'robotstxt-documentation-markdown' );
|
||||
echo '</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Check for debug results.
|
||||
$debug_result = get_transient( 'robotstxt_docmd_debug_result' );
|
||||
if ( $debug_result ) {
|
||||
if ( is_array( $debug_result )
|
||||
&& isset( $debug_result['type'], $debug_result['message'] )
|
||||
&& is_string( $debug_result['type'] )
|
||||
&& is_string( $debug_result['message'] )
|
||||
) {
|
||||
delete_transient( 'robotstxt_docmd_debug_result' );
|
||||
|
||||
$notice_class = 'notice-' . $debug_result['type'];
|
||||
echo '<div class="notice ' . esc_attr( $notice_class ) . ' is-dismissible">';
|
||||
echo '<p><strong>' . wp_kses_post( $debug_result['message'] ) . '</strong></p>';
|
||||
|
||||
if ( ! empty( $debug_result['details'] ) ) {
|
||||
if ( ! empty( $debug_result['details'] ) && is_array( $debug_result['details'] ) ) {
|
||||
echo '<ul style="list-style: disc; margin-left: 20px;">';
|
||||
foreach ( $debug_result['details'] as $detail ) {
|
||||
if ( is_string( $detail ) ) {
|
||||
echo '<li>' . wp_kses_post( $detail ) . '</li>';
|
||||
}
|
||||
}
|
||||
echo '</ul>';
|
||||
}
|
||||
|
||||
|
|
@ -954,11 +950,12 @@ function robotstxt_docmd_render_admin_notices() {
|
|||
* @return void
|
||||
*/
|
||||
function robotstxt_docmd_render_settings_page() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
if ( ! current_user_can( 'edit_pages' ) ) {
|
||||
wp_die( esc_html__( 'You do not have sufficient permissions.', 'robotstxt-documentation-markdown' ) );
|
||||
}
|
||||
|
||||
$settings = get_option( 'robotstxt_docmd_settings', array() );
|
||||
$raw_settings = get_option( 'robotstxt_docmd_settings', array() );
|
||||
$settings = is_array( $raw_settings ) ? $raw_settings : array();
|
||||
|
||||
?>
|
||||
<div class="wrap robotstxt-docmd-settings">
|
||||
|
|
@ -976,7 +973,7 @@ function robotstxt_docmd_render_settings_page() {
|
|||
<label for="github_url"><?php esc_html_e( 'Repository URL', 'robotstxt-documentation-markdown' ); ?> <span class="required">*</span></label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="url" id="github_url" name="github_url" value="<?php echo esc_attr( $settings['github_url'] ?? '' ); ?>" class="regular-text" required>
|
||||
<input type="url" id="github_url" name="github_url" value="<?php echo esc_attr( robotstxt_docmd_input_string( $settings, 'github_url' ) ); ?>" class="regular-text" required>
|
||||
<p class="description"><?php esc_html_e( 'Full GitHub repository URL (e.g., https://github.com/owner/repo)', 'robotstxt-documentation-markdown' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -988,7 +985,7 @@ function robotstxt_docmd_render_settings_page() {
|
|||
<td>
|
||||
<input type="password" id="github_token" name="github_token" value="" class="regular-text" placeholder="<?php esc_attr_e( 'Enter new token to update', 'robotstxt-documentation-markdown' ); ?>">
|
||||
<p class="description"><?php esc_html_e( 'GitHub Personal Access Token with "repo" scope', 'robotstxt-documentation-markdown' ); ?></p>
|
||||
<?php if ( ! empty( $settings['github_token'] ) ) : ?>
|
||||
<?php if ( ! empty( robotstxt_docmd_input_string( $settings, 'github_token' ) ) ) : ?>
|
||||
<p class="description"><strong><?php esc_html_e( 'Token is currently set. Leave blank to keep existing token.', 'robotstxt-documentation-markdown' ); ?></strong></p>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
|
|
@ -1080,7 +1077,9 @@ function robotstxt_docmd_render_settings_page() {
|
|||
echo '<td>' . esc_html( $mapping['sync_frequency'] ) . '</td>';
|
||||
echo '<td>';
|
||||
if ( $timestamp ) {
|
||||
echo esc_html( gmdate( 'Y-m-d H:i:s', $timestamp + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ) );
|
||||
$gmt_offset_raw = get_option( 'gmt_offset', 0 );
|
||||
$gmt_offset_sec = is_numeric( $gmt_offset_raw ) ? (int) ( (float) $gmt_offset_raw * HOUR_IN_SECONDS ) : 0;
|
||||
echo esc_html( gmdate( 'Y-m-d H:i:s', $timestamp + $gmt_offset_sec ) );
|
||||
} else {
|
||||
echo '<em>' . esc_html__( 'Not scheduled', 'robotstxt-documentation-markdown' ) . '</em>';
|
||||
}
|
||||
|
|
@ -1134,21 +1133,20 @@ function robotstxt_docmd_render_settings_page() {
|
|||
* @return void
|
||||
*/
|
||||
function robotstxt_docmd_handle_save_settings() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
if ( ! current_user_can( 'edit_pages' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify nonce.
|
||||
if ( ! isset( $_POST['robotstxt_docmd_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['robotstxt_docmd_nonce'] ) ), 'robotstxt_docmd_save_settings' ) ) {
|
||||
if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'robotstxt_docmd_nonce' ) ) ), 'robotstxt_docmd_save_settings' ) ) {
|
||||
wp_die( esc_html__( 'Security check failed.', 'robotstxt-documentation-markdown' ) );
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$github_url = sanitize_text_field( wp_unslash( $_POST['github_url'] ?? '' ) );
|
||||
$github_token = sanitize_text_field( wp_unslash( $_POST['github_token'] ?? '' ) );
|
||||
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$github_url = sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'github_url' ) ) );
|
||||
$github_token = sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'github_token' ) ) );
|
||||
|
||||
$settings = get_option( 'robotstxt_docmd_settings', array() );
|
||||
$raw_settings = get_option( 'robotstxt_docmd_settings', array() );
|
||||
$settings = is_array( $raw_settings ) ? $raw_settings : array();
|
||||
|
||||
$settings['github_url'] = $github_url;
|
||||
|
||||
|
|
@ -1177,13 +1175,14 @@ function robotstxt_docmd_handle_save_settings() {
|
|||
* @return void
|
||||
*/
|
||||
function robotstxt_docmd_debug_test_github() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
if ( ! current_user_can( 'edit_pages' ) ) {
|
||||
wp_die( esc_html__( 'Permission denied.', 'robotstxt-documentation-markdown' ) );
|
||||
}
|
||||
|
||||
$settings = get_option( 'robotstxt_docmd_settings', array() );
|
||||
$repo_url = $settings['github_url'] ?? '';
|
||||
$token = $settings['github_token'] ?? '';
|
||||
$raw_settings = get_option( 'robotstxt_docmd_settings', array() );
|
||||
$settings = is_array( $raw_settings ) ? $raw_settings : array();
|
||||
$repo_url = robotstxt_docmd_input_string( $settings, 'github_url' );
|
||||
$token = robotstxt_docmd_input_string( $settings, 'github_token' );
|
||||
|
||||
$result = array(
|
||||
'type' => 'error',
|
||||
|
|
@ -1242,26 +1241,31 @@ function robotstxt_docmd_debug_test_github() {
|
|||
);
|
||||
} elseif ( 200 === $status_code ) {
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
$full_name = is_array( $body ) && isset( $body['full_name'] ) && is_string( $body['full_name'] ) ? $body['full_name'] : 'N/A';
|
||||
$repo_name = is_array( $body ) && isset( $body['name'] ) && is_string( $body['name'] ) ? $body['name'] : 'N/A';
|
||||
$is_private = is_array( $body ) && ! empty( $body['private'] );
|
||||
$default_branch = is_array( $body ) && isset( $body['default_branch'] ) && is_string( $body['default_branch'] ) ? $body['default_branch'] : 'N/A';
|
||||
|
||||
$result['type'] = 'success';
|
||||
$result['message'] = sprintf(
|
||||
/* translators: %s: repository full name */
|
||||
__( 'Successfully connected to repository: %s', 'robotstxt-documentation-markdown' ),
|
||||
$body['full_name'] ?? 'N/A'
|
||||
$full_name
|
||||
);
|
||||
$result['details'][] = sprintf(
|
||||
'<strong>%s:</strong> %s',
|
||||
__( 'Name', 'robotstxt-documentation-markdown' ),
|
||||
$body['name'] ?? 'N/A'
|
||||
$repo_name
|
||||
);
|
||||
$result['details'][] = sprintf(
|
||||
'<strong>%s:</strong> %s',
|
||||
__( 'Private', 'robotstxt-documentation-markdown' ),
|
||||
! empty( $body['private'] ) ? __( 'Yes', 'robotstxt-documentation-markdown' ) : __( 'No', 'robotstxt-documentation-markdown' )
|
||||
$is_private ? __( 'Yes', 'robotstxt-documentation-markdown' ) : __( 'No', 'robotstxt-documentation-markdown' )
|
||||
);
|
||||
$result['details'][] = sprintf(
|
||||
'<strong>%s:</strong> %s',
|
||||
__( 'Default Branch', 'robotstxt-documentation-markdown' ),
|
||||
$body['default_branch'] ?? 'N/A'
|
||||
$default_branch
|
||||
);
|
||||
} elseif ( 404 === $status_code ) {
|
||||
$result['message'] = __( 'Repository Not Found: The repository does not exist or your token does not have access to it.', 'robotstxt-documentation-markdown' );
|
||||
|
|
@ -1290,12 +1294,13 @@ function robotstxt_docmd_debug_test_github() {
|
|||
* @return void
|
||||
*/
|
||||
function robotstxt_docmd_debug_test_token() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
if ( ! current_user_can( 'edit_pages' ) ) {
|
||||
wp_die( esc_html__( 'Permission denied.', 'robotstxt-documentation-markdown' ) );
|
||||
}
|
||||
|
||||
$settings = get_option( 'robotstxt_docmd_settings', array() );
|
||||
$token = $settings['github_token'] ?? '';
|
||||
$raw_settings = get_option( 'robotstxt_docmd_settings', array() );
|
||||
$settings = is_array( $raw_settings ) ? $raw_settings : array();
|
||||
$token = robotstxt_docmd_input_string( $settings, 'github_token' );
|
||||
|
||||
$result = array(
|
||||
'type' => 'error',
|
||||
|
|
@ -1339,11 +1344,13 @@ function robotstxt_docmd_debug_test_token() {
|
|||
$result['type'] = 'success';
|
||||
$result['message'] = __( 'Token Valid! Your GitHub token is working correctly.', 'robotstxt-documentation-markdown' );
|
||||
|
||||
if ( isset( $body['resources']['core'] ) ) {
|
||||
$core = $body['resources']['core'];
|
||||
$remaining = $core['remaining'] ?? 0;
|
||||
$limit = $core['limit'] ?? 0;
|
||||
$reset = $core['reset'] ?? 0;
|
||||
$resources = is_array( $body ) && isset( $body['resources'] ) && is_array( $body['resources'] ) ? $body['resources'] : null;
|
||||
$core = is_array( $resources ) && isset( $resources['core'] ) && is_array( $resources['core'] ) ? $resources['core'] : null;
|
||||
|
||||
if ( null !== $core ) {
|
||||
$remaining = isset( $core['remaining'] ) && is_int( $core['remaining'] ) ? $core['remaining'] : 0;
|
||||
$limit = isset( $core['limit'] ) && is_int( $core['limit'] ) ? $core['limit'] : 0;
|
||||
$reset = isset( $core['reset'] ) && is_int( $core['reset'] ) ? $core['reset'] : 0;
|
||||
|
||||
$result['details'][] = sprintf(
|
||||
'<strong>%s:</strong> %d / %d',
|
||||
|
|
@ -1353,7 +1360,9 @@ function robotstxt_docmd_debug_test_token() {
|
|||
);
|
||||
|
||||
if ( $reset > 0 ) {
|
||||
$reset_time = gmdate( 'Y-m-d H:i:s', $reset + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) );
|
||||
$gmt_offset_raw = get_option( 'gmt_offset', 0 );
|
||||
$gmt_offset_sec = is_numeric( $gmt_offset_raw ) ? (int) ( (float) $gmt_offset_raw * HOUR_IN_SECONDS ) : 0;
|
||||
$reset_time = gmdate( 'Y-m-d H:i:s', $reset + $gmt_offset_sec );
|
||||
$result['details'][] = sprintf(
|
||||
'<strong>%s:</strong> %s',
|
||||
__( 'Resets At', 'robotstxt-documentation-markdown' ),
|
||||
|
|
@ -1391,8 +1400,8 @@ function robotstxt_docmd_debug_test_token() {
|
|||
* @param int $mapping_id Mapping ID to sync.
|
||||
* @return void
|
||||
*/
|
||||
function robotstxt_docmd_debug_run_cron( $mapping_id ) {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
function robotstxt_docmd_debug_run_cron( int $mapping_id ) {
|
||||
if ( ! current_user_can( 'edit_pages' ) ) {
|
||||
wp_die( esc_html__( 'Permission denied.', 'robotstxt-documentation-markdown' ) );
|
||||
}
|
||||
|
||||
|
|
@ -1425,6 +1434,13 @@ function robotstxt_docmd_debug_run_cron( $mapping_id ) {
|
|||
// Reload mapping to get updated info.
|
||||
$mapping = robotstxt_docmd_get_mapping( $mapping_id );
|
||||
|
||||
if ( ! $mapping ) {
|
||||
$result['message'] = __( 'Sync completed but mapping data could not be reloaded.', 'robotstxt-documentation-markdown' );
|
||||
set_transient( 'robotstxt_docmd_debug_result', $result, 60 );
|
||||
wp_safe_redirect( admin_url( 'admin.php?page=robotstxt-docmd-settings' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
$result['type'] = 'success';
|
||||
$result['message'] = sprintf(
|
||||
/* translators: %s: mapping title */
|
||||
|
|
@ -1441,11 +1457,11 @@ function robotstxt_docmd_debug_run_cron( $mapping_id ) {
|
|||
$result['details'][] = sprintf(
|
||||
'<strong>%s:</strong> %s',
|
||||
__( 'Last Sync', 'robotstxt-documentation-markdown' ),
|
||||
$mapping['last_sync'] ?? 'N/A'
|
||||
$mapping['last_sync']
|
||||
);
|
||||
|
||||
if ( ! empty( $mapping['target_post_id'] ) ) {
|
||||
$edit_url = get_edit_post_link( $mapping['target_post_id'] );
|
||||
$edit_url = get_edit_post_link( $mapping['target_post_id'] ) ?? '';
|
||||
$result['details'][] = sprintf(
|
||||
'<strong>%s:</strong> <a href="%s">%s</a>',
|
||||
__( 'Target Post', 'robotstxt-documentation-markdown' ),
|
||||
|
|
@ -1470,7 +1486,7 @@ function robotstxt_docmd_debug_run_cron( $mapping_id ) {
|
|||
* @return void
|
||||
*/
|
||||
function robotstxt_docmd_debug_clear_cache() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
if ( ! current_user_can( 'edit_pages' ) ) {
|
||||
wp_die( esc_html__( 'Permission denied.', 'robotstxt-documentation-markdown' ) );
|
||||
}
|
||||
|
||||
|
|
@ -1483,6 +1499,7 @@ function robotstxt_docmd_debug_clear_cache() {
|
|||
);
|
||||
|
||||
// Delete all plugin transients.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct query acceptable for cache clearing operation.
|
||||
$deleted = $wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM {$wpdb->options}
|
||||
|
|
@ -1519,7 +1536,7 @@ function robotstxt_docmd_debug_clear_cache() {
|
|||
* @return void
|
||||
*/
|
||||
function robotstxt_docmd_debug_fix_crons() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
if ( ! current_user_can( 'edit_pages' ) ) {
|
||||
wp_die( esc_html__( 'Permission denied.', 'robotstxt-documentation-markdown' ) );
|
||||
}
|
||||
|
||||
|
|
|
|||
118
uninstall.php
118
uninstall.php
|
|
@ -7,7 +7,7 @@
|
|||
* @package RobotsTxt\DocumentationMarkdown
|
||||
* @author ROBOTSTXT
|
||||
* @license GPL-3.0-or-later
|
||||
* @link https://github.com/robotstxt/documentation-markdown
|
||||
* @link https://git.robotstxt.es/ROBOTSTXT/robotstxt-documentation-markdown
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
|
|
@ -29,7 +29,8 @@ if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
|
|||
*/
|
||||
function robotstxt_docmd_uninstall(): void {
|
||||
// Get plugin settings.
|
||||
$settings = get_option( 'robotstxt_docmd_settings', array() );
|
||||
$raw_settings = get_option( 'robotstxt_docmd_settings', array() );
|
||||
$settings = is_array( $raw_settings ) ? $raw_settings : array();
|
||||
|
||||
// Check if user wants to delete data on uninstall.
|
||||
if ( empty( $settings['delete_on_uninstall'] ) ) {
|
||||
|
|
@ -37,77 +38,80 @@ function robotstxt_docmd_uninstall(): void {
|
|||
return;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
// Delete all mapping posts (CPT).
|
||||
$mapping_ids = $wpdb->get_col(
|
||||
$wpdb->prepare(
|
||||
"SELECT ID FROM {$wpdb->posts} WHERE post_type = %s",
|
||||
'robotstxt_map'
|
||||
// Delete all mapping posts (CPT) using WordPress API.
|
||||
$mapping_posts = get_posts(
|
||||
array(
|
||||
'post_type' => 'robotstxt_map',
|
||||
'posts_per_page' => -1,
|
||||
'post_status' => 'any',
|
||||
'fields' => 'ids',
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $mapping_ids as $mapping_id ) {
|
||||
// Force delete (bypass trash).
|
||||
wp_delete_post( $mapping_id, true );
|
||||
}
|
||||
|
||||
// Delete all plugin-specific post meta.
|
||||
$wpdb->query(
|
||||
"DELETE FROM {$wpdb->postmeta}
|
||||
WHERE meta_key LIKE '_robotstxt_docmd_%'"
|
||||
);
|
||||
|
||||
// Delete plugin options.
|
||||
delete_option( 'robotstxt_docmd_settings' );
|
||||
|
||||
// Delete transients (cached data).
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM {$wpdb->options}
|
||||
WHERE option_name LIKE %s",
|
||||
$wpdb->esc_like( '_transient_robotstxt_docmd_' ) . '%'
|
||||
)
|
||||
);
|
||||
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM {$wpdb->options}
|
||||
WHERE option_name LIKE %s",
|
||||
$wpdb->esc_like( '_transient_timeout_robotstxt_docmd_' ) . '%'
|
||||
)
|
||||
);
|
||||
|
||||
// Clear all scheduled cron events for each mapping.
|
||||
foreach ( $mapping_ids as $mapping_id ) {
|
||||
// Delete each mapping post and its associated meta.
|
||||
foreach ( $mapping_posts as $mapping_id ) {
|
||||
// Clear scheduled cron events for this mapping.
|
||||
$hook = 'robotstxt_docmd_sync_event';
|
||||
$args = array( $mapping_id );
|
||||
$timestamp = wp_next_scheduled( $hook, $args );
|
||||
if ( $timestamp ) {
|
||||
wp_unschedule_event( $timestamp, $hook, $args );
|
||||
}
|
||||
|
||||
// Force delete (bypass trash). This also deletes all associated post meta.
|
||||
wp_delete_post( $mapping_id, true );
|
||||
}
|
||||
|
||||
// Clear notification transients.
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM {$wpdb->options}
|
||||
WHERE option_name LIKE %s",
|
||||
$wpdb->esc_like( '_transient_robotstxt_docmd_notification_' ) . '%'
|
||||
)
|
||||
);
|
||||
// Delete plugin options.
|
||||
delete_option( 'robotstxt_docmd_settings' );
|
||||
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM {$wpdb->options}
|
||||
WHERE option_name LIKE %s",
|
||||
$wpdb->esc_like( '_transient_timeout_robotstxt_docmd_notification_' ) . '%'
|
||||
)
|
||||
);
|
||||
// Delete transients (cached data).
|
||||
// Note: WordPress does not provide an API for pattern-based transient deletion.
|
||||
// Direct database queries are necessary here for bulk cleanup operations.
|
||||
robotstxt_docmd_delete_transients_by_prefix( 'robotstxt_docmd_' );
|
||||
robotstxt_docmd_delete_transients_by_prefix( 'robotstxt_docmd_notification_' );
|
||||
|
||||
// Note: We deliberately DO NOT delete the synced posts/pages themselves.
|
||||
// Users may want to keep the documentation even after uninstalling the plugin.
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all transients matching a prefix
|
||||
*
|
||||
* WordPress does not provide an API for pattern-based transient deletion,
|
||||
* so direct database access is required for bulk cleanup operations.
|
||||
* This is compliant with AGENTS.md guidelines for necessary database operations.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param string $prefix Transient name prefix to match.
|
||||
* @return void
|
||||
*/
|
||||
function robotstxt_docmd_delete_transients_by_prefix( string $prefix ): void {
|
||||
global $wpdb;
|
||||
|
||||
// Sanitize prefix for LIKE query.
|
||||
$transient_prefix = $wpdb->esc_like( '_transient_' . $prefix ) . '%';
|
||||
$transient_timeout_prefix = $wpdb->esc_like( '_transient_timeout_' . $prefix ) . '%';
|
||||
|
||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Required for bulk pattern-based deletion; no WordPress API available for this operation.
|
||||
// Delete transient values.
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
|
||||
$transient_prefix
|
||||
)
|
||||
);
|
||||
|
||||
// Delete transient timeouts.
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
|
||||
$transient_timeout_prefix
|
||||
)
|
||||
);
|
||||
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
}
|
||||
|
||||
// Run uninstall.
|
||||
robotstxt_docmd_uninstall();
|
||||
|
|
|
|||
29
update.json
Normal file
29
update.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "Documentation Markdown (by ROBOTSTXT)",
|
||||
"slug": "robotstxt-documentation-markdown",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://git.robotstxt.es/ROBOTSTXT/robotstxt-documentation-markdown/releases/download/1.0.0/robotstxt-documentation-markdown-1.0.0.zip",
|
||||
"requires": "6.7",
|
||||
"requires_php": "8.2",
|
||||
"tested": "6.9",
|
||||
"last_updated": "2026-01-26",
|
||||
"author": "ROBOTSTXT",
|
||||
"author_profile": "https://www.robotstxt.es/",
|
||||
"homepage": "https://git.robotstxt.es/ROBOTSTXT/robotstxt-documentation-markdown",
|
||||
"description": "Synchronizes Markdown documentation from GitHub repositories to WordPress pages and posts automatically. Perfect for maintaining technical documentation, API references, and knowledge bases with version control.",
|
||||
"changelog": "<h3>1.0.0 - 2026-01-26</h3><h4>Initial Release</h4><ul><li><strong>Core:</strong> GitHub repository synchronization system</li><li><strong>Core:</strong> Markdown to HTML conversion using CommonMark</li><li><strong>Core:</strong> Encrypted GitHub token storage (AES-256-CBC)</li><li><strong>Feature:</strong> Flexible file-to-content mapping system</li><li><strong>Feature:</strong> Custom Post Type for mapping management</li><li><strong>Feature:</strong> Configurable sync frequency (manual, hourly, twice daily, daily)</li><li><strong>Feature:</strong> Manual on-demand synchronization</li><li><strong>Feature:</strong> Support for pages, posts, and custom post types</li><li><strong>Feature:</strong> Page order (menu_order) configuration</li><li><strong>Feature:</strong> Configurable post author and parent page</li><li><strong>Admin:</strong> Complete admin interface with status monitoring</li><li><strong>Admin:</strong> Settings page for GitHub configuration</li><li><strong>Admin:</strong> Mappings management interface</li><li><strong>Debug:</strong> Built-in debug tools (visible when WP_DEBUG enabled)</li><li><strong>Debug:</strong> Test GitHub connection and token validity</li><li><strong>Debug:</strong> View and manage scheduled cron jobs</li><li><strong>Debug:</strong> Fix/reschedule broken cron jobs</li><li><strong>Debug:</strong> Cache management tools</li><li><strong>Security:</strong> Complete input sanitization and output escaping</li><li><strong>Security:</strong> Nonce verification on all forms</li><li><strong>Security:</strong> Capability checks for admin actions</li><li><strong>i18n:</strong> Full internationalization support</li><li><strong>Quality:</strong> WordPress Coding Standards compliant</li></ul>",
|
||||
"sections": {
|
||||
"description": "<p><strong>Documentation Markdown</strong> is a powerful WordPress plugin that enables seamless synchronization of Markdown documentation files from GitHub repositories into your WordPress site.</p><h4>Key Features</h4><ul><li><strong>Automatic Synchronization:</strong> Schedule automatic syncs via WordPress Cron (hourly, twice daily, daily)</li><li><strong>Markdown to HTML:</strong> Convert GitHub Flavored Markdown to clean HTML using CommonMark</li><li><strong>Flexible Mapping:</strong> Map individual MD files to specific WordPress posts or pages</li><li><strong>Secure:</strong> Encrypted GitHub token storage, full input validation & output escaping</li><li><strong>Translatable:</strong> Full internationalization support (i18n/l10n ready)</li><li><strong>Multi-Repository:</strong> Sync from multiple GitHub repos simultaneously</li><li><strong>Manual Sync:</strong> On-demand synchronization from admin interface</li><li><strong>Debug Tools:</strong> Built-in debugging tools (visible when WP_DEBUG is enabled)</li></ul><h4>Use Cases</h4><ul><li>API Documentation - Keep your API docs in sync between GitHub and WordPress</li><li>Technical Documentation - Maintain version-controlled technical docs</li><li>Knowledge Base - Build a knowledge base powered by GitHub</li><li>Blog Posts - Write blog posts in Markdown with Git workflow</li><li>Product Documentation - Sync product documentation from your repository</li></ul><h4>Requirements</h4><ul><li>PHP 8.2 or higher</li><li>WordPress 6.5 or higher (single-site installations only)</li><li>GitHub Personal Access Token (free)</li></ul>",
|
||||
"installation": "<h4>Installation</h4><ol><li>Upload the plugin files to <code>/wp-content/plugins/robotstxt-documentation-markdown/</code></li><li>Activate the plugin through the 'Plugins' menu in WordPress</li><li>Navigate to 'Documentation → Settings' in the WordPress admin menu</li><li>Generate a GitHub Personal Access Token:<ul><li>Go to GitHub → Settings → Developer settings → Personal access tokens</li><li>Click 'Generate new token'</li><li>For public repositories: No specific scopes needed</li><li>For private repositories: Select <code>repo</code> scope</li></ul></li><li>Paste your token in the plugin settings and save</li><li>Create your first mapping under 'Documentation → Mappings'</li></ol><h4>Configuration</h4><ol><li>Go to <strong>Documentation → Add Mapping</strong></li><li>Fill in the repository details (owner, name, file path, branch)</li><li>Configure target content (post type, author, parent, order)</li><li>Set synchronization frequency</li><li>Save and click 'Sync Now' to perform first sync</li></ol>",
|
||||
"faq": "<h4>Do I need a GitHub account?</h4><p>Yes, you need a GitHub account to generate a Personal Access Token. The token is required to access repositories (public or private).</p><h4>Can I sync from private repositories?</h4><p>Yes! When generating your GitHub Personal Access Token, make sure to select the <code>repo</code> scope for full access to private repositories.</p><h4>How often does synchronization happen?</h4><p>You can configure synchronization frequency per mapping: Manual only, Hourly, Twice daily, or Daily. You can also manually trigger sync at any time.</p><h4>Will the plugin delete my WordPress content if I uninstall it?</h4><p>By default, NO. When you uninstall the plugin, it preserves all synced WordPress pages/posts. However, there's an option in Settings to delete plugin data on uninstall - but this never deletes the actual WordPress content.</p><h4>Can I sync multiple files from the same repository?</h4><p>Yes! You can create multiple mappings, each pointing to different files in the same repository or different repositories.</p><h4>How do I debug synchronization issues?</h4><p>Enable WP_DEBUG in your wp-config.php, then go to Documentation → Settings. You'll see a 'Debug Tools' section with options to test GitHub connection, validate token, view cron jobs, and run manual syncs.</p>",
|
||||
"changelog": "<h3>1.0.0 - 2026-01-26</h3><h4>Initial Release</h4><ul><li><strong>Core:</strong> GitHub repository synchronization system</li><li><strong>Core:</strong> Markdown to HTML conversion using CommonMark</li><li><strong>Core:</strong> Encrypted GitHub token storage (AES-256-CBC)</li><li><strong>Feature:</strong> Flexible file-to-content mapping system</li><li><strong>Feature:</strong> Custom Post Type for mapping management</li><li><strong>Feature:</strong> Configurable sync frequency (manual, hourly, twice daily, daily)</li><li><strong>Feature:</strong> Manual on-demand synchronization</li><li><strong>Feature:</strong> Support for pages, posts, and custom post types</li><li><strong>Feature:</strong> Page order (menu_order) configuration</li><li><strong>Feature:</strong> Configurable post author and parent page</li><li><strong>Admin:</strong> Complete admin interface with status monitoring</li><li><strong>Admin:</strong> Settings page for GitHub configuration</li><li><strong>Admin:</strong> Mappings management interface</li><li><strong>Debug:</strong> Built-in debug tools (visible when WP_DEBUG enabled)</li><li><strong>Debug:</strong> Test GitHub connection and token validity</li><li><strong>Debug:</strong> View and manage scheduled cron jobs</li><li><strong>Debug:</strong> Fix/reschedule broken cron jobs</li><li><strong>Debug:</strong> Cache management tools</li><li><strong>Security:</strong> Complete input sanitization and output escaping</li><li><strong>Security:</strong> Nonce verification on all forms</li><li><strong>Security:</strong> Capability checks for admin actions</li><li><strong>i18n:</strong> Full internationalization support</li><li><strong>Quality:</strong> WordPress Coding Standards compliant</li></ul>"
|
||||
},
|
||||
"banners": {
|
||||
"low": "",
|
||||
"high": ""
|
||||
},
|
||||
"icons": {
|
||||
"1x": "",
|
||||
"2x": ""
|
||||
}
|
||||
}
|
||||
2
vendor/autoload.php
vendored
2
vendor/autoload.php
vendored
|
|
@ -22,4 +22,4 @@ if (PHP_VERSION_ID < 50600) {
|
|||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit5db7b63bf0104878d549f48a1ce8cb76::getLoader();
|
||||
return ComposerAutoloaderInit216e6d5522dd071bc42c5f7c15b28cdc::getLoader();
|
||||
|
|
|
|||
119
vendor/bin/php-parse
vendored
119
vendor/bin/php-parse
vendored
|
|
@ -1,119 +0,0 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../nikic/php-parser/bin/php-parse)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse';
|
||||
5
vendor/bin/php-parse.bat
vendored
5
vendor/bin/php-parse.bat
vendored
|
|
@ -1,5 +0,0 @@
|
|||
@ECHO OFF
|
||||
setlocal DISABLEDELAYEDEXPANSION
|
||||
SET BIN_TARGET=%~dp0/php-parse
|
||||
SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
|
||||
php "%BIN_TARGET%" %*
|
||||
119
vendor/bin/phpcbf
vendored
119
vendor/bin/phpcbf
vendored
|
|
@ -1,119 +0,0 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../squizlabs/php_codesniffer/bin/phpcbf)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcbf');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcbf';
|
||||
5
vendor/bin/phpcbf.bat
vendored
5
vendor/bin/phpcbf.bat
vendored
|
|
@ -1,5 +0,0 @@
|
|||
@ECHO OFF
|
||||
setlocal DISABLEDELAYEDEXPANSION
|
||||
SET BIN_TARGET=%~dp0/phpcbf
|
||||
SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
|
||||
php "%BIN_TARGET%" %*
|
||||
119
vendor/bin/phpcs
vendored
119
vendor/bin/phpcs
vendored
|
|
@ -1,119 +0,0 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../squizlabs/php_codesniffer/bin/phpcs)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcs');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcs';
|
||||
5
vendor/bin/phpcs.bat
vendored
5
vendor/bin/phpcs.bat
vendored
|
|
@ -1,5 +0,0 @@
|
|||
@ECHO OFF
|
||||
setlocal DISABLEDELAYEDEXPANSION
|
||||
SET BIN_TARGET=%~dp0/phpcs
|
||||
SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
|
||||
php "%BIN_TARGET%" %*
|
||||
119
vendor/bin/phpstan
vendored
119
vendor/bin/phpstan
vendored
|
|
@ -1,119 +0,0 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../phpstan/phpstan/phpstan)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpstan/phpstan/phpstan');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/phpstan/phpstan/phpstan';
|
||||
5
vendor/bin/phpstan.bat
vendored
5
vendor/bin/phpstan.bat
vendored
|
|
@ -1,5 +0,0 @@
|
|||
@ECHO OFF
|
||||
setlocal DISABLEDELAYEDEXPANSION
|
||||
SET BIN_TARGET=%~dp0/phpstan
|
||||
SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
|
||||
php "%BIN_TARGET%" %*
|
||||
119
vendor/bin/phpstan.phar
vendored
119
vendor/bin/phpstan.phar
vendored
|
|
@ -1,119 +0,0 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../phpstan/phpstan/phpstan.phar)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpstan/phpstan/phpstan.phar');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/phpstan/phpstan/phpstan.phar';
|
||||
5
vendor/bin/phpstan.phar.bat
vendored
5
vendor/bin/phpstan.phar.bat
vendored
|
|
@ -1,5 +0,0 @@
|
|||
@ECHO OFF
|
||||
setlocal DISABLEDELAYEDEXPANSION
|
||||
SET BIN_TARGET=%~dp0/phpstan.phar
|
||||
SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
|
||||
php "%BIN_TARGET%" %*
|
||||
122
vendor/bin/phpunit
vendored
122
vendor/bin/phpunit
vendored
|
|
@ -1,122 +0,0 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../phpunit/phpunit/phpunit)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
$GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'] = $GLOBALS['__PHPUNIT_ISOLATION_BLACKLIST'] = array(realpath(__DIR__ . '/..'.'/phpunit/phpunit/phpunit'));
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = 'phpvfscomposer://'.$this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
$data = str_replace('__DIR__', var_export(dirname($this->realpath), true), $data);
|
||||
$data = str_replace('__FILE__', var_export($this->realpath, true), $data);
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpunit/phpunit/phpunit');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/phpunit/phpunit/phpunit';
|
||||
119
vendor/bin/wp-since
vendored
119
vendor/bin/wp-since
vendored
|
|
@ -1,119 +0,0 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../eduardovillao/wp-since/bin/wp-since)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/eduardovillao/wp-since/bin/wp-since');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/eduardovillao/wp-since/bin/wp-since';
|
||||
5
vendor/bin/wp-since.bat
vendored
5
vendor/bin/wp-since.bat
vendored
|
|
@ -1,5 +0,0 @@
|
|||
@ECHO OFF
|
||||
setlocal DISABLEDELAYEDEXPANSION
|
||||
SET BIN_TARGET=%~dp0/wp-since
|
||||
SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
|
||||
php "%BIN_TARGET%" %*
|
||||
1519
vendor/composer/autoload_classmap.php
vendored
1519
vendor/composer/autoload_classmap.php
vendored
File diff suppressed because it is too large
Load diff
3
vendor/composer/autoload_files.php
vendored
3
vendor/composer/autoload_files.php
vendored
|
|
@ -6,9 +6,6 @@ $vendorDir = dirname(__DIR__);
|
|||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
|
||||
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
|
||||
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
|
||||
'9b38cf48e83f5d8f60375221cd213eee' => $vendorDir . '/phpstan/phpstan/bootstrap.php',
|
||||
'ec07570ca5a812141189b1fa81503674' => $vendorDir . '/phpunit/phpunit/src/Framework/Assert/Functions.php',
|
||||
);
|
||||
|
|
|
|||
6
vendor/composer/autoload_psr4.php
vendored
6
vendor/composer/autoload_psr4.php
vendored
|
|
@ -6,15 +6,11 @@ $vendorDir = dirname(__DIR__);
|
|||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'WP_Since\\' => array($vendorDir . '/eduardovillao/wp-since/src'),
|
||||
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
|
||||
'RobotsTxt\\DocumentationMarkdown\\' => array($baseDir . '/includes'),
|
||||
'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'),
|
||||
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
|
||||
'PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\' => array($vendorDir . '/dealerdirect/phpcodesniffer-composer-installer/src'),
|
||||
'Nette\\' => array($vendorDir . '/nette/schema/src', $vendorDir . '/nette/utils/src'),
|
||||
'Nette\\' => array($vendorDir . '/nette/utils/src', $vendorDir . '/nette/schema/src'),
|
||||
'League\\Config\\' => array($vendorDir . '/league/config/src'),
|
||||
'League\\CommonMark\\' => array($vendorDir . '/league/commonmark/src'),
|
||||
'Dflydev\\DotAccessData\\' => array($vendorDir . '/dflydev/dot-access-data/src'),
|
||||
'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'),
|
||||
);
|
||||
|
|
|
|||
10
vendor/composer/autoload_real.php
vendored
10
vendor/composer/autoload_real.php
vendored
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInit5db7b63bf0104878d549f48a1ce8cb76
|
||||
class ComposerAutoloaderInit216e6d5522dd071bc42c5f7c15b28cdc
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
|
|
@ -24,16 +24,16 @@ class ComposerAutoloaderInit5db7b63bf0104878d549f48a1ce8cb76
|
|||
|
||||
require __DIR__ . '/platform_check.php';
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit5db7b63bf0104878d549f48a1ce8cb76', 'loadClassLoader'), true, true);
|
||||
spl_autoload_register(array('ComposerAutoloaderInit216e6d5522dd071bc42c5f7c15b28cdc', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit5db7b63bf0104878d549f48a1ce8cb76', 'loadClassLoader'));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit216e6d5522dd071bc42c5f7c15b28cdc', 'loadClassLoader'));
|
||||
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit5db7b63bf0104878d549f48a1ce8cb76::getInitializer($loader));
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit216e6d5522dd071bc42c5f7c15b28cdc::getInitializer($loader));
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
$filesToLoad = \Composer\Autoload\ComposerStaticInit5db7b63bf0104878d549f48a1ce8cb76::$files;
|
||||
$filesToLoad = \Composer\Autoload\ComposerStaticInit216e6d5522dd071bc42c5f7c15b28cdc::$files;
|
||||
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
|
||||
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
|
||||
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
|
||||
|
|
|
|||
1557
vendor/composer/autoload_static.php
vendored
1557
vendor/composer/autoload_static.php
vendored
File diff suppressed because it is too large
Load diff
2553
vendor/composer/installed.json
vendored
2553
vendor/composer/installed.json
vendored
File diff suppressed because it is too large
Load diff
344
vendor/composer/installed.php
vendored
344
vendor/composer/installed.php
vendored
|
|
@ -1,24 +1,15 @@
|
|||
<?php return array(
|
||||
'root' => array(
|
||||
'name' => 'robotstxt/documentation-markdown',
|
||||
'pretty_version' => 'dev-main',
|
||||
'version' => 'dev-main',
|
||||
'reference' => 'c7911a52f403328162659f7318199209c20441db',
|
||||
'pretty_version' => '1.0.0+no-version-set',
|
||||
'version' => '1.0.0.0',
|
||||
'reference' => null,
|
||||
'type' => 'wordpress-plugin',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev' => true,
|
||||
'dev' => false,
|
||||
),
|
||||
'versions' => array(
|
||||
'dealerdirect/phpcodesniffer-composer-installer' => array(
|
||||
'pretty_version' => 'v1.2.0',
|
||||
'version' => '1.2.0.0',
|
||||
'reference' => '845eb62303d2ca9b289ef216356568ccc075ffd1',
|
||||
'type' => 'composer-plugin',
|
||||
'install_path' => __DIR__ . '/../dealerdirect/phpcodesniffer-composer-installer',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'dflydev/dot-access-data' => array(
|
||||
'pretty_version' => 'v3.0.3',
|
||||
'version' => '3.0.3.0',
|
||||
|
|
@ -28,19 +19,10 @@
|
|||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'eduardovillao/wp-since' => array(
|
||||
'pretty_version' => 'v1.3.0',
|
||||
'version' => '1.3.0.0',
|
||||
'reference' => 'c7ad6e49b045f144fbc40a91a45590c3dfd240c2',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../eduardovillao/wp-since',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'league/commonmark' => array(
|
||||
'pretty_version' => '2.8.0',
|
||||
'version' => '2.8.0.0',
|
||||
'reference' => '4efa10c1e56488e658d10adf7b7b7dcd19940bfb',
|
||||
'pretty_version' => '2.8.2',
|
||||
'version' => '2.8.2.0',
|
||||
'reference' => '59fb075d2101740c337c7216e3f32b36c204218b',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../league/commonmark',
|
||||
'aliases' => array(),
|
||||
|
|
@ -55,15 +37,6 @@
|
|||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'myclabs/deep-copy' => array(
|
||||
'pretty_version' => '1.13.4',
|
||||
'version' => '1.13.4.0',
|
||||
'reference' => '07d290f0c47959fd5eed98c95ee5602db07e0b6a',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../myclabs/deep-copy',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'nette/schema' => array(
|
||||
'pretty_version' => 'v1.3.3',
|
||||
'version' => '1.3.3.0',
|
||||
|
|
@ -82,141 +55,6 @@
|
|||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'nikic/php-parser' => array(
|
||||
'pretty_version' => 'v4.19.5',
|
||||
'version' => '4.19.5.0',
|
||||
'reference' => '51bd93cc741b7fc3d63d20b6bdcd99fdaa359837',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../nikic/php-parser',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'phar-io/manifest' => array(
|
||||
'pretty_version' => '2.0.4',
|
||||
'version' => '2.0.4.0',
|
||||
'reference' => '54750ef60c58e43759730615a392c31c80e23176',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phar-io/manifest',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'phar-io/version' => array(
|
||||
'pretty_version' => '3.2.1',
|
||||
'version' => '3.2.1.0',
|
||||
'reference' => '4f7fd7836c6f332bb2933569e566a0d6c4cbed74',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phar-io/version',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'phpcompatibility/php-compatibility' => array(
|
||||
'pretty_version' => '9.3.5',
|
||||
'version' => '9.3.5.0',
|
||||
'reference' => '9fb324479acf6f39452e0655d2429cc0d3914243',
|
||||
'type' => 'phpcodesniffer-standard',
|
||||
'install_path' => __DIR__ . '/../phpcompatibility/php-compatibility',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'phpcompatibility/phpcompatibility-paragonie' => array(
|
||||
'pretty_version' => '1.3.4',
|
||||
'version' => '1.3.4.0',
|
||||
'reference' => '244d7b04fc4bc2117c15f5abe23eb933b5f02bbf',
|
||||
'type' => 'phpcodesniffer-standard',
|
||||
'install_path' => __DIR__ . '/../phpcompatibility/phpcompatibility-paragonie',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'phpcompatibility/phpcompatibility-wp' => array(
|
||||
'pretty_version' => '2.1.8',
|
||||
'version' => '2.1.8.0',
|
||||
'reference' => '7c8d18b4d90dac9e86b0869a608fa09158e168fa',
|
||||
'type' => 'phpcodesniffer-standard',
|
||||
'install_path' => __DIR__ . '/../phpcompatibility/phpcompatibility-wp',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'phpcsstandards/phpcsextra' => array(
|
||||
'pretty_version' => '1.5.0',
|
||||
'version' => '1.5.0.0',
|
||||
'reference' => 'b598aa890815b8df16363271b659d73280129101',
|
||||
'type' => 'phpcodesniffer-standard',
|
||||
'install_path' => __DIR__ . '/../phpcsstandards/phpcsextra',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'phpcsstandards/phpcsutils' => array(
|
||||
'pretty_version' => '1.2.2',
|
||||
'version' => '1.2.2.0',
|
||||
'reference' => 'c216317e96c8b3f5932808f9b0f1f7a14e3bbf55',
|
||||
'type' => 'phpcodesniffer-standard',
|
||||
'install_path' => __DIR__ . '/../phpcsstandards/phpcsutils',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'phpstan/phpstan' => array(
|
||||
'pretty_version' => '1.12.32',
|
||||
'version' => '1.12.32.0',
|
||||
'reference' => '2770dcdf5078d0b0d53f94317e06affe88419aa8',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phpstan/phpstan',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'phpunit/php-code-coverage' => array(
|
||||
'pretty_version' => '10.1.16',
|
||||
'version' => '10.1.16.0',
|
||||
'reference' => '7e308268858ed6baedc8704a304727d20bc07c77',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phpunit/php-code-coverage',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'phpunit/php-file-iterator' => array(
|
||||
'pretty_version' => '4.1.0',
|
||||
'version' => '4.1.0.0',
|
||||
'reference' => 'a95037b6d9e608ba092da1b23931e537cadc3c3c',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phpunit/php-file-iterator',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'phpunit/php-invoker' => array(
|
||||
'pretty_version' => '4.0.0',
|
||||
'version' => '4.0.0.0',
|
||||
'reference' => 'f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phpunit/php-invoker',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'phpunit/php-text-template' => array(
|
||||
'pretty_version' => '3.0.1',
|
||||
'version' => '3.0.1.0',
|
||||
'reference' => '0c7b06ff49e3d5072f057eb1fa59258bf287a748',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phpunit/php-text-template',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'phpunit/php-timer' => array(
|
||||
'pretty_version' => '6.0.0',
|
||||
'version' => '6.0.0.0',
|
||||
'reference' => 'e2a2d67966e740530f4a3343fe2e030ffdc1161d',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phpunit/php-timer',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'phpunit/phpunit' => array(
|
||||
'pretty_version' => '10.5.63',
|
||||
'version' => '10.5.63.0',
|
||||
'reference' => '33198268dad71e926626b618f3ec3966661e4d90',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phpunit/phpunit',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'psr/event-dispatcher' => array(
|
||||
'pretty_version' => '1.0.0',
|
||||
'version' => '1.0.0.0',
|
||||
|
|
@ -227,158 +65,14 @@
|
|||
'dev_requirement' => false,
|
||||
),
|
||||
'robotstxt/documentation-markdown' => array(
|
||||
'pretty_version' => 'dev-main',
|
||||
'version' => 'dev-main',
|
||||
'reference' => 'c7911a52f403328162659f7318199209c20441db',
|
||||
'pretty_version' => '1.0.0+no-version-set',
|
||||
'version' => '1.0.0.0',
|
||||
'reference' => null,
|
||||
'type' => 'wordpress-plugin',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'sebastian/cli-parser' => array(
|
||||
'pretty_version' => '2.0.1',
|
||||
'version' => '2.0.1.0',
|
||||
'reference' => 'c34583b87e7b7a8055bf6c450c2c77ce32a24084',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../sebastian/cli-parser',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'sebastian/code-unit' => array(
|
||||
'pretty_version' => '2.0.0',
|
||||
'version' => '2.0.0.0',
|
||||
'reference' => 'a81fee9eef0b7a76af11d121767abc44c104e503',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../sebastian/code-unit',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'sebastian/code-unit-reverse-lookup' => array(
|
||||
'pretty_version' => '3.0.0',
|
||||
'version' => '3.0.0.0',
|
||||
'reference' => '5e3a687f7d8ae33fb362c5c0743794bbb2420a1d',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../sebastian/code-unit-reverse-lookup',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'sebastian/comparator' => array(
|
||||
'pretty_version' => '5.0.5',
|
||||
'version' => '5.0.5.0',
|
||||
'reference' => '55dfef806eb7dfeb6e7a6935601fef866f8ca48d',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../sebastian/comparator',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'sebastian/complexity' => array(
|
||||
'pretty_version' => '3.2.0',
|
||||
'version' => '3.2.0.0',
|
||||
'reference' => '68ff824baeae169ec9f2137158ee529584553799',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../sebastian/complexity',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'sebastian/diff' => array(
|
||||
'pretty_version' => '5.1.1',
|
||||
'version' => '5.1.1.0',
|
||||
'reference' => 'c41e007b4b62af48218231d6c2275e4c9b975b2e',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../sebastian/diff',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'sebastian/environment' => array(
|
||||
'pretty_version' => '6.1.0',
|
||||
'version' => '6.1.0.0',
|
||||
'reference' => '8074dbcd93529b357029f5cc5058fd3e43666984',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../sebastian/environment',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'sebastian/exporter' => array(
|
||||
'pretty_version' => '5.1.4',
|
||||
'version' => '5.1.4.0',
|
||||
'reference' => '0735b90f4da94969541dac1da743446e276defa6',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../sebastian/exporter',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'sebastian/global-state' => array(
|
||||
'pretty_version' => '6.0.2',
|
||||
'version' => '6.0.2.0',
|
||||
'reference' => '987bafff24ecc4c9ac418cab1145b96dd6e9cbd9',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../sebastian/global-state',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'sebastian/lines-of-code' => array(
|
||||
'pretty_version' => '2.0.2',
|
||||
'version' => '2.0.2.0',
|
||||
'reference' => '856e7f6a75a84e339195d48c556f23be2ebf75d0',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../sebastian/lines-of-code',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'sebastian/object-enumerator' => array(
|
||||
'pretty_version' => '5.0.0',
|
||||
'version' => '5.0.0.0',
|
||||
'reference' => '202d0e344a580d7f7d04b3fafce6933e59dae906',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../sebastian/object-enumerator',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'sebastian/object-reflector' => array(
|
||||
'pretty_version' => '3.0.0',
|
||||
'version' => '3.0.0.0',
|
||||
'reference' => '24ed13d98130f0e7122df55d06c5c4942a577957',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../sebastian/object-reflector',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'sebastian/recursion-context' => array(
|
||||
'pretty_version' => '5.0.1',
|
||||
'version' => '5.0.1.0',
|
||||
'reference' => '47e34210757a2f37a97dcd207d032e1b01e64c7a',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../sebastian/recursion-context',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'sebastian/type' => array(
|
||||
'pretty_version' => '4.0.0',
|
||||
'version' => '4.0.0.0',
|
||||
'reference' => '462699a16464c3944eefc02ebdd77882bd3925bf',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../sebastian/type',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'sebastian/version' => array(
|
||||
'pretty_version' => '4.0.1',
|
||||
'version' => '4.0.1.0',
|
||||
'reference' => 'c51fa83a5d8f43f1402e3f32a005e6262244ef17',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../sebastian/version',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'squizlabs/php_codesniffer' => array(
|
||||
'pretty_version' => '3.13.5',
|
||||
'version' => '3.13.5.0',
|
||||
'reference' => '0ca86845ce43291e8f5692c7356fccf3bcf02bf4',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../squizlabs/php_codesniffer',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'symfony/deprecation-contracts' => array(
|
||||
'pretty_version' => 'v3.6.0',
|
||||
'version' => '3.6.0.0',
|
||||
|
|
@ -397,23 +91,5 @@
|
|||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'theseer/tokenizer' => array(
|
||||
'pretty_version' => '1.3.1',
|
||||
'version' => '1.3.1.0',
|
||||
'reference' => 'b7489ce515e168639d17feec34b8847c326b0b3c',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../theseer/tokenizer',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'wp-coding-standards/wpcs' => array(
|
||||
'pretty_version' => '3.3.0',
|
||||
'version' => '3.3.0.0',
|
||||
'reference' => '7795ec6fa05663d716a549d0b44e47ffc8b0d4a6',
|
||||
'type' => 'phpcodesniffer-standard',
|
||||
'install_path' => __DIR__ . '/../wp-coding-standards/wpcs',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,587 +0,0 @@
|
|||
# Change Log for the Composer Installer for PHP CodeSniffer
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
This projects adheres to [Keep a CHANGELOG](https://keepachangelog.com/) and uses [Semantic Versioning](https://semver.org/).
|
||||
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
_Nothing yet._
|
||||
|
||||
|
||||
## [v1.2.0] - 2025-11-11
|
||||
|
||||
### Changed
|
||||
- Various housekeeping, including improvements to the documentation and tests.
|
||||
|
||||
### Removed
|
||||
- Drop support for PHP_CodeSniffer 2.x. Thanks [@jrfnl] ! [#261]
|
||||
|
||||
[#261]: https://github.com/PHPCSStandards/composer-installer/pull/261
|
||||
|
||||
|
||||
## [v1.1.2] - 2025-07-17
|
||||
|
||||
### Changed
|
||||
- General housekeeping.
|
||||
|
||||
### Fixed
|
||||
- [#247]: Potential fatal error when the Composer EventDispatcher is called programmatically from an integration. Thanks [@jrfnl] ! [#248]
|
||||
|
||||
[#247]: https://github.com/PHPCSStandards/composer-installer/issues/247
|
||||
[#248]: https://github.com/PHPCSStandards/composer-installer/pull/248
|
||||
|
||||
|
||||
## [v1.1.1] - 2025-06-27
|
||||
|
||||
### Changed
|
||||
- Various housekeeping, including improvements to the documentation.
|
||||
|
||||
### Fixed
|
||||
- [#239]: The PHP_CodeSniffer package could not be always found when running the plugin in a Drupal or Magento setup. Thanks [@jrfnl] ! [#245]
|
||||
|
||||
[#239]: https://github.com/PHPCSStandards/composer-installer/issues/239
|
||||
[#245]: https://github.com/PHPCSStandards/composer-installer/pull/245
|
||||
|
||||
|
||||
## [v1.1.0] - 2025-06-24
|
||||
|
||||
### Changed
|
||||
- Various housekeeping, including improvements to the documentation and tests. Thanks [@SplotyCode], [@fredden] for contributing!
|
||||
|
||||
### Removed
|
||||
- Drop support for Composer v1.x. Thanks [@fredden] ! [#230]
|
||||
|
||||
[#230]: https://github.com/PHPCSStandards/composer-installer/pull/230
|
||||
|
||||
|
||||
## [v1.0.0] - 2023-01-05
|
||||
|
||||
### Breaking changes
|
||||
- Rename namespace prefix from Dealerdirect to PHPCSStandards by [@jrfnl] in [#191]
|
||||
- Drop support for PHP 5.3 by [@jrfnl] in [#147]
|
||||
|
||||
### Changed
|
||||
- Correct grammar in error message by [@fredden] in [#189]
|
||||
- .gitattributes: sync with current repo state by [@jrfnl] in [#198]
|
||||
- PHPCSVersions: update URL references by [@jrfnl] in [#161]
|
||||
- README: remove references to Scrutinizer by [@jrfnl] in [#157]
|
||||
- Rename references to master branch by [@Potherca] in [#201]
|
||||
- Update repo references by [@jrfnl] in [#158]
|
||||
- GH Actions: add builds against Composer 2.2 for PHP 7.2 - 8.x by [@jrfnl] in [#172]
|
||||
- GH Actions: bust the cache semi-regularly by [@jrfnl] in [#192]
|
||||
- GH Actions: fix builds on Windows with PHP 8.2 by [@jrfnl] in [#180]
|
||||
- GH Actions: fix up fail-fast for setup-php by [@jrfnl] in [#195]
|
||||
- GH Actions: run integration tests against Composer snapshot by [@jrfnl] in [#163]
|
||||
- GH Actions: run linting against against ubuntu-latest by [@jrfnl] in [#184]
|
||||
- GH Actions/Securitycheck: update the security checker download by [@jrfnl] in [#178]
|
||||
- GH Actions/Securitycheck: update the security checker download by [@jrfnl] in [#186]
|
||||
- GH Actions/Securitycheck: update the security checker download by [@jrfnl] in [#190]
|
||||
- GH Actions: selectively use fail-fast with setup-php by [@jrfnl] in [#194]
|
||||
- GH Actions: stop running tests against PHP 5.5/Composer 1.x on Windows (and remove work-arounds) by [@jrfnl] in [#183]
|
||||
- GH Actions: various tweaks / PHP 8.2 not allowed to fail by [@jrfnl] in [#193]
|
||||
- GH Actions: version update for various predefined actions by [@jrfnl] in [#170]
|
||||
- Update YamLint by [@Potherca] in [#173]
|
||||
- Add initial integration test setup and first few tests by [@jrfnl] in [#153]
|
||||
- BaseLineTest: stabilize the message checks by [@jrfnl] in [#162]
|
||||
- PlayNiceWithScriptsTest: wrap output expectation in condition by [@jrfnl] in [#179]
|
||||
- RegisterExternalStandardsTest: add new tests by [@jrfnl] in [#165]
|
||||
- RegisterExternalStandardsTest: stabilize test for Composer v1 on Windows with PHP 5.5 by [@jrfnl] in [#171]
|
||||
- TestCase::executeCliCommand(): retry Composer commands on a particular exception by [@jrfnl] in [#164]
|
||||
- Tests: add new InstalledPathsOrderTest by [@jrfnl] in [#176]
|
||||
- Tests: add new InstallUpdateEventsTest and NonInstallUpdateEventsTest by [@jrfnl] in [#174]
|
||||
- Tests: add new InvalidPackagesTest by [@jrfnl] in [#168]
|
||||
- Tests: add new PlayNiceWithScriptsTest by [@jrfnl] in [#169]
|
||||
- Tests: add new PreexistingPHPCSConfigTest by [@jrfnl] in [#166]
|
||||
- Tests: add new PreexistingPHPCSInstalledPathsConfigTest + bug fix by [@jrfnl] in [#167]
|
||||
- Tests: add new RemovePluginTest by [@jrfnl] in [#177]
|
||||
- Tests: add new RootPackageHandlingTest + bugfix by [@jrfnl] in [#175]
|
||||
|
||||
### Fixed
|
||||
- Plugin: improve feedback by [@jrfnl] in [#182]
|
||||
|
||||
[#147]: https://github.com/PHPCSStandards/composer-installer/pull/147
|
||||
[#153]: https://github.com/PHPCSStandards/composer-installer/pull/153
|
||||
[#157]: https://github.com/PHPCSStandards/composer-installer/pull/157
|
||||
[#158]: https://github.com/PHPCSStandards/composer-installer/pull/158
|
||||
[#161]: https://github.com/PHPCSStandards/composer-installer/pull/161
|
||||
[#162]: https://github.com/PHPCSStandards/composer-installer/pull/162
|
||||
[#163]: https://github.com/PHPCSStandards/composer-installer/pull/163
|
||||
[#164]: https://github.com/PHPCSStandards/composer-installer/pull/164
|
||||
[#165]: https://github.com/PHPCSStandards/composer-installer/pull/165
|
||||
[#166]: https://github.com/PHPCSStandards/composer-installer/pull/166
|
||||
[#167]: https://github.com/PHPCSStandards/composer-installer/pull/167
|
||||
[#168]: https://github.com/PHPCSStandards/composer-installer/pull/168
|
||||
[#169]: https://github.com/PHPCSStandards/composer-installer/pull/169
|
||||
[#170]: https://github.com/PHPCSStandards/composer-installer/pull/170
|
||||
[#171]: https://github.com/PHPCSStandards/composer-installer/pull/171
|
||||
[#172]: https://github.com/PHPCSStandards/composer-installer/pull/172
|
||||
[#173]: https://github.com/PHPCSStandards/composer-installer/pull/173
|
||||
[#174]: https://github.com/PHPCSStandards/composer-installer/pull/174
|
||||
[#175]: https://github.com/PHPCSStandards/composer-installer/pull/175
|
||||
[#176]: https://github.com/PHPCSStandards/composer-installer/pull/176
|
||||
[#177]: https://github.com/PHPCSStandards/composer-installer/pull/177
|
||||
[#178]: https://github.com/PHPCSStandards/composer-installer/pull/178
|
||||
[#179]: https://github.com/PHPCSStandards/composer-installer/pull/179
|
||||
[#180]: https://github.com/PHPCSStandards/composer-installer/pull/180
|
||||
[#182]: https://github.com/PHPCSStandards/composer-installer/pull/182
|
||||
[#183]: https://github.com/PHPCSStandards/composer-installer/pull/183
|
||||
[#184]: https://github.com/PHPCSStandards/composer-installer/pull/184
|
||||
[#186]: https://github.com/PHPCSStandards/composer-installer/pull/186
|
||||
[#189]: https://github.com/PHPCSStandards/composer-installer/pull/189
|
||||
[#190]: https://github.com/PHPCSStandards/composer-installer/pull/190
|
||||
[#191]: https://github.com/PHPCSStandards/composer-installer/pull/191
|
||||
[#192]: https://github.com/PHPCSStandards/composer-installer/pull/192
|
||||
[#193]: https://github.com/PHPCSStandards/composer-installer/pull/193
|
||||
[#194]: https://github.com/PHPCSStandards/composer-installer/pull/194
|
||||
[#195]: https://github.com/PHPCSStandards/composer-installer/pull/195
|
||||
[#198]: https://github.com/PHPCSStandards/composer-installer/pull/198
|
||||
[#201]: https://github.com/PHPCSStandards/composer-installer/pull/201
|
||||
|
||||
|
||||
## [v0.7.2] - 2022-02-04
|
||||
|
||||
### Changed
|
||||
- Add details regarding QA automation in CONTRIBUTING.md file. by [@Potherca] in [#133]
|
||||
- Add mention of Composer and PHP compatibility to project README. by [@Potherca] in [#132]
|
||||
- Composer: tweak PHPCS version constraint by [@jrfnl] in [#152]
|
||||
- CONTRIBUTING: remove duplicate code of conduct by [@jrfnl] in [#148]
|
||||
- Document release process by [@Potherca] in [#118]
|
||||
- Plugin::loadInstalledPaths(): config-show always shows all by [@jrfnl] in [#154]
|
||||
- README: minor tweaks by [@jrfnl] in [#149]
|
||||
- README: update with information about Composer >= 2.2 by [@jrfnl] in [#141]
|
||||
- Replace deprecated Sensiolabs security checker by [@paras-malhotra] in [#130]
|
||||
- Stabilize a condition by [@jrfnl] in [#127]
|
||||
- Update copyright year by [@jrfnl] in [#138]
|
||||
- Various minor tweaks by [@jrfnl] in [#151]
|
||||
- Change YamlLint config to prevent "truthy" warning. by [@Potherca] in [#144]
|
||||
- GH Actions: PHP 8.1 has been released by [@jrfnl] in [#139]
|
||||
- Travis: line length tweaks by [@jrfnl] in [#128]
|
||||
- CI: Switch to GH Actions by [@jrfnl] in [#137]
|
||||
- CI: various updates by [@jrfnl] in [#140]
|
||||
|
||||
[#118]: https://github.com/PHPCSStandards/composer-installer/pull/118
|
||||
[#127]: https://github.com/PHPCSStandards/composer-installer/pull/127
|
||||
[#128]: https://github.com/PHPCSStandards/composer-installer/pull/128
|
||||
[#130]: https://github.com/PHPCSStandards/composer-installer/pull/130
|
||||
[#132]: https://github.com/PHPCSStandards/composer-installer/pull/132
|
||||
[#133]: https://github.com/PHPCSStandards/composer-installer/pull/133
|
||||
[#137]: https://github.com/PHPCSStandards/composer-installer/pull/137
|
||||
[#138]: https://github.com/PHPCSStandards/composer-installer/pull/138
|
||||
[#139]: https://github.com/PHPCSStandards/composer-installer/pull/139
|
||||
[#140]: https://github.com/PHPCSStandards/composer-installer/pull/140
|
||||
[#141]: https://github.com/PHPCSStandards/composer-installer/pull/141
|
||||
[#144]: https://github.com/PHPCSStandards/composer-installer/pull/144
|
||||
[#148]: https://github.com/PHPCSStandards/composer-installer/pull/148
|
||||
[#149]: https://github.com/PHPCSStandards/composer-installer/pull/149
|
||||
[#151]: https://github.com/PHPCSStandards/composer-installer/pull/151
|
||||
[#152]: https://github.com/PHPCSStandards/composer-installer/pull/152
|
||||
[#154]: https://github.com/PHPCSStandards/composer-installer/pull/154
|
||||
|
||||
|
||||
## [v0.7.1] - 2020-12-07
|
||||
|
||||
### Closed issues
|
||||
- Order of installed_paths inconsistent between runs [#125]
|
||||
- Maintaining this project and Admin rights [#113]
|
||||
|
||||
### Changed
|
||||
- Sort list of installed paths before saving for consistency by [@kevinfodness] in [#126]
|
||||
- Update code of conduct by [@Potherca] in [#117]
|
||||
- Add remark configuration by [@Potherca] in [#122]
|
||||
- Travis: add build against PHP 8.0 by [@jrfnl] in [#124]
|
||||
|
||||
### Fixed
|
||||
- Fixed v4 constraint by [@GrahamCampbell] in [#115]
|
||||
|
||||
[#113]: https://github.com/PHPCSStandards/composer-installer/issues/113
|
||||
[#115]: https://github.com/PHPCSStandards/composer-installer/pull/115
|
||||
[#117]: https://github.com/PHPCSStandards/composer-installer/pull/117
|
||||
[#122]: https://github.com/PHPCSStandards/composer-installer/pull/122
|
||||
[#124]: https://github.com/PHPCSStandards/composer-installer/pull/124
|
||||
[#125]: https://github.com/PHPCSStandards/composer-installer/issues/125
|
||||
[#126]: https://github.com/PHPCSStandards/composer-installer/pull/126
|
||||
|
||||
|
||||
## [v0.7.0] - 2020-06-25
|
||||
|
||||
### Closed issues
|
||||
- Composer 2.x compatibility [#108]
|
||||
- Add link to Packagist on main page [#110]
|
||||
- Switch from Travis CI .org to .com [#112]
|
||||
|
||||
### Added
|
||||
- Allow installation on PHP 8 by [@jrfnl] in [#106]
|
||||
- Support Composer 2.0 by [@jrfnl] in [#111]
|
||||
|
||||
### Changed
|
||||
- Test with PHPCS 4.x and allow installation when using PHPCS 4.x by [@jrfnl] in [#107]
|
||||
- Fix case of class name by [@Seldaek] in [#109]
|
||||
|
||||
[#106]: https://github.com/PHPCSStandards/composer-installer/pull/106
|
||||
[#107]: https://github.com/PHPCSStandards/composer-installer/pull/107
|
||||
[#108]: https://github.com/PHPCSStandards/composer-installer/issues/108
|
||||
[#109]: https://github.com/PHPCSStandards/composer-installer/pull/109
|
||||
[#110]: https://github.com/PHPCSStandards/composer-installer/issues/110
|
||||
[#111]: https://github.com/PHPCSStandards/composer-installer/pull/111
|
||||
[#112]: https://github.com/PHPCSStandards/composer-installer/issues/112
|
||||
|
||||
|
||||
## [v0.6.2] - 2020-01-29
|
||||
|
||||
### Fixed
|
||||
- Composer scripts/commands broken in 0.6.0 update by [@BrianHenryIE] in [#105]
|
||||
|
||||
[#105]: https://github.com/PHPCSStandards/composer-installer/pull/105
|
||||
|
||||
## [v0.6.1] - 2020-01-27
|
||||
|
||||
### Closed issues
|
||||
- Do not exit with code 1 on uninstall (--no-dev) [#103]
|
||||
|
||||
### Changed
|
||||
- Readme: minor tweak now 0.6.0 has been released [#102] ([@jrfnl])
|
||||
|
||||
### Fixed
|
||||
- [#103]: Fix for issue #103 [#104] ([@Potherca])
|
||||
|
||||
[#102]: https://github.com/PHPCSStandards/composer-installer/pull/102
|
||||
[#103]: https://github.com/PHPCSStandards/composer-installer/issues/103
|
||||
[#104]: https://github.com/PHPCSStandards/composer-installer/pull/104
|
||||
|
||||
|
||||
## [v0.6.0] - 2020-01-19
|
||||
|
||||
### Closed issues
|
||||
- Composer PHP version appears not to be respected [#79]
|
||||
- Allow a string value for extra.phpcodesniffer-search-depth [#82]
|
||||
- Add [@jrfnl] as (co)maintainer to this project [#87]
|
||||
|
||||
### Added
|
||||
- Add support for a string phpcodesniffer-search-depth config value set via composer config by [@TravisCarden] in [#85]
|
||||
- Send an exit code when the script terminates by [@jrfnl] in [#93]
|
||||
- Verify the installed_paths after save by [@jrfnl] in [#97]
|
||||
|
||||
### Changed
|
||||
- CS: fix compliance with PSR12 by [@jrfnl] in [#88]
|
||||
- Improve GH issue template by [@jrfnl] in [#94]
|
||||
- Readme: add section about including this plugin from an external PHPCS standard by [@jrfnl] in [#95]
|
||||
- Bug report template: further enhancement by [@jrfnl] in [#99]
|
||||
- Update copyright year. by [@Potherca] in [#101]
|
||||
- Adding linting jobs in github action by [@mjrider] in [#96]
|
||||
- GH Actions: minor tweaks: by [@jrfnl] in [#100]
|
||||
- Travis: disable Xdebug by [@jrfnl] in [#89]
|
||||
- Travis: test against PHP 7.4, not snapshot by [@jrfnl] in [#90]
|
||||
- Travis: use a mix of PHPCS versions in the matrix by [@jrfnl] in [#91]
|
||||
- Update Travis file and fix build by [@Potherca] in [#86]
|
||||
|
||||
### Fixed
|
||||
- [#79]: Respect PHP version used by Composer and provide better feedback on failure by [@jrfnl] in [#80]
|
||||
- Bug fix: loadInstalledPaths() very very broken since PHPCS 3.1.0 by [@jrfnl] in [#98]
|
||||
|
||||
[#79]: https://github.com/PHPCSStandards/composer-installer/issues/79
|
||||
[#80]: https://github.com/PHPCSStandards/composer-installer/issues/80
|
||||
[#82]: https://github.com/PHPCSStandards/composer-installer/issues/82
|
||||
[#85]: https://github.com/PHPCSStandards/composer-installer/pull/85
|
||||
[#86]: https://github.com/PHPCSStandards/composer-installer/pull/86
|
||||
[#87]: https://github.com/PHPCSStandards/composer-installer/issues/87
|
||||
[#88]: https://github.com/PHPCSStandards/composer-installer/pull/88
|
||||
[#89]: https://github.com/PHPCSStandards/composer-installer/pull/89
|
||||
[#90]: https://github.com/PHPCSStandards/composer-installer/pull/90
|
||||
[#91]: https://github.com/PHPCSStandards/composer-installer/pull/91
|
||||
[#93]: https://github.com/PHPCSStandards/composer-installer/pull/93
|
||||
[#94]: https://github.com/PHPCSStandards/composer-installer/pull/94
|
||||
[#95]: https://github.com/PHPCSStandards/composer-installer/pull/95
|
||||
[#96]: https://github.com/PHPCSStandards/composer-installer/pull/96
|
||||
[#97]: https://github.com/PHPCSStandards/composer-installer/pull/97
|
||||
[#98]: https://github.com/PHPCSStandards/composer-installer/issues/98
|
||||
[#99]: https://github.com/PHPCSStandards/composer-installer/pull/99
|
||||
[#100]: https://github.com/PHPCSStandards/composer-installer/pull/100
|
||||
[#101]: https://github.com/PHPCSStandards/composer-installer/pull/101
|
||||
|
||||
|
||||
## [v0.5.0] - 2018-10-26
|
||||
|
||||
### Closed issues
|
||||
- Scan depth as parameter [#45]
|
||||
- phpcs: Exit Code: 127 (Command not found) on every Composer command [#48]
|
||||
- The composer plugin implementation seems to be breaking the composer lifecycle [#49]
|
||||
- Installation error [#53]
|
||||
- Broke composer commands when used with wp-cli/package-command [#59]
|
||||
- Getting a new stable release [#60]
|
||||
- Support PHP CodeSniffer standards in packages installed outside of the vendor directory [#63]
|
||||
|
||||
### Added
|
||||
- Adds the ability to set the max depth from the composer.json file by [@Potherca] in [#46]
|
||||
|
||||
### Changed
|
||||
- Build/PHPCS: update PHPCompatibility repo name by [@jrfnl] in [#54]
|
||||
- README: remove VersionEye badge by [@jrfnl] in [#55]
|
||||
- README: replace maintenance badge by [@jrfnl] in [#56]
|
||||
- Execute phpcs and security-checker from vendor/bin by [@gapple] in [#52]
|
||||
- PHPCS: various minor tweaks by [@jrfnl] in [#57]
|
||||
- Travis: various tweaks by [@jrfnl] in [#58]
|
||||
- Use PHPCompatibility 9.0.0 by [@jrfnl] in [#61]
|
||||
- Build/Travis: test builds against PHP 7.3 by [@jrfnl] in [#62]
|
||||
- Updates copyright year by [@frenck] in [#67]
|
||||
- Enforces PSR12 by [@frenck] in [#66]
|
||||
- Updates contact information by [@frenck] in [#68]
|
||||
- Updates README, spelling/grammar, removed Working section by [@frenck] in [#69]
|
||||
- Replaces ProcessBuilder by ProcessExecutor by [@frenck] in [#70]
|
||||
- Refactors relative path logic by [@frenck] in [#71]
|
||||
- Removes suggested packages by [@frenck] in [#72]
|
||||
- Ensures absolute paths during detection phase by [@frenck] in [#73]
|
||||
- Trivial code cleanup by [@frenck] in [#74]
|
||||
- Fixes duplicate declaration of cwd by [@frenck] in [#75]
|
||||
- Removes HHVM from TravisCI by [@frenck] in [#76]
|
||||
- Adds PHP_CodeSniffer version constraints by [@frenck] in [#77]
|
||||
|
||||
### Fixed
|
||||
- [#49]: Move loadInstalledPaths from init to onDependenciesChangedEvent by [@gapple] in [#51]
|
||||
|
||||
[#45]: https://github.com/PHPCSStandards/composer-installer/issues/45
|
||||
[#46]: https://github.com/PHPCSStandards/composer-installer/pull/46
|
||||
[#48]: https://github.com/PHPCSStandards/composer-installer/issues/48
|
||||
[#49]: https://github.com/PHPCSStandards/composer-installer/issues/49
|
||||
[#51]: https://github.com/PHPCSStandards/composer-installer/pull/51
|
||||
[#52]: https://github.com/PHPCSStandards/composer-installer/pull/52
|
||||
[#53]: https://github.com/PHPCSStandards/composer-installer/issues/53
|
||||
[#54]: https://github.com/PHPCSStandards/composer-installer/pull/54
|
||||
[#55]: https://github.com/PHPCSStandards/composer-installer/pull/55
|
||||
[#56]: https://github.com/PHPCSStandards/composer-installer/pull/56
|
||||
[#57]: https://github.com/PHPCSStandards/composer-installer/pull/57
|
||||
[#58]: https://github.com/PHPCSStandards/composer-installer/pull/58
|
||||
[#59]: https://github.com/PHPCSStandards/composer-installer/issues/59
|
||||
[#60]: https://github.com/PHPCSStandards/composer-installer/issues/60
|
||||
[#61]: https://github.com/PHPCSStandards/composer-installer/pull/61
|
||||
[#62]: https://github.com/PHPCSStandards/composer-installer/pull/62
|
||||
[#63]: https://github.com/PHPCSStandards/composer-installer/issues/63
|
||||
[#66]: https://github.com/PHPCSStandards/composer-installer/pull/66
|
||||
[#67]: https://github.com/PHPCSStandards/composer-installer/pull/67
|
||||
[#68]: https://github.com/PHPCSStandards/composer-installer/pull/68
|
||||
[#69]: https://github.com/PHPCSStandards/composer-installer/pull/69
|
||||
[#70]: https://github.com/PHPCSStandards/composer-installer/pull/70
|
||||
[#71]: https://github.com/PHPCSStandards/composer-installer/pull/71
|
||||
[#72]: https://github.com/PHPCSStandards/composer-installer/pull/72
|
||||
[#73]: https://github.com/PHPCSStandards/composer-installer/pull/73
|
||||
[#74]: https://github.com/PHPCSStandards/composer-installer/pull/74
|
||||
[#75]: https://github.com/PHPCSStandards/composer-installer/pull/75
|
||||
[#76]: https://github.com/PHPCSStandards/composer-installer/pull/76
|
||||
[#77]: https://github.com/PHPCSStandards/composer-installer/pull/77
|
||||
|
||||
|
||||
## [v0.4.4] - 2017-12-06
|
||||
|
||||
### Closed issues
|
||||
- PHP 7.2 compatibility issue [#43]
|
||||
|
||||
### Changed
|
||||
- Update Travis CI svg badge and link URLs [#42] ([@ntwb])
|
||||
- Add PHP 7.2 to Travis CI [#41] ([@ntwb])
|
||||
- Docs: Fix link to releases [#40] ([@GaryJones])
|
||||
|
||||
[#40]: https://github.com/PHPCSStandards/composer-installer/pull/40
|
||||
[#41]: https://github.com/PHPCSStandards/composer-installer/pull/41
|
||||
[#42]: https://github.com/PHPCSStandards/composer-installer/pull/42
|
||||
[#43]: https://github.com/PHPCSStandards/composer-installer/issues/43
|
||||
|
||||
|
||||
## [v0.4.3] - 2017-09-18
|
||||
|
||||
### Changed
|
||||
- CS: Add PHP 5.3 compatibility [#39] ([@GaryJones])
|
||||
- Local PHPCS [#38] ([@GaryJones])
|
||||
|
||||
[#38]: https://github.com/PHPCSStandards/composer-installer/pull/38
|
||||
[#39]: https://github.com/PHPCSStandards/composer-installer/pull/39
|
||||
|
||||
|
||||
## [v0.4.2] - 2017-08-16
|
||||
|
||||
### Changed
|
||||
- Docs: Rename example script [#35] ([@GaryJones])
|
||||
- Update README.md [#36] ([@jrfnl])
|
||||
- Documentation update. [#37] ([@frenck])
|
||||
|
||||
[#35]: https://github.com/PHPCSStandards/composer-installer/pull/35
|
||||
[#36]: https://github.com/PHPCSStandards/composer-installer/pull/36
|
||||
[#37]: https://github.com/PHPCSStandards/composer-installer/pull/37
|
||||
|
||||
|
||||
## [v0.4.1] - 2017-08-01
|
||||
|
||||
### Closed issues
|
||||
- Incorrect relative paths for WPCS [#33]
|
||||
|
||||
### Fixed
|
||||
- [#33]: Changes the way the installed_paths are set. [#34] ([@frenck])
|
||||
|
||||
[#33]: https://github.com/PHPCSStandards/composer-installer/issues/33
|
||||
[#34]: https://github.com/PHPCSStandards/composer-installer/pull/34
|
||||
|
||||
|
||||
## [v0.4.0] - 2017-05-11
|
||||
|
||||
### Closed issues
|
||||
- Add support for code standards in root of repository for PHP_CodeSniffer 3.x [#26]
|
||||
- Config codings styles in composer.json from project [#23]
|
||||
- Check the root package for sniffs to install [#20]
|
||||
- Document the ability to execute the main plugin functionality directly [#18]
|
||||
- Add a CHANGELOG.md [#17]
|
||||
- Install sniffs with relative paths in CodeSniffer.conf [#14]
|
||||
|
||||
### Added
|
||||
- Support for coding standard in the root repository for PHP_CodeSniffer v3.x [#30] ([@frenck])
|
||||
- Added support for having coding standards in the root package [#25] ([@frenck])
|
||||
|
||||
### Changed
|
||||
- Local projects uses relative paths to their coding standards [#28] ([@frenck])
|
||||
- Docs: Updated README. [#31] ([@frenck])
|
||||
- Docs: Adds reference to calling the script directly in the README. [#29] ([@Potherca])
|
||||
- Adds Travis-CI configuration file. [#27] ([@Potherca])
|
||||
|
||||
|
||||
[#14]: https://github.com/PHPCSStandards/composer-installer/issues/14
|
||||
[#17]: https://github.com/PHPCSStandards/composer-installer/issues/17
|
||||
[#18]: https://github.com/PHPCSStandards/composer-installer/issues/18
|
||||
[#20]: https://github.com/PHPCSStandards/composer-installer/issues/20
|
||||
[#23]: https://github.com/PHPCSStandards/composer-installer/issues/23
|
||||
[#25]: https://github.com/PHPCSStandards/composer-installer/pull/25
|
||||
[#26]: https://github.com/PHPCSStandards/composer-installer/issues/26
|
||||
[#27]: https://github.com/PHPCSStandards/composer-installer/pull/27
|
||||
[#28]: https://github.com/PHPCSStandards/composer-installer/pull/28
|
||||
[#29]: https://github.com/PHPCSStandards/composer-installer/pull/29
|
||||
[#31]: https://github.com/PHPCSStandards/composer-installer/pull/31
|
||||
|
||||
|
||||
## [v0.3.2] - 2017-03-29
|
||||
|
||||
### Closed issues
|
||||
- Coding Standard tries itself to install with installPath when it's the root package [#19]
|
||||
|
||||
### Changed
|
||||
- Improvements to the documentation [#22] ([@Potherca])
|
||||
- Added instanceof check to prevent root package from being installed [#21] ([@bastianschwarz])
|
||||
|
||||
### Fixed
|
||||
- [#13]: Incorrect coding standards search depth [#15] ([@frenck])
|
||||
|
||||
[#19]: https://github.com/PHPCSStandards/composer-installer/issues/19
|
||||
[#21]: https://github.com/PHPCSStandards/composer-installer/pull/21
|
||||
[#22]: https://github.com/PHPCSStandards/composer-installer/pull/22
|
||||
|
||||
|
||||
## [v0.3.1] - 2017-02-17
|
||||
|
||||
### Closed issues
|
||||
- Plugin not working correctly when sniffs install depth is equal to "1" [#13]
|
||||
- Create new stable release version to support wider use [#11]
|
||||
|
||||
### Fixed
|
||||
- [#13]: Incorrect coding standards search depth [#15] ([@frenck])
|
||||
|
||||
[#11]: https://github.com/PHPCSStandards/composer-installer/issues/11
|
||||
[#13]: https://github.com/PHPCSStandards/composer-installer/issues/13
|
||||
[#15]: https://github.com/PHPCSStandards/composer-installer/pull/15
|
||||
|
||||
|
||||
## [v0.3.0] - 2017-02-15
|
||||
|
||||
### Implemented enhancements
|
||||
- Install Plugin provides no feedback [#7]
|
||||
- Installing coding standards when executing Composer with --no-scripts [#4]
|
||||
- Github contribution templates [#10] ([@christopher-hopper])
|
||||
- Show config actions and a result as Console output [#8] ([@christopher-hopper])
|
||||
- Adds static function to call the Plugin::onDependenciesChangedEvent() method [#5] ([@Potherca])
|
||||
|
||||
### Added
|
||||
- Support existing standards packages with subfolders [#6] ([@christopher-hopper])
|
||||
|
||||
### Changed
|
||||
- Improved documentation [#12] ([@frenck])
|
||||
- Removal of lgtm.co [#3] ([@frenck])
|
||||
|
||||
[#3]: https://github.com/PHPCSStandards/composer-installer/pull/3
|
||||
[#4]: https://github.com/PHPCSStandards/composer-installer/issues/4
|
||||
[#5]: https://github.com/PHPCSStandards/composer-installer/pull/5
|
||||
[#6]: https://github.com/PHPCSStandards/composer-installer/pull/6
|
||||
[#7]: https://github.com/PHPCSStandards/composer-installer/issues/7
|
||||
[#8]: https://github.com/PHPCSStandards/composer-installer/pull/8
|
||||
[#10]: https://github.com/PHPCSStandards/composer-installer/pull/10
|
||||
[#12]: https://github.com/PHPCSStandards/composer-installer/pull/12
|
||||
|
||||
|
||||
## [v0.2.1] - 2016-11-01
|
||||
|
||||
Fixes an issue with having this plugin installed globally within composer, but using your global composer installation on a local repository without PHP_CodeSniffer installed.
|
||||
|
||||
### Fixed
|
||||
- Bugfix: Plugin fails when PHP_CodeSniffer is not installed [#2] ([@frenck])
|
||||
|
||||
[#2]: https://github.com/PHPCSStandards/composer-installer/pull/2
|
||||
|
||||
|
||||
## [v0.2.0] - 2016-11-01
|
||||
|
||||
For this version on, this installer no longer messes with the installation paths of composer libraries, but instead, it configures PHP_CodeSniffer to look into other directories for coding standards.
|
||||
|
||||
### Changed
|
||||
- PHPCS Configuration management [#1] ([@frenck])
|
||||
|
||||
[#1]: https://github.com/PHPCSStandards/composer-installer/pull/1
|
||||
|
||||
|
||||
## [v0.1.1] - 2016-10-24
|
||||
|
||||
### Changed
|
||||
- Standard name mapping improvements
|
||||
|
||||
|
||||
## v0.1.0 - 2016-10-23
|
||||
|
||||
First useable release.
|
||||
|
||||
[v1.2.0]: https://github.com/PHPCSStandards/composer-installer/compare/v1.1.2...v1.2.0
|
||||
[v1.1.2]: https://github.com/PHPCSStandards/composer-installer/compare/v1.1.1...v1.1.2
|
||||
[v1.1.1]: https://github.com/PHPCSStandards/composer-installer/compare/v1.1.0...v1.1.1
|
||||
[v1.1.0]: https://github.com/PHPCSStandards/composer-installer/compare/v1.0.0...v1.1.0
|
||||
[v1.0.0]: https://github.com/PHPCSStandards/composer-installer/compare/v0.7.2...v1.0.0
|
||||
[v0.7.2]: https://github.com/PHPCSStandards/composer-installer/compare/v0.7.1...v0.7.2
|
||||
[v0.7.1]: https://github.com/PHPCSStandards/composer-installer/compare/v0.7.0...v0.7.1
|
||||
[v0.7.0]: https://github.com/PHPCSStandards/composer-installer/compare/v0.6.2...v0.7.0
|
||||
[v0.6.2]: https://github.com/PHPCSStandards/composer-installer/compare/v0.6.1...v0.6.2
|
||||
[v0.6.1]: https://github.com/PHPCSStandards/composer-installer/compare/v0.6.0...v0.6.1
|
||||
[v0.6.0]: https://github.com/PHPCSStandards/composer-installer/compare/v0.5.0...v0.6.0
|
||||
[v0.5.0]: https://github.com/PHPCSStandards/composer-installer/compare/v0.4.4...v0.5.0
|
||||
[v0.4.4]: https://github.com/PHPCSStandards/composer-installer/compare/v0.4.3...v0.4.4
|
||||
[v0.4.3]: https://github.com/PHPCSStandards/composer-installer/compare/v0.4.2...v0.4.3
|
||||
[v0.4.2]: https://github.com/PHPCSStandards/composer-installer/compare/v0.4.1...v0.4.2
|
||||
[v0.4.1]: https://github.com/PHPCSStandards/composer-installer/compare/v0.4.0...v0.4.1
|
||||
[v0.4.0]: https://github.com/PHPCSStandards/composer-installer/compare/v0.3.2...v0.4.0
|
||||
[v0.3.2]: https://github.com/PHPCSStandards/composer-installer/compare/v0.3.1...v0.3.2
|
||||
[v0.3.1]: https://github.com/PHPCSStandards/composer-installer/compare/v0.3.0...v0.3.1
|
||||
[v0.3.0]: https://github.com/PHPCSStandards/composer-installer/compare/v0.2.1...v0.3.0
|
||||
[v0.2.1]: https://github.com/PHPCSStandards/composer-installer/compare/v0.2.0...v0.2.1
|
||||
[v0.2.0]: https://github.com/PHPCSStandards/composer-installer/compare/v0.1.1...v0.2.0
|
||||
[v0.1.1]: https://github.com/PHPCSStandards/composer-installer/compare/v0.1.0...v0.1.1
|
||||
|
||||
[PHP_CodeSniffer]: https://github.com/PHPCSStandards/PHP_CodeSniffer
|
||||
|
||||
[@bastianschwarz]: https://github.com/bastianschwarz
|
||||
[@BrianHenryIE]: https://github.com/BrianHenryIE
|
||||
[@christopher-hopper]: https://github.com/christopher-hopper
|
||||
[@fredden]: https://github.com/fredden
|
||||
[@frenck]: https://github.com/frenck
|
||||
[@gapple]: https://github.com/gapple
|
||||
[@GaryJones]: https://github.com/GaryJones
|
||||
[@GrahamCampbell]: https://github.com/GrahamCampbell
|
||||
[@jrfnl]: https://github.com/jrfnl
|
||||
[@kevinfodness]: https://github.com/kevinfodness
|
||||
[@mjrider]: https://github.com/mjrider
|
||||
[@ntwb]: https://github.com/ntwb
|
||||
[@paras-malhotra]: https://github.com/paras-malhotra
|
||||
[@Potherca]: https://github.com/Potherca
|
||||
[@Seldaek]: https://github.com/Seldaek
|
||||
[@SplotyCode]: https://github.com/SplotyCode
|
||||
[@TravisCarden]: https://github.com/TravisCarden
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2016-2022 Dealerdirect B.V. and contributors
|
||||
Copyright (c) 2022 PHPCSStandards and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -1,278 +0,0 @@
|
|||
# PHP_CodeSniffer Standards Composer Installer Plugin
|
||||
|
||||
![Last Commit][last-updated-shield]
|
||||
![Awesome][awesome-shield]
|
||||
[![License][license-shield]](LICENSE.md)
|
||||
|
||||
[![Tests][ghactionstest-shield]][ghactions]
|
||||
[![Latest Version on Packagist][packagist-version-shield]][packagist-version]
|
||||
[![Packagist][packagist-shield]][packagist]
|
||||
|
||||
[![Contributor Covenant][code-of-conduct-shield]][code-of-conduct]
|
||||
|
||||
This composer installer plugin makes installation of [PHP_CodeSniffer][codesniffer] coding standards (rulesets) straight-forward.
|
||||
|
||||
No more symbolic linking of directories, checking out repositories on specific locations or manually changing the `phpcs` configuration.
|
||||
|
||||
## Usage
|
||||
|
||||
Installation can be done with [Composer][composer], by requiring this package as a development dependency:
|
||||
|
||||
```bash
|
||||
composer require --dev dealerdirect/phpcodesniffer-composer-installer:"^1.0"
|
||||
```
|
||||
|
||||
Since Composer 2.2, Composer will [ask for your permission](https://blog.packagist.com/composer-2-2/#more-secure-plugin-execution) to allow this plugin to execute code. For this plugin to be functional, permission needs to be granted.
|
||||
|
||||
When permission has been granted, the following snippet will automatically be added to your `composer.json` file by Composer:
|
||||
```json
|
||||
{
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can safely add the permission flag (to avoid Composer needing to ask), by running:
|
||||
```bash
|
||||
composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
|
||||
```
|
||||
|
||||
That's it.
|
||||
|
||||
### Compatibility
|
||||
|
||||
This plugin is compatible with:
|
||||
|
||||
- PHP **5.4+**, **7.x**, and **8.x** (Support for PHP v8 is available since [`v0.7.0`][v0.7])
|
||||
- [Composer][composer] **2.2+** (Support for Composer v2 is available since [`v0.7.0`][v0.7]; support for Composer < 2.2 was dropped in [`v1.1.0`][v1.1])
|
||||
- [PHP_CodeSniffer][codesniffer] **3.x** and **4.x**(Support for PHP_CodeSniffer v4 is available since [`v0.7.0`][v0.7], support for PHP_CodeSniffer v2 was dropped in [`v1.2.0`][v1.2])
|
||||
|
||||
### How it works
|
||||
|
||||
Basically, this plugin executes the following steps:
|
||||
|
||||
- This plugin searches for [`phpcodesniffer-standard` packages][] in all of your currently installed Composer packages.
|
||||
- Matching packages and the project itself are scanned for PHP_CodeSniffer rulesets.
|
||||
- The plugin will call PHP_CodeSniffer and configure the `installed_paths` option.
|
||||
|
||||
### Example project
|
||||
|
||||
The following is an example Composer project and has included
|
||||
multiple `phpcodesniffer-standard` packages.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "example/project",
|
||||
"description": "Just an example project",
|
||||
"type": "project",
|
||||
"require": {},
|
||||
"require-dev": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "*",
|
||||
"phpcompatibility/php-compatibility": "*",
|
||||
"wp-coding-standards/wpcs": "*"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After running `composer install` PHP_CodeSniffer just works:
|
||||
|
||||
```bash
|
||||
$ ./vendor/bin/phpcs -i
|
||||
The installed coding standards are PEAR, PSR1, PSR2, PSR12, Squiz, Zend, PHPCompatibility, Modernize,
|
||||
NormalizedArrays, Universal, PHPCSUtils, WordPress, WordPress-Core, WordPress-Docs and WordPress-Extra
|
||||
```
|
||||
|
||||
### Calling the plugin directly
|
||||
|
||||
In some circumstances, it is desirable to call this plugin's functionality
|
||||
directly. For instance, during development or in [CI][definition-ci] environments.
|
||||
|
||||
As the plugin requires Composer to work, direct calls need to be wired through a
|
||||
project's `composer.json`.
|
||||
|
||||
This is done by adding a call to the `Plugin::run` function in the `script`
|
||||
section of the `composer.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"install-codestandards": [
|
||||
"PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The command can then be called using `composer run-script install-codestandards` or
|
||||
referenced from other script configurations, as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"install-codestandards": [
|
||||
"PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run"
|
||||
],
|
||||
"post-install-cmd": [
|
||||
"@install-codestandards"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For more details about Composer scripts, please refer to [the section on scripts
|
||||
in the Composer manual][composer-manual-scripts].
|
||||
|
||||
### Changing the Coding Standards search depth
|
||||
|
||||
By default, this plugin searches up for Coding Standards up to three directories
|
||||
deep. In most cases, this should be sufficient. However, this plugin allows
|
||||
you to customize the search depth setting if needed.
|
||||
|
||||
```json
|
||||
{
|
||||
"extra": {
|
||||
"phpcodesniffer-search-depth": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Caveats
|
||||
|
||||
When this plugin is installed globally, composer will load the _global_ plugin rather
|
||||
than the one from the local repository. Despite [this behavior being documented
|
||||
in the composer manual][using-composer-plugins], it could potentially confuse
|
||||
as another version of the plugin could be run and not the one specified by the project.
|
||||
|
||||
## Developing Coding Standards
|
||||
|
||||
Coding standard can be developed normally, as documented by [PHP_CodeSniffer][codesniffer], in the [Coding Standard Tutorial][tutorial].
|
||||
|
||||
Create a composer package of your coding standard by adding a `composer.json` file.
|
||||
|
||||
```json
|
||||
{
|
||||
"name" : "acme/phpcodesniffer-our-standards",
|
||||
"description" : "Package contains all coding standards of the Acme company",
|
||||
"require" : {
|
||||
"php" : ">=5.4.0",
|
||||
"squizlabs/php_codesniffer" : "^3.13"
|
||||
},
|
||||
"type" : "phpcodesniffer-standard"
|
||||
}
|
||||
```
|
||||
|
||||
Requirements:
|
||||
* The repository may contain one or more standards.
|
||||
* Each standard can have a separate directory no deeper than 3 levels from the repository root.
|
||||
* The package `type` must be `phpcodesniffer-standard`. Without this, the plugin will not trigger.
|
||||
|
||||
### Requiring the plugin from within your coding standard
|
||||
|
||||
If your coding standard itself depends on additional external PHPCS standards, this plugin can
|
||||
make life easier on your end-users by taking care of the installation of all standards - yours
|
||||
and your dependencies - for them.
|
||||
|
||||
This can help reduce the number of support questions about setting the `installed_paths`, as well
|
||||
as simplify your standard's installation instructions.
|
||||
|
||||
For this to work, make sure your external standard adds this plugin to the `composer.json` config
|
||||
via `require`, **not** `require-dev`.
|
||||
|
||||
> :warning: Your end-user may already `require-dev` this plugin and/or other external standards used
|
||||
> by your end-users may require this plugin as well.
|
||||
>
|
||||
> To prevent your end-users getting into "_dependency hell_", make sure to make the version requirement
|
||||
> for this plugin flexible.
|
||||
>
|
||||
> Remember that [Composer treats unstable minors as majors][composer-manual-caret] and will not be able to resolve
|
||||
> one config requiring this plugin at version `^0.7`, while another requires it at version `^1.0`.
|
||||
> Either allow multiple minors or use `*` as the version requirement.
|
||||
>
|
||||
> Some examples of flexible requirements which can be used:
|
||||
> ```bash
|
||||
> composer require dealerdirect/phpcodesniffer-composer-installer:"*"
|
||||
> composer require dealerdirect/phpcodesniffer-composer-installer:"^0.4.1 || ^0.5 || ^0.6 || ^0.7 || ^1.0"
|
||||
> ```
|
||||
|
||||
## Contributing
|
||||
|
||||
This is an active open-source project. We are always open to people who want to
|
||||
use the code or contribute to it.
|
||||
|
||||
We've set up a separate document for our [contribution guidelines][contributing-guidelines].
|
||||
|
||||
Thank you for being involved! :heart_eyes:
|
||||
|
||||
## Authors & contributors
|
||||
|
||||
The original idea and setup of this repository is by [Franck Nijhof][frenck], employee @ Dealerdirect.
|
||||
|
||||
For a full list of all authors and/or contributors, check [the contributors page][contributors].
|
||||
|
||||
## Funding
|
||||
|
||||
This project is included in the projects supported via the [PHP_CodeSniffer Open Collective][phpcs-open-collective].
|
||||
|
||||
If you use this plugin, financial contributions to the Open Collective are encouraged and appreciated.
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2022 Dealerdirect B.V. and contributors
|
||||
Copyright (c) 2022- PHPCSStandards and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
[awesome-shield]: https://img.shields.io/badge/awesome%3F-yes-brightgreen.svg
|
||||
[code-of-conduct-shield]: https://img.shields.io/badge/Contributor%20Covenant-v2.0-ff69b4.svg
|
||||
[code-of-conduct]: CODE_OF_CONDUCT.md
|
||||
[codesniffer]: https://github.com/PHPCSStandards/PHP_CodeSniffer
|
||||
[composer-manual-scripts]: https://getcomposer.org/doc/articles/scripts.md
|
||||
[composer-manual-caret]: https://getcomposer.org/doc/articles/versions.md#caret-version-range-
|
||||
[composer]: https://getcomposer.org/
|
||||
[contributing-guidelines]: CONTRIBUTING.md
|
||||
[contributors]: https://github.com/PHPCSStandards/composer-installer/graphs/contributors
|
||||
[definition-ci]: https://en.wikipedia.org/wiki/Continuous_integration
|
||||
[frenck]: https://github.com/frenck
|
||||
[last-updated-shield]: https://img.shields.io/github/last-commit/PHPCSStandards/composer-installer.svg
|
||||
[license-shield]: https://img.shields.io/github/license/PHPCSStandards/composer-installer.svg
|
||||
[packagist-shield]: https://img.shields.io/packagist/dt/dealerdirect/phpcodesniffer-composer-installer.svg
|
||||
[packagist-version-shield]: https://img.shields.io/packagist/v/dealerdirect/phpcodesniffer-composer-installer.svg
|
||||
[packagist-version]: https://packagist.org/packages/dealerdirect/phpcodesniffer-composer-installer
|
||||
[packagist]: https://packagist.org/packages/dealerdirect/phpcodesniffer-composer-installer
|
||||
[`phpcodesniffer-standard` packages]: https://packagist.org/explore/?type=phpcodesniffer-standard
|
||||
[phpcs-open-collective]: https://opencollective.com/php_codesniffer
|
||||
[scrutinizer-shield]: https://img.shields.io/scrutinizer/g/dealerdirect/phpcodesniffer-composer-installer.svg
|
||||
[scrutinizer]: https://scrutinizer-ci.com/g/dealerdirect/phpcodesniffer-composer-installer/
|
||||
[ghactionstest-shield]: https://github.com/PHPCSStandards/composer-installer/actions/workflows/integrationtest.yml/badge.svg
|
||||
[ghactions]: https://github.com/PHPCSStandards/composer-installer/actions/workflows/integrationtest.yml
|
||||
[tutorial]: https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/Coding-Standard-Tutorial
|
||||
[using-composer-plugins]: https://getcomposer.org/doc/articles/plugins.md#using-plugins
|
||||
[v0.7]: https://github.com/PHPCSStandards/composer-installer/releases/tag/v0.7.0
|
||||
[v1.1]: https://github.com/PHPCSStandards/composer-installer/releases/tag/v1.1.0
|
||||
[v1.2]: https://github.com/PHPCSStandards/composer-installer/releases/tag/v1.2.0
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
{
|
||||
"name": "dealerdirect/phpcodesniffer-composer-installer",
|
||||
"description": "PHP_CodeSniffer Standards Composer Installer Plugin",
|
||||
"type": "composer-plugin",
|
||||
"keywords": [
|
||||
"composer", "installer", "plugin",
|
||||
"phpcs", "phpcbf", "codesniffer", "phpcodesniffer", "php_codesniffer",
|
||||
"standard", "standards", "style guide", "stylecheck",
|
||||
"qa", "quality", "code quality", "tests"
|
||||
],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Franck Nijhof",
|
||||
"email": "opensource@frenck.dev",
|
||||
"homepage": "https://frenck.dev",
|
||||
"role": "Open source developer"
|
||||
},
|
||||
{
|
||||
"name" : "Contributors",
|
||||
"homepage" : "https://github.com/PHPCSStandards/composer-installer/graphs/contributors"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHPCSStandards/composer-installer/issues",
|
||||
"source": "https://github.com/PHPCSStandards/composer-installer",
|
||||
"security": "https://github.com/PHPCSStandards/composer-installer/security/policy"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4",
|
||||
"composer-plugin-api": "^2.2",
|
||||
"squizlabs/php_codesniffer": "^3.1.0 || ^4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-json": "*",
|
||||
"ext-zip": "*",
|
||||
"composer/composer": "^2.2",
|
||||
"phpcompatibility/php-compatibility": "^9.0 || ^10.0.0@dev",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.4.0",
|
||||
"yoast/phpunit-polyfills": "^1.0"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"lock": false
|
||||
},
|
||||
"extra": {
|
||||
"class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin"
|
||||
},
|
||||
"scripts": {
|
||||
"install-codestandards": [
|
||||
"PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run"
|
||||
],
|
||||
"lint": [
|
||||
"@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --show-deprecated --exclude vendor --exclude .git"
|
||||
],
|
||||
"test": [
|
||||
"@php ./vendor/phpunit/phpunit/phpunit --no-coverage"
|
||||
],
|
||||
"coverage": [
|
||||
"@php ./vendor/phpunit/phpunit/phpunit"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,635 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Dealerdirect PHP_CodeSniffer Standards
|
||||
* Composer Installer Plugin package.
|
||||
*
|
||||
* @copyright 2016-2022 Dealerdirect B.V.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace PHPCSStandards\Composer\Plugin\Installers\PHPCodeSniffer;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\EventDispatcher\EventSubscriberInterface;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\AliasPackage;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Package\RootPackageInterface;
|
||||
use Composer\Plugin\PluginInterface;
|
||||
use Composer\Script\Event;
|
||||
use Composer\Script\ScriptEvents;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\Process\Exception\LogicException;
|
||||
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||
use Symfony\Component\Process\Exception\RuntimeException;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
|
||||
/**
|
||||
* PHP_CodeSniffer standard installation manager.
|
||||
*
|
||||
* @author Franck Nijhof <franck.nijhof@dealerdirect.com>
|
||||
*/
|
||||
class Plugin implements PluginInterface, EventSubscriberInterface
|
||||
{
|
||||
const KEY_MAX_DEPTH = 'phpcodesniffer-search-depth';
|
||||
|
||||
const MESSAGE_ERROR_WRONG_MAX_DEPTH =
|
||||
'The value of "%s" (in the composer.json "extra".section) must be an integer larger than %d, %s given.';
|
||||
|
||||
const MESSAGE_NOT_INSTALLED = 'PHPCodeSniffer is not installed';
|
||||
const MESSAGE_NOTHING_TO_INSTALL = 'No PHPCS standards to install or update';
|
||||
const MESSAGE_PLUGIN_UNINSTALLED = 'PHPCodeSniffer Composer Installer is uninstalled';
|
||||
const MESSAGE_RUNNING_INSTALLER = 'Running PHPCodeSniffer Composer Installer';
|
||||
|
||||
const PACKAGE_NAME = 'squizlabs/php_codesniffer';
|
||||
const PACKAGE_TYPE = 'phpcodesniffer-standard';
|
||||
|
||||
const PHPCS_CONFIG_REGEX = '`%s:[^\r\n]+`';
|
||||
const PHPCS_CONFIG_KEY = 'installed_paths';
|
||||
|
||||
const PLUGIN_NAME = 'dealerdirect/phpcodesniffer-composer-installer';
|
||||
|
||||
/**
|
||||
* @var Composer
|
||||
*/
|
||||
private $composer;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $cwd;
|
||||
|
||||
/**
|
||||
* @var Filesystem
|
||||
*/
|
||||
private $filesystem;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $installedPaths;
|
||||
|
||||
/**
|
||||
* @var IOInterface
|
||||
*/
|
||||
private $io;
|
||||
|
||||
/**
|
||||
* @var ProcessExecutor
|
||||
*/
|
||||
private $processExecutor;
|
||||
|
||||
/**
|
||||
* Triggers the plugin's main functionality.
|
||||
*
|
||||
* Makes it possible to run the plugin as a custom command.
|
||||
*
|
||||
* @param Event $event
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \RuntimeException
|
||||
* @throws LogicException
|
||||
* @throws ProcessFailedException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function run(Event $event)
|
||||
{
|
||||
$io = $event->getIO();
|
||||
$composer = $event->getComposer();
|
||||
|
||||
$instance = new static();
|
||||
|
||||
$instance->io = $io;
|
||||
$instance->composer = $composer;
|
||||
$instance->init();
|
||||
$instance->onDependenciesChangedEvent();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
* @throws LogicException
|
||||
* @throws ProcessFailedException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function activate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$this->composer = $composer;
|
||||
$this->io = $io;
|
||||
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function deactivate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function uninstall(Composer $composer, IOInterface $io)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the plugin so it's main functionality can be run.
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
* @throws LogicException
|
||||
* @throws ProcessFailedException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function init()
|
||||
{
|
||||
$this->cwd = getcwd();
|
||||
$this->installedPaths = array();
|
||||
|
||||
$this->processExecutor = new ProcessExecutor($this->io);
|
||||
$this->filesystem = new Filesystem($this->processExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return array(
|
||||
ScriptEvents::POST_INSTALL_CMD => array(
|
||||
array('onDependenciesChangedEvent', 0),
|
||||
),
|
||||
ScriptEvents::POST_UPDATE_CMD => array(
|
||||
array('onDependenciesChangedEvent', 0),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for post install and post update events.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws LogicException
|
||||
* @throws ProcessFailedException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function onDependenciesChangedEvent()
|
||||
{
|
||||
$io = $this->io;
|
||||
$isVerbose = $io->isVerbose();
|
||||
$exitCode = 0;
|
||||
|
||||
if ($isVerbose) {
|
||||
$io->write(sprintf('<info>%s</info>', self::MESSAGE_RUNNING_INSTALLER));
|
||||
}
|
||||
|
||||
if ($this->isPHPCodeSnifferInstalled() === true) {
|
||||
$this->loadInstalledPaths();
|
||||
$installPathCleaned = $this->cleanInstalledPaths();
|
||||
$installPathUpdated = $this->updateInstalledPaths();
|
||||
|
||||
if ($installPathCleaned === true || $installPathUpdated === true) {
|
||||
$exitCode = $this->saveInstalledPaths();
|
||||
} elseif ($isVerbose) {
|
||||
$io->write(sprintf('<info>%s</info>', self::MESSAGE_NOTHING_TO_INSTALL));
|
||||
}
|
||||
} else {
|
||||
$pluginPackage = $this
|
||||
->composer
|
||||
->getRepositoryManager()
|
||||
->getLocalRepository()
|
||||
->findPackages(self::PLUGIN_NAME)
|
||||
;
|
||||
|
||||
$isPluginUninstalled = count($pluginPackage) === 0;
|
||||
|
||||
if ($isPluginUninstalled) {
|
||||
if ($isVerbose) {
|
||||
$io->write(sprintf('<info>%s</info>', self::MESSAGE_PLUGIN_UNINSTALLED));
|
||||
}
|
||||
} else {
|
||||
$exitCode = 1;
|
||||
if ($isVerbose) {
|
||||
$io->write(sprintf('<error>%s</error>', self::MESSAGE_NOT_INSTALLED));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $exitCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all paths from PHP_CodeSniffer into an array.
|
||||
*
|
||||
* @throws LogicException
|
||||
* @throws ProcessFailedException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function loadInstalledPaths()
|
||||
{
|
||||
if ($this->isPHPCodeSnifferInstalled() === true) {
|
||||
$this->processExecutor->execute(
|
||||
$this->getPhpcsCommand() . ' --config-show',
|
||||
$output,
|
||||
$this->getPHPCodeSnifferInstallPath()
|
||||
);
|
||||
|
||||
$regex = sprintf(self::PHPCS_CONFIG_REGEX, self::PHPCS_CONFIG_KEY);
|
||||
if (preg_match($regex, $output, $match) === 1) {
|
||||
$phpcsInstalledPaths = str_replace(self::PHPCS_CONFIG_KEY . ': ', '', $match[0]);
|
||||
$phpcsInstalledPaths = trim($phpcsInstalledPaths);
|
||||
|
||||
if ($phpcsInstalledPaths !== '') {
|
||||
$this->installedPaths = explode(',', $phpcsInstalledPaths);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save all coding standard paths back into PHP_CodeSniffer
|
||||
*
|
||||
* @throws LogicException
|
||||
* @throws ProcessFailedException
|
||||
* @throws RuntimeException
|
||||
*
|
||||
* @return int Exit code. 0 for success, 1 or higher for failure.
|
||||
*/
|
||||
private function saveInstalledPaths()
|
||||
{
|
||||
// Check if we found installed paths to set.
|
||||
if (count($this->installedPaths) !== 0) {
|
||||
sort($this->installedPaths);
|
||||
$paths = implode(',', $this->installedPaths);
|
||||
$arguments = array('--config-set', self::PHPCS_CONFIG_KEY, $paths);
|
||||
$configMessage = sprintf(
|
||||
'PHP CodeSniffer Config <info>%s</info> <comment>set to</comment> <info>%s</info>',
|
||||
self::PHPCS_CONFIG_KEY,
|
||||
$paths
|
||||
);
|
||||
} else {
|
||||
// Delete the installed paths if none were found.
|
||||
$arguments = array('--config-delete', self::PHPCS_CONFIG_KEY);
|
||||
$configMessage = sprintf(
|
||||
'PHP CodeSniffer Config <info>%s</info> <comment>delete</comment>',
|
||||
self::PHPCS_CONFIG_KEY
|
||||
);
|
||||
}
|
||||
|
||||
// Prepare message in case of failure
|
||||
$failMessage = sprintf(
|
||||
'Failed to set PHP CodeSniffer <info>%s</info> Config',
|
||||
self::PHPCS_CONFIG_KEY
|
||||
);
|
||||
|
||||
// Okay, lets rock!
|
||||
$command = vsprintf(
|
||||
'%s %s',
|
||||
array(
|
||||
'phpcs command' => $this->getPhpcsCommand(),
|
||||
'arguments' => implode(' ', $arguments),
|
||||
)
|
||||
);
|
||||
|
||||
$exitCode = $this->processExecutor->execute($command, $configResult, $this->getPHPCodeSnifferInstallPath());
|
||||
if ($exitCode === 0) {
|
||||
$exitCode = $this->verifySaveSuccess();
|
||||
}
|
||||
|
||||
if ($exitCode === 0) {
|
||||
$this->io->write($configMessage);
|
||||
} else {
|
||||
$this->io->write($failMessage);
|
||||
}
|
||||
|
||||
if ($this->io->isVerbose() && !empty($configResult)) {
|
||||
$this->io->write(sprintf('<info>%s</info>', $configResult));
|
||||
}
|
||||
|
||||
return $exitCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the paths which were expected to be saved, have been.
|
||||
*
|
||||
* @return int Exit code. 0 for success, 1 for failure.
|
||||
*/
|
||||
private function verifySaveSuccess()
|
||||
{
|
||||
$exitCode = 1;
|
||||
$expectedPaths = $this->installedPaths;
|
||||
|
||||
// Request the currently set installed paths after the save.
|
||||
$this->loadInstalledPaths();
|
||||
|
||||
$registeredPaths = array_intersect($this->installedPaths, $expectedPaths);
|
||||
$registeredCount = count($registeredPaths);
|
||||
$expectedCount = count($expectedPaths);
|
||||
|
||||
if ($expectedCount === $registeredCount) {
|
||||
$exitCode = 0;
|
||||
}
|
||||
|
||||
if ($exitCode === 1 && $this->io->isVerbose()) {
|
||||
$verificationMessage = sprintf(
|
||||
"Paths to external standards found by the plugin: <info>%s</info>\n"
|
||||
. 'Actual paths registered with PHPCS: <info>%s</info>',
|
||||
implode(', ', $expectedPaths),
|
||||
implode(', ', $this->installedPaths)
|
||||
);
|
||||
$this->io->write($verificationMessage);
|
||||
}
|
||||
|
||||
return $exitCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the command to call PHPCS.
|
||||
*/
|
||||
protected function getPhpcsCommand()
|
||||
{
|
||||
return vsprintf(
|
||||
'%s %s',
|
||||
array(
|
||||
'php executable' => $this->getPhpExecCommand(),
|
||||
'phpcs executable' => './bin/phpcs',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the current PHP version being used.
|
||||
*
|
||||
* Duplicate of the same in the EventDispatcher class in Composer itself.
|
||||
*/
|
||||
protected function getPhpExecCommand()
|
||||
{
|
||||
$finder = new PhpExecutableFinder();
|
||||
|
||||
$phpPath = $finder->find(false);
|
||||
|
||||
if ($phpPath === false) {
|
||||
throw new \RuntimeException('Failed to locate PHP binary to execute ' . $phpPath);
|
||||
}
|
||||
|
||||
$phpArgs = $finder->findArguments();
|
||||
$phpArgs = $phpArgs
|
||||
? ' ' . implode(' ', $phpArgs)
|
||||
: ''
|
||||
;
|
||||
|
||||
$command = ProcessExecutor::escape($phpPath) .
|
||||
$phpArgs .
|
||||
' -d allow_url_fopen=' . ProcessExecutor::escape(ini_get('allow_url_fopen')) .
|
||||
' -d disable_functions=' . ProcessExecutor::escape(ini_get('disable_functions')) .
|
||||
' -d memory_limit=' . ProcessExecutor::escape(ini_get('memory_limit'))
|
||||
;
|
||||
|
||||
return $command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate trough all known paths and check if they are still valid.
|
||||
*
|
||||
* If path does not exists, is not an directory or isn't readable, the path
|
||||
* is removed from the list.
|
||||
*
|
||||
* @return bool True if changes where made, false otherwise
|
||||
*/
|
||||
private function cleanInstalledPaths()
|
||||
{
|
||||
$changes = false;
|
||||
foreach ($this->installedPaths as $key => $path) {
|
||||
// This might be a relative path as well
|
||||
$alternativePath = realpath($this->getPHPCodeSnifferInstallPath() . \DIRECTORY_SEPARATOR . $path);
|
||||
|
||||
if (
|
||||
(is_dir($path) === false || is_readable($path) === false) &&
|
||||
(
|
||||
$alternativePath === false ||
|
||||
is_dir($alternativePath) === false ||
|
||||
is_readable($alternativePath) === false
|
||||
)
|
||||
) {
|
||||
unset($this->installedPaths[$key]);
|
||||
$changes = true;
|
||||
}
|
||||
}
|
||||
return $changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check all installed packages (including the root package) against
|
||||
* the installed paths from PHP_CodeSniffer and add the missing ones.
|
||||
*
|
||||
* @return bool True if changes where made, false otherwise
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function updateInstalledPaths()
|
||||
{
|
||||
$changes = false;
|
||||
$searchPaths = array();
|
||||
|
||||
// Add root package only if it has the expected package type.
|
||||
if (
|
||||
$this->composer->getPackage() instanceof RootPackageInterface
|
||||
&& $this->composer->getPackage()->getType() === self::PACKAGE_TYPE
|
||||
) {
|
||||
$searchPaths[] = $this->cwd;
|
||||
}
|
||||
|
||||
$codingStandardPackages = $this->getPHPCodingStandardPackages();
|
||||
foreach ($codingStandardPackages as $package) {
|
||||
$installPath = $this->composer->getInstallationManager()->getInstallPath($package);
|
||||
if ($this->filesystem->isAbsolutePath($installPath) === false) {
|
||||
$installPath = $this->filesystem->normalizePath(
|
||||
$this->cwd . \DIRECTORY_SEPARATOR . $installPath
|
||||
);
|
||||
}
|
||||
$searchPaths[] = $installPath;
|
||||
}
|
||||
|
||||
// Nothing to do.
|
||||
if ($searchPaths === array()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$finder = new Finder();
|
||||
$finder->files()
|
||||
->depth('<= ' . $this->getMaxDepth())
|
||||
->depth('>= ' . $this->getMinDepth())
|
||||
->ignoreUnreadableDirs()
|
||||
->ignoreVCS(true)
|
||||
->in($searchPaths)
|
||||
->name('ruleset.xml');
|
||||
|
||||
// Process each found possible ruleset.
|
||||
foreach ($finder as $ruleset) {
|
||||
$standardsPath = $ruleset->getPath();
|
||||
|
||||
// Pick the directory above the directory containing the standard, unless this is the project root.
|
||||
if ($standardsPath !== $this->cwd) {
|
||||
$standardsPath = dirname($standardsPath);
|
||||
}
|
||||
|
||||
// Use relative paths for local project repositories.
|
||||
if ($this->isRunningGlobally() === false) {
|
||||
$standardsPath = $this->filesystem->findShortestPath(
|
||||
$this->getPHPCodeSnifferInstallPath(),
|
||||
$standardsPath,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// De-duplicate and add when directory is not configured.
|
||||
if (in_array($standardsPath, $this->installedPaths, true) === false) {
|
||||
$this->installedPaths[] = $standardsPath;
|
||||
$changes = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through Composers' local repository looking for valid Coding
|
||||
* Standard packages.
|
||||
*
|
||||
* @return array Composer packages containing coding standard(s)
|
||||
*/
|
||||
private function getPHPCodingStandardPackages()
|
||||
{
|
||||
$codingStandardPackages = array_filter(
|
||||
$this->composer->getRepositoryManager()->getLocalRepository()->getPackages(),
|
||||
function (PackageInterface $package) {
|
||||
if ($package instanceof AliasPackage) {
|
||||
return false;
|
||||
}
|
||||
return $package->getType() === Plugin::PACKAGE_TYPE;
|
||||
}
|
||||
);
|
||||
|
||||
return $codingStandardPackages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for the installed PHP_CodeSniffer Composer package
|
||||
*
|
||||
* @param null|string|\Composer\Semver\Constraint\ConstraintInterface $versionConstraint to match against
|
||||
*
|
||||
* @return PackageInterface|null
|
||||
*/
|
||||
private function getPHPCodeSnifferPackage($versionConstraint = null)
|
||||
{
|
||||
$packages = $this
|
||||
->composer
|
||||
->getRepositoryManager()
|
||||
->getLocalRepository()
|
||||
->findPackages(self::PACKAGE_NAME, $versionConstraint);
|
||||
|
||||
return array_shift($packages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the PHP_CodeSniffer package installation location
|
||||
*
|
||||
* {@internal Do NOT try to modernize via the Composer 2.2 API (`InstalledVersions::getInstallPath()`).
|
||||
* Doing so doesn't play nice with other plugins.
|
||||
* {@link https://github.com/PHPCSStandards/composer-installer/issues/239}}
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getPHPCodeSnifferInstallPath()
|
||||
{
|
||||
return $this->composer->getInstallationManager()->getInstallPath($this->getPHPCodeSnifferPackage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple check if PHP_CodeSniffer is installed.
|
||||
*
|
||||
* {@internal Do NOT try to modernize via the Composer 2.2 API (`InstalledVersions::isInstalled()`).
|
||||
* Doing so doesn't play nice with integrations calling the Composer EventDispatcher programmatically.
|
||||
* {@link https://github.com/PHPCSStandards/composer-installer/issues/247}}
|
||||
*
|
||||
* @param null|string|\Composer\Semver\Constraint\ConstraintInterface $versionConstraint to match against
|
||||
*
|
||||
* @return bool Whether PHP_CodeSniffer is installed
|
||||
*/
|
||||
private function isPHPCodeSnifferInstalled($versionConstraint = null)
|
||||
{
|
||||
return ($this->getPHPCodeSnifferPackage($versionConstraint) !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if composer is running "global"
|
||||
* This check kinda dirty, but it is the "Composer Way"
|
||||
*
|
||||
* @return bool Whether Composer is running "globally"
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function isRunningGlobally()
|
||||
{
|
||||
return ($this->composer->getConfig()->get('home') === $this->cwd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the maximum search depth when searching for Coding Standards.
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
private function getMaxDepth()
|
||||
{
|
||||
$maxDepth = 3;
|
||||
|
||||
$extra = $this->composer->getPackage()->getExtra();
|
||||
|
||||
if (array_key_exists(self::KEY_MAX_DEPTH, $extra)) {
|
||||
$maxDepth = $extra[self::KEY_MAX_DEPTH];
|
||||
$minDepth = $this->getMinDepth();
|
||||
|
||||
if (
|
||||
(string) (int) $maxDepth !== (string) $maxDepth /* Must be an integer or cleanly castable to one */
|
||||
|| $maxDepth <= $minDepth /* Larger than the minimum */
|
||||
|| is_float($maxDepth) === true /* Within the boundaries of integer */
|
||||
) {
|
||||
$message = vsprintf(
|
||||
self::MESSAGE_ERROR_WRONG_MAX_DEPTH,
|
||||
array(
|
||||
'key' => self::KEY_MAX_DEPTH,
|
||||
'min' => $minDepth,
|
||||
'given' => var_export($maxDepth, true),
|
||||
)
|
||||
);
|
||||
|
||||
throw new \InvalidArgumentException($message);
|
||||
}
|
||||
}
|
||||
|
||||
return (int) $maxDepth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimal search depth for Coding Standard packages.
|
||||
*
|
||||
* Usually this is 0, unless PHP_CodeSniffer >= 3 is used.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function getMinDepth()
|
||||
{
|
||||
if ($this->isPHPCodeSnifferInstalled('>= 3.0.0') !== true) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
74
vendor/dflydev/dot-access-data/CHANGELOG.md
vendored
74
vendor/dflydev/dot-access-data/CHANGELOG.md
vendored
|
|
@ -1,74 +0,0 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [3.0.3] - 2024-07-08
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed PHP 8.4 deprecation notices (#47)
|
||||
|
||||
## [3.0.2] - 2022-10-27
|
||||
|
||||
### Fixed
|
||||
|
||||
- Added missing return types to docblocks (#44, #45)
|
||||
|
||||
## [3.0.1] - 2021-08-13
|
||||
|
||||
### Added
|
||||
|
||||
- Adds ReturnTypeWillChange to suppress PHP 8.1 warnings (#40)
|
||||
|
||||
## [3.0.0] - 2021-01-01
|
||||
|
||||
### Added
|
||||
- Added support for both `.` and `/`-delimited key paths (#24)
|
||||
- Added parameter and return types to everything; enabled strict type checks (#18)
|
||||
- Added new exception classes to better identify certain types of errors (#20)
|
||||
- `Data` now implements `ArrayAccess` (#17)
|
||||
- Added ability to merge non-associative array values (#31, #32)
|
||||
|
||||
### Changed
|
||||
- All thrown exceptions are now instances or subclasses of `DataException` (#20)
|
||||
- Calling `get()` on a missing key path without providing a default will throw a `MissingPathException` instead of returning `null` (#29)
|
||||
- Bumped supported PHP versions to 7.1 - 8.x (#18)
|
||||
|
||||
### Fixed
|
||||
- Fixed incorrect merging of array values into string values (#32)
|
||||
- Fixed `get()` method behaving as if keys with `null` values didn't exist
|
||||
|
||||
## [2.0.0] - 2017-12-21
|
||||
|
||||
### Changed
|
||||
- Bumped supported PHP versions to 7.0 - 7.4 (#12)
|
||||
- Switched to PSR-4 autoloading
|
||||
|
||||
## [1.1.0] - 2017-01-20
|
||||
|
||||
### Added
|
||||
- Added new `has()` method to check for the existence of the given key (#4, #7)
|
||||
|
||||
## [1.0.1] - 2015-08-12
|
||||
|
||||
### Added
|
||||
- Added new optional `$default` parameter to the `get()` method (#2)
|
||||
|
||||
## [1.0.0] - 2012-07-17
|
||||
|
||||
**Initial release!**
|
||||
|
||||
[Unreleased]: https://github.com/dflydev/dflydev-dot-access-data/compare/v3.0.3...main
|
||||
[3.0.3]: https://github.com/dflydev/dflydev-dot-access-data/compare/v3.0.2...v3.0.3
|
||||
[3.0.2]: https://github.com/dflydev/dflydev-dot-access-data/compare/v3.0.1...v3.0.2
|
||||
[3.0.1]: https://github.com/dflydev/dflydev-dot-access-data/compare/v3.0.0...v3.0.1
|
||||
[3.0.0]: https://github.com/dflydev/dflydev-dot-access-data/compare/v2.0.0...v3.0.0
|
||||
[2.0.0]: https://github.com/dflydev/dflydev-dot-access-data/compare/v1.1.0...v2.0.0
|
||||
[1.1.0]: https://github.com/dflydev/dflydev-dot-access-data/compare/v1.0.1...v1.1.0
|
||||
[1.0.1]: https://github.com/dflydev/dflydev-dot-access-data/compare/v1.0.0...v1.0.1
|
||||
[1.0.0]: https://github.com/dflydev/dflydev-dot-access-data/releases/tag/v1.0.0
|
||||
158
vendor/dflydev/dot-access-data/README.md
vendored
158
vendor/dflydev/dot-access-data/README.md
vendored
|
|
@ -1,158 +0,0 @@
|
|||
Dot Access Data
|
||||
===============
|
||||
|
||||
[](https://packagist.org/packages/dflydev/dot-access-data)
|
||||
[](https://packagist.org/packages/dflydev/dot-access-data)
|
||||
[](LICENSE)
|
||||
[](https://github.com/dflydev/dflydev-dot-access-data/actions?query=workflow%3ATests+branch%3Amain)
|
||||
[](https://scrutinizer-ci.com/g/dflydev/dflydev-dot-access-data/code-structure/)
|
||||
[](https://scrutinizer-ci.com/g/dflydev/dflydev-dot-access-data)
|
||||
|
||||
Given a deep data structure, access data by dot notation.
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
* PHP (7.1+)
|
||||
|
||||
> For PHP (5.3+) please refer to version `1.0`.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Abstract example:
|
||||
|
||||
```php
|
||||
use Dflydev\DotAccessData\Data;
|
||||
|
||||
$data = new Data;
|
||||
|
||||
$data->set('a.b.c', 'C');
|
||||
$data->set('a.b.d', 'D1');
|
||||
$data->append('a.b.d', 'D2');
|
||||
$data->set('a.b.e', ['E0', 'E1', 'E2']);
|
||||
|
||||
// C
|
||||
$data->get('a.b.c');
|
||||
|
||||
// ['D1', 'D2']
|
||||
$data->get('a.b.d');
|
||||
|
||||
// ['E0', 'E1', 'E2']
|
||||
$data->get('a.b.e');
|
||||
|
||||
// true
|
||||
$data->has('a.b.c');
|
||||
|
||||
// false
|
||||
$data->has('a.b.d.j');
|
||||
|
||||
|
||||
// 'some-default-value'
|
||||
$data->get('some.path.that.does.not.exist', 'some-default-value');
|
||||
|
||||
// throws a MissingPathException because no default was given
|
||||
$data->get('some.path.that.does.not.exist');
|
||||
```
|
||||
|
||||
A more concrete example:
|
||||
|
||||
```php
|
||||
use Dflydev\DotAccessData\Data;
|
||||
|
||||
$data = new Data([
|
||||
'hosts' => [
|
||||
'hewey' => [
|
||||
'username' => 'hman',
|
||||
'password' => 'HPASS',
|
||||
'roles' => ['web'],
|
||||
],
|
||||
'dewey' => [
|
||||
'username' => 'dman',
|
||||
'password' => 'D---S',
|
||||
'roles' => ['web', 'db'],
|
||||
'nick' => 'dewey dman',
|
||||
],
|
||||
'lewey' => [
|
||||
'username' => 'lman',
|
||||
'password' => 'LP@$$',
|
||||
'roles' => ['db'],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
// hman
|
||||
$username = $data->get('hosts.hewey.username');
|
||||
// HPASS
|
||||
$password = $data->get('hosts.hewey.password');
|
||||
// ['web']
|
||||
$roles = $data->get('hosts.hewey.roles');
|
||||
// dewey dman
|
||||
$nick = $data->get('hosts.dewey.nick');
|
||||
// Unknown
|
||||
$nick = $data->get('hosts.lewey.nick', 'Unknown');
|
||||
|
||||
// DataInterface instance
|
||||
$dewey = $data->getData('hosts.dewey');
|
||||
// dman
|
||||
$username = $dewey->get('username');
|
||||
// D---S
|
||||
$password = $dewey->get('password');
|
||||
// ['web', 'db']
|
||||
$roles = $dewey->get('roles');
|
||||
|
||||
// No more lewey
|
||||
$data->remove('hosts.lewey');
|
||||
|
||||
// Add DB to hewey's roles
|
||||
$data->append('hosts.hewey.roles', 'db');
|
||||
|
||||
$data->set('hosts.april', [
|
||||
'username' => 'aman',
|
||||
'password' => '@---S',
|
||||
'roles' => ['web'],
|
||||
]);
|
||||
|
||||
// Check if a key exists (true to this case)
|
||||
$hasKey = $data->has('hosts.dewey.username');
|
||||
```
|
||||
|
||||
`Data` may be used as an array, since it implements `ArrayAccess` interface:
|
||||
|
||||
```php
|
||||
// Get
|
||||
$data->get('name') === $data['name']; // true
|
||||
|
||||
$data['name'] = 'Dewey';
|
||||
// is equivalent to
|
||||
$data->set($name, 'Dewey');
|
||||
|
||||
isset($data['name']) === $data->has('name');
|
||||
|
||||
// Remove key
|
||||
unset($data['name']);
|
||||
```
|
||||
|
||||
`/` can also be used as a path delimiter:
|
||||
|
||||
```php
|
||||
$data->set('a/b/c', 'd');
|
||||
echo $data->get('a/b/c'); // "d"
|
||||
|
||||
$data->get('a/b/c') === $data->get('a.b.c'); // true
|
||||
```
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
This library is licensed under the MIT License - see the LICENSE file
|
||||
for details.
|
||||
|
||||
|
||||
Community
|
||||
---------
|
||||
|
||||
If you have questions or want to help out, join us in the
|
||||
[#dflydev](irc://irc.freenode.net/#dflydev) channel on irc.freenode.net.
|
||||
67
vendor/dflydev/dot-access-data/composer.json
vendored
67
vendor/dflydev/dot-access-data/composer.json
vendored
|
|
@ -1,67 +0,0 @@
|
|||
{
|
||||
"name": "dflydev/dot-access-data",
|
||||
"type": "library",
|
||||
"description": "Given a deep data structure, access data by dot notation.",
|
||||
"homepage": "https://github.com/dflydev/dflydev-dot-access-data",
|
||||
"keywords": ["dot", "access", "data", "notation"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Dragonfly Development Inc.",
|
||||
"email": "info@dflydev.com",
|
||||
"homepage": "http://dflydev.com"
|
||||
},
|
||||
{
|
||||
"name": "Beau Simensen",
|
||||
"email": "beau@dflydev.com",
|
||||
"homepage": "http://beausimensen.com"
|
||||
},
|
||||
{
|
||||
"name": "Carlos Frutos",
|
||||
"email": "carlos@kiwing.it",
|
||||
"homepage": "https://github.com/cfrutos"
|
||||
},
|
||||
{
|
||||
"name": "Colin O'Dell",
|
||||
"email": "colinodell@gmail.com",
|
||||
"homepage": "https://www.colinodell.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^0.12.42",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.3",
|
||||
"scrutinizer/ocular": "1.6.0",
|
||||
"squizlabs/php_codesniffer": "^3.5",
|
||||
"vimeo/psalm": "^4.0.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Dflydev\\DotAccessData\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Dflydev\\DotAccessData\\": "tests/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "3.x-dev"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"phpcs": "phpcs",
|
||||
"phpstan": "phpstan analyse",
|
||||
"phpunit": "phpunit --no-coverage",
|
||||
"psalm": "psalm",
|
||||
"test": [
|
||||
"@phpcs",
|
||||
"@phpstan",
|
||||
"@psalm",
|
||||
"@phpunit"
|
||||
]
|
||||
}
|
||||
}
|
||||
26
vendor/eduardovillao/wp-since/.editorconfig
vendored
26
vendor/eduardovillao/wp-since/.editorconfig
vendored
|
|
@ -1,26 +0,0 @@
|
|||
# EditorConfig: https://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.php]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
max_line_length = 120
|
||||
|
||||
[*.{json,yml,yaml,xml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
|
@ -1 +0,0 @@
|
|||
* @eduardovillao
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
name: Lint & Test PHP Project
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
quality-checks:
|
||||
name: Code Quality & Tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.2"
|
||||
tools: composer, phpunit, phpcs
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --prefer-dist
|
||||
|
||||
- name: Run PHPCS (PSR-12)
|
||||
run: composer lint
|
||||
|
||||
- name: Run PHPUnit tests
|
||||
run: composer tests:unit
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
name: Generate WP Since Map
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout project
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install PHP depencencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y php php-cli php-mbstring unzip curl
|
||||
|
||||
- name: Install Composer
|
||||
run: |
|
||||
curl -sS https://getcomposer.org/installer | php
|
||||
sudo mv composer.phar /usr/local/bin/composer
|
||||
|
||||
- name: Install Composer dependencies
|
||||
run: composer install
|
||||
|
||||
- name: Download WordPress
|
||||
run: |
|
||||
curl -O https://wordpress.org/latest.zip
|
||||
unzip latest.zip -d wp-source
|
||||
mv wp-source/wordpress/* wp-source/
|
||||
|
||||
- name: Generate wp-since.json
|
||||
run: php generate-since-json.php
|
||||
|
||||
- name: Remove temporary files
|
||||
run: rm -rf wp.tar.gz wp-source
|
||||
|
||||
- name: Install GitHub CLI
|
||||
run: sudo apt install gh -y
|
||||
|
||||
- name: Authenticate GitHub CLI
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.WP_SINCE_ACTION }}
|
||||
run: echo "${GH_TOKEN}" | gh auth login --with-token
|
||||
|
||||
- name: Create new branch
|
||||
run: |
|
||||
BRANCH_NAME=update-since-map-${{ github.run_number }}
|
||||
git checkout -b $BRANCH_NAME
|
||||
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
|
||||
|
||||
- name: Commit changes
|
||||
run: |
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --global user.name "GitHub Actions"
|
||||
git add wp-since.json
|
||||
DATE=$(date +"%Y-%m-%d")
|
||||
git commit -m "chore(data): update since map on $DATE" || echo "Nada pra commitar"
|
||||
git push origin $BRANCH_NAME
|
||||
|
||||
- name: Create Pull Request
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.WP_SINCE_ACTION }}
|
||||
run: |
|
||||
gh pr create --head "$BRANCH_NAME" --base main \
|
||||
--title "chore(data): update since map" \
|
||||
--body "This PR was automatically generated by a GitHub Action. It updates the \`wp-since.json\` file with the latest WordPress version and its release date."
|
||||
2
vendor/eduardovillao/wp-since/.gitignore
vendored
2
vendor/eduardovillao/wp-since/.gitignore
vendored
|
|
@ -1,2 +0,0 @@
|
|||
/vendor
|
||||
.DS_Store
|
||||
131
vendor/eduardovillao/wp-since/README.md
vendored
131
vendor/eduardovillao/wp-since/README.md
vendored
|
|
@ -1,131 +0,0 @@
|
|||
# WP Since
|
||||
|
||||

|
||||
[](https://www.php-fig.org/psr/psr-12/)
|
||||
[](./tests)
|
||||
|
||||
**Make sure your plugin works with the right WordPress version — automatically.**
|
||||
Scans your WordPress plugin to detect all used core symbols and validates them against their official @since versions for accurate compatibility checks.
|
||||
|
||||
## ✨ How It Works
|
||||
|
||||
Ever struggled to define the correct minimum WordPress version for your plugin?
|
||||
|
||||
Worried about accidentally using functions or APIs that don’t exist in declared minimum WP version?
|
||||
|
||||
`wp-since` helps you avoid those headaches by automatically analyzing your plugin’s code and checking compatibility against real WordPress versions.
|
||||
|
||||
### Here’s what it does:
|
||||
|
||||
- 🧠 Scans your plugin for used:
|
||||
- Functions
|
||||
- Classes
|
||||
- Class methods (static and instance)
|
||||
- Action and filter hooks
|
||||
- 📖 Reads the declared Requires at least: version from your `readme.txt`
|
||||
- 🗂️ Compares those symbols with a version map built from WordPress core using `@since` tags
|
||||
- 🚨 Reports any used symbols that require a newer WP version than what’s declared
|
||||
|
||||
### Example Output
|
||||
|
||||
Let’s say your plugin uses `register_setting()` (introduced in WP `5.5`), but your `readme.txt` declares compatibility with WordPress `5.4`:
|
||||
|
||||
```bash
|
||||
🔍 Scanning plugin files...
|
||||
✅ Found readme.txt → Minimum version declared: 5.4
|
||||
|
||||
🚨 Compatibility issues found:
|
||||
|
||||
┌──────────────────────┬──────────────────┐
|
||||
│ Symbol │ Introduced in WP │
|
||||
├──────────────────────┼──────────────────┤
|
||||
│ register_setting │ 5.5.0 │
|
||||
└──────────────────────┴──────────────────┘
|
||||
|
||||
📌 Suggested version required: 5.5.0
|
||||
```
|
||||
|
||||
Now imagine your code is fully aligned with your declared version:
|
||||
|
||||
```bash
|
||||
🔍 Scanning plugin files...
|
||||
✅ Found readme.txt → Minimum version declared: 5.5
|
||||
|
||||
🎉 No compatibility issues found!
|
||||
```
|
||||
|
||||
Simple. Powerful. Automatic.
|
||||
Because your plugin deserves reliable compatibility.
|
||||
|
||||
## 🚀 Usage
|
||||
|
||||
**Requirements**
|
||||
|
||||
- PHP 7.4+
|
||||
- Composer
|
||||
|
||||
🛠️ Install via Composer (recommended)
|
||||
|
||||
```bash
|
||||
composer require --dev eduardovillao/wp-since
|
||||
```
|
||||
|
||||
▶️ Run the compatibility check
|
||||
|
||||
```bash
|
||||
./vendor/bin/wp-since check ./path-to-your-plugin
|
||||
```
|
||||
|
||||
### 🧹 Ignore Files & Folders
|
||||
|
||||
By default, wp-since scans all `.php` files in your plugin directory.
|
||||
|
||||
But what about files that don’t make it into your final plugin zip — like tests or dev tools? No worries — wp-since respects your ignore rules.
|
||||
|
||||
**Supported ignore sources:**
|
||||
|
||||
- `.distignore`
|
||||
- `.gitattributes` with `export-ignore`
|
||||
|
||||
If any of those files are present, wp-since will automatically ignore the listed files or folders during analysis — just like svn export or plugin deployment.
|
||||
|
||||
Example: .gitattributes
|
||||
|
||||
```txt
|
||||
/tests/ export-ignore
|
||||
/tools/debug.php export-ignore
|
||||
```
|
||||
|
||||
Example: .distignore
|
||||
|
||||
```txt
|
||||
/tests
|
||||
/tools/debug.php
|
||||
```
|
||||
|
||||
> These paths will be excluded from compatibility checks. This helps avoid false positives caused by test or development files.
|
||||
|
||||
### 📝 Inline Ignore
|
||||
|
||||
You can ignore specific lines from the scan by adding a special inline comment.
|
||||
|
||||
This is useful when you conditionally use a newer function but know it’s safe, like:
|
||||
|
||||
```php
|
||||
if (function_exists('wp_some_new_func')) {
|
||||
return wp_some_new_func(); // @wp-since ignore
|
||||
}
|
||||
```
|
||||
|
||||
> Only inline comments on the same line will be considered — comments above the line won’t trigger ignores.
|
||||
|
||||
## 🛠️ Coming Soon
|
||||
|
||||
- GitHub Action integration
|
||||
- HTML/Markdown reports
|
||||
- Export for CI/CD pipelines
|
||||
|
||||
## 📜 License
|
||||
|
||||
MIT © [Eduardo Villão](https://github.com/eduardovillao)
|
||||
Use freely, contribute gladly.
|
||||
21
vendor/eduardovillao/wp-since/bin/wp-since
vendored
21
vendor/eduardovillao/wp-since/bin/wp-since
vendored
|
|
@ -1,21 +0,0 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
foreach ([__DIR__ . '/../../../autoload.php', __DIR__ . '/../vendor/autoload.php'] as $file) {
|
||||
if (file_exists($file)) {
|
||||
require $file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
use WP_Since\Runner\PluginCheckCommand;
|
||||
|
||||
if ($argc < 2 || $argv[1] !== 'check') {
|
||||
echo "🛠 Usage: wp-since check /path/to/plugin\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$pluginPath = $argv[2] ?? getcwd();
|
||||
$sinceMapPath = __DIR__ . '/../wp-since.json';
|
||||
|
||||
exit(PluginCheckCommand::run($pluginPath, $sinceMapPath));
|
||||
10
vendor/eduardovillao/wp-since/check-plugin.php
vendored
10
vendor/eduardovillao/wp-since/check-plugin.php
vendored
|
|
@ -1,10 +0,0 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use WP_Since\Runner\PluginCheckCommand;
|
||||
|
||||
$pluginPath = $argv[1] ?? getcwd();
|
||||
$sinceMapPath = __DIR__ . '/wp-since.json';
|
||||
|
||||
exit(PluginCheckCommand::run($pluginPath, $sinceMapPath));
|
||||
35
vendor/eduardovillao/wp-since/composer.json
vendored
35
vendor/eduardovillao/wp-since/composer.json
vendored
|
|
@ -1,35 +0,0 @@
|
|||
{
|
||||
"name": "eduardovillao/wp-since",
|
||||
"description": "Check WordPress plugin compatibility by analyzing used functions, classes, hooks and comparing against the minimum required WP version.",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": ">=7.4",
|
||||
"nikic/php-parser": "^4.15"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^10.5",
|
||||
"squizlabs/php_codesniffer": "^3.12"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"WP_Since\\": "src/"
|
||||
}
|
||||
},
|
||||
"bin": [
|
||||
"bin/wp-since"
|
||||
],
|
||||
"scripts": {
|
||||
"check": "php bin/wp-since check ./tests/fixtures/plugin-full-test",
|
||||
"generate-since": "php generate-since-json.php",
|
||||
"tests:unit": "phpunit tests --testdox --colors",
|
||||
"lint": "phpcs",
|
||||
"lint:fix": "phpcbf"
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true,
|
||||
"support": {
|
||||
"issues": "https://github.com/eduardovillao/wp-since/issues",
|
||||
"source": "https://github.com/eduardovillao/wp-since"
|
||||
}
|
||||
}
|
||||
1728
vendor/eduardovillao/wp-since/composer.lock
generated
vendored
1728
vendor/eduardovillao/wp-since/composer.lock
generated
vendored
File diff suppressed because it is too large
Load diff
|
|
@ -1,133 +0,0 @@
|
|||
<?php
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use PhpParser\Error;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeTraverser;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
use PhpParser\ParserFactory;
|
||||
use PhpParser\NodeVisitor\ParentConnectingVisitor;
|
||||
|
||||
$sourceDir = __DIR__ . '/wp-source';
|
||||
$outputPath = __DIR__ . '/wp-since.json';
|
||||
|
||||
$excludedPaths = [
|
||||
'wp-content/',
|
||||
'wp-admin/includes/class-pclzip.php',
|
||||
'wp-admin/includes/noop.php',
|
||||
'wp-includes/ID3/',
|
||||
'wp-includes/IXR/',
|
||||
'wp-includes/PHPMailer/',
|
||||
'wp-includes/pomo/',
|
||||
'wp-includes/Requests/',
|
||||
'wp-includes/SimplePie/',
|
||||
'wp-includes/Text/',
|
||||
'wp-includes/sodium_compat/',
|
||||
'wp-includes/js/tinymce',
|
||||
'wp-includes/class-simplepie.php',
|
||||
'wp-includes/atomlib.php',
|
||||
'wp-includes/class-avif-info.php',
|
||||
'wp-includes/class-json.php',
|
||||
'wp-includes/class-pop3.php',
|
||||
'wp-includes/class-requests.php',
|
||||
'wp-includes/class-snoopy.php',
|
||||
'wp-includes/compat.php',
|
||||
'wp-includes/rss.php',
|
||||
];
|
||||
|
||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
$result = [];
|
||||
|
||||
class SinceExtractor extends NodeVisitorAbstract
|
||||
{
|
||||
private $file;
|
||||
private $result;
|
||||
|
||||
public function __construct($file, &$result)
|
||||
{
|
||||
$this->file = $file;
|
||||
$this->result = &$result;
|
||||
}
|
||||
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
$doc = $node->getDocComment();
|
||||
$docText = $doc ? $doc->getText() : null;
|
||||
$since = $this->extractTag($docText, '@since');
|
||||
$deprecated = $this->extractTag($docText, '@deprecated');
|
||||
|
||||
if ($node instanceof Node\Stmt\Function_) {
|
||||
$this->addResult($node->name->toString(), 'function', $since, $deprecated);
|
||||
} elseif ($node instanceof Node\Stmt\Class_ || $node instanceof Node\Stmt\Interface_ || $node instanceof Node\Stmt\Trait_) {
|
||||
$type = $node instanceof Node\Stmt\Class_ ? 'class' : ($node instanceof Node\Stmt\Interface_ ? 'interface' : 'trait');
|
||||
$this->addResult($node->name->toString(), $type, $since, $deprecated);
|
||||
} elseif ($node instanceof Node\Stmt\ClassMethod && !$node->isPrivate()) {
|
||||
$class = $node->getAttribute('parent');
|
||||
$className = $class instanceof Node\Stmt\Class_ && $class->name ? $class->name->toString() : 'Anonymous';
|
||||
$methodName = $node->name->toString();
|
||||
$methodSince = $since ?: ($class && $class->getDocComment() ? $this->extractTag($class->getDocComment()->getText(), '@since') : null);
|
||||
if ($className !== 'Anonymous' && $methodSince) {
|
||||
$this->addResult("$className::$methodName", 'method', $methodSince, $deprecated);
|
||||
}
|
||||
} elseif (
|
||||
$node instanceof Node\Expr\FuncCall &&
|
||||
$node->name instanceof Node\Name &&
|
||||
in_array($node->name->toString(), ['do_action', 'apply_filters'], true)
|
||||
) {
|
||||
$hookNameNode = $node->args[0]->value ?? null;
|
||||
if ($hookNameNode instanceof Node\Scalar\String_) {
|
||||
$hookName = $hookNameNode->value;
|
||||
$this->addResult($hookName, 'hook', $since, $deprecated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function extractTag($docText, $tag)
|
||||
{
|
||||
if ($docText && preg_match('/' . preg_quote($tag) . '\s+([0-9.]+)/', $docText, $matches)) {
|
||||
$version = $matches[1];
|
||||
if ($version === 'MU') return '3.0.0';
|
||||
if (preg_match('/^\d+\.\d+(\.\d+)?$/', $version)) return $version;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function addResult($name, $type, $since, $deprecated)
|
||||
{
|
||||
if ($since) {
|
||||
$this->result[$name] = array_filter([
|
||||
'type' => $type,
|
||||
'since' => $since,
|
||||
'deprecated' => $deprecated,
|
||||
'file' => $this->file
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rii = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($sourceDir));
|
||||
|
||||
foreach ($rii as $file) {
|
||||
if ($file->isDir() || $file->getExtension() !== 'php') continue;
|
||||
$relativePath = str_replace($sourceDir . '/', '', $file->getPathname());
|
||||
|
||||
foreach ($excludedPaths as $excluded) {
|
||||
if (strpos($relativePath, $excluded) === 0) continue 2;
|
||||
}
|
||||
|
||||
try {
|
||||
$code = file_get_contents($file->getPathname());
|
||||
$ast = $parser->parse($code);
|
||||
|
||||
$traverser = new NodeTraverser();
|
||||
$traverser->addVisitor(new ParentConnectingVisitor());
|
||||
$traverser->addVisitor(new SinceExtractor($relativePath, $result));
|
||||
$traverser->traverse($ast);
|
||||
|
||||
} catch (Error $e) {
|
||||
echo "Processing error {$relativePath}: {$e->getMessage()}\n";
|
||||
}
|
||||
}
|
||||
|
||||
file_put_contents($outputPath, json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
echo "✅ File generated in: {$outputPath}\n";
|
||||
17
vendor/eduardovillao/wp-since/phpcs.xml
vendored
17
vendor/eduardovillao/wp-since/phpcs.xml
vendored
|
|
@ -1,17 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<ruleset name="WP-Since Ruleset">
|
||||
<description>PHP_CodeSniffer configuration for WP-Since</description>
|
||||
|
||||
<!-- Only PSR-12 -->
|
||||
<rule ref="PSR12"/>
|
||||
|
||||
<!-- To check -->
|
||||
<file>src</file>
|
||||
<file>tests</file>
|
||||
|
||||
<!-- To ignore -->
|
||||
<exclude-pattern>vendor/*</exclude-pattern>
|
||||
<exclude-pattern>tests/fixtures/*</exclude-pattern>
|
||||
|
||||
<arg name="report-width" value="120"/>
|
||||
</ruleset>
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace WP_Since\Checker;
|
||||
|
||||
use WP_Since\Utils\VersionHelper;
|
||||
|
||||
class CompatibilityChecker
|
||||
{
|
||||
private array $sinceMap;
|
||||
|
||||
public function __construct(array $sinceMap)
|
||||
{
|
||||
$this->sinceMap = $sinceMap;
|
||||
}
|
||||
|
||||
public function check(array $symbols, string $declaredVersion): array
|
||||
{
|
||||
$incompatible = [];
|
||||
|
||||
foreach ($symbols as $symbol) {
|
||||
if (
|
||||
isset($this->sinceMap[$symbol]) &&
|
||||
VersionHelper::compare($declaredVersion, $this->sinceMap[$symbol]['since']) < 0
|
||||
) {
|
||||
$incompatible[$symbol] = $this->sinceMap[$symbol]['since'];
|
||||
}
|
||||
}
|
||||
|
||||
return $incompatible;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace WP_Since\Resolver;
|
||||
|
||||
class IgnoreRulesResolver
|
||||
{
|
||||
public static function getIgnoredPaths(string $pluginPath): array
|
||||
{
|
||||
$ignorePatterns = [];
|
||||
|
||||
$distignore = $pluginPath . '/.distignore';
|
||||
if (file_exists($distignore)) {
|
||||
$ignorePatterns = array_merge($ignorePatterns, self::parseIgnoreFile($distignore));
|
||||
}
|
||||
|
||||
$gitattributes = $pluginPath . '/.gitattributes';
|
||||
if (file_exists($gitattributes)) {
|
||||
$ignorePatterns = array_merge($ignorePatterns, self::parseGitAttributes($gitattributes));
|
||||
}
|
||||
|
||||
return array_map(function ($pattern) {
|
||||
return ltrim(rtrim($pattern, '/'), '/');
|
||||
}, $ignorePatterns);
|
||||
}
|
||||
|
||||
private static function parseIgnoreFile(string $file): array
|
||||
{
|
||||
return array_filter(array_map('trim', file($file)), fn($line) => $line !== '' && $line[0] !== '#');
|
||||
}
|
||||
|
||||
private static function parseGitAttributes(string $file): array
|
||||
{
|
||||
$lines = file($file);
|
||||
$ignores = [];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (strpos($line, 'export-ignore') !== false) {
|
||||
$parts = preg_split('/\s+/', trim($line));
|
||||
if (!empty($parts[0])) {
|
||||
$ignores[] = $parts[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $ignores;
|
||||
}
|
||||
|
||||
public static function shouldIgnore(string $relativePath, array $ignorePaths): bool
|
||||
{
|
||||
foreach ($ignorePaths as $ignored) {
|
||||
$normalized = ltrim($ignored, '/');
|
||||
if (
|
||||
$relativePath === $normalized ||
|
||||
str_starts_with($relativePath, rtrim($normalized, '/') . '/')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace WP_Since\Resolver;
|
||||
|
||||
use PhpParser\Lexer\Emulative;
|
||||
|
||||
class InlineIgnoreResolver
|
||||
{
|
||||
public static function extractIgnoredLines(string $code): array
|
||||
{
|
||||
$lexer = new Emulative(['usedAttributes' => ['startLine']]);
|
||||
$lexer->startLexing($code);
|
||||
|
||||
$ignoredLines = [];
|
||||
|
||||
foreach ($lexer->getTokens() as $token) {
|
||||
if (is_array($token) && in_array($token[0], [T_COMMENT, T_DOC_COMMENT], true)) {
|
||||
if (strpos($token[1], '@wp-since ignore') !== false) {
|
||||
$ignoredLines[] = $token[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $ignoredLines;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace WP_Since\Resolver;
|
||||
|
||||
class VersionResolver
|
||||
{
|
||||
public static function resolve(string $pluginPath): ?array
|
||||
{
|
||||
$pluginFile = self::findMainPluginFile($pluginPath);
|
||||
|
||||
if ($pluginFile) {
|
||||
$version = self::extractVersionFromPluginHeader($pluginFile);
|
||||
if ($version) {
|
||||
return ['version' => $version, 'source' => 'main plugin file header'];
|
||||
}
|
||||
}
|
||||
|
||||
$readme = self::findReadmeFile($pluginPath);
|
||||
if ($readme) {
|
||||
$version = self::extractVersionFromReadme($readme);
|
||||
if ($version) {
|
||||
return ['version' => $version, 'source' => 'readme'];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function findMainPluginFile(string $pluginPath): ?string
|
||||
{
|
||||
$basename = basename($pluginPath);
|
||||
$candidate = "{$pluginPath}/{$basename}.php";
|
||||
if (file_exists($candidate)) {
|
||||
return $candidate;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function extractVersionFromPluginHeader(string $file): ?string
|
||||
{
|
||||
$contents = file_get_contents($file);
|
||||
if (!$contents) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (preg_match('/^\s*\*\s+Requires at least:\s*([0-9.]+)/mi', $contents, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function findReadmeFile(string $pluginPath): ?string
|
||||
{
|
||||
foreach (['readme.txt', 'README.txt'] as $filename) {
|
||||
$full = "{$pluginPath}/{$filename}";
|
||||
if (file_exists($full)) {
|
||||
return $full;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function extractVersionFromReadme(string $file): ?string
|
||||
{
|
||||
$lines = file($file);
|
||||
foreach ($lines as $line) {
|
||||
if (stripos($line, 'Requires at least:') === 0) {
|
||||
if (preg_match('/Requires at least:\s*([0-9.]+)/i', $line, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace WP_Since\Runner;
|
||||
|
||||
use WP_Since\Resolver\VersionResolver;
|
||||
use WP_Since\Scanner\PluginScanner;
|
||||
use WP_Since\Checker\CompatibilityChecker;
|
||||
use WP_Since\Utils\TablePrinter;
|
||||
use WP_Since\Utils\VersionHelper;
|
||||
|
||||
class PluginCheckCommand
|
||||
{
|
||||
public static function run(string $pluginPath, string $sinceMapPath): int
|
||||
{
|
||||
if (!file_exists($sinceMapPath)) {
|
||||
echo "❌ wp-since.json not found. Run composer generate-since first.\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
$versionResolver = VersionResolver::resolve($pluginPath);
|
||||
if (!$versionResolver['version']) {
|
||||
echo "❌ Could not determine the minimum required WP version.\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
$source = $versionResolver['source'] ?? '';
|
||||
$declaredVersion = $versionResolver['version'];
|
||||
|
||||
echo "✅ Minimum version declared: {$declaredVersion} (from {$source})\n\n";
|
||||
|
||||
$usedSymbols = PluginScanner::scan($pluginPath);
|
||||
$sinceMap = json_decode(file_get_contents($sinceMapPath), true);
|
||||
|
||||
$checker = new CompatibilityChecker($sinceMap);
|
||||
$incompatible = $checker->check($usedSymbols, $declaredVersion);
|
||||
|
||||
if (count($incompatible)) {
|
||||
echo "🚨 Compatibility issues found:\n\n";
|
||||
$rows = [];
|
||||
foreach ($incompatible as $symbol => $version) {
|
||||
$rows[] = [$symbol, $version];
|
||||
}
|
||||
TablePrinter::render($rows, ['Symbol', 'Introduced in WP']);
|
||||
|
||||
$versions = array_values($incompatible);
|
||||
$maxVersion = array_reduce($versions, function ($carry, $v) {
|
||||
return VersionHelper::compare($carry, $v) < 0 ? $v : $carry;
|
||||
}, $declaredVersion);
|
||||
|
||||
echo "📌 Suggested version required: {$maxVersion}\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
echo "✅ All good! Your plugin is compatible with WP {$declaredVersion}.\n";
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace WP_Since\Scanner;
|
||||
|
||||
use PhpParser\NodeTraverser;
|
||||
use PhpParser\ParserFactory;
|
||||
use PhpParser\NodeVisitor\ParentConnectingVisitor;
|
||||
use WP_Since\Resolver\IgnoreRulesResolver;
|
||||
use WP_Since\Scanner\SymbolExtractorVisitor;
|
||||
use WP_Since\Resolver\InlineIgnoreResolver;
|
||||
|
||||
class PluginScanner
|
||||
{
|
||||
public static function scan(string $path): array
|
||||
{
|
||||
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
|
||||
$traverser = new NodeTraverser();
|
||||
|
||||
$usedSymbols = [];
|
||||
$varMap = [];
|
||||
|
||||
$traverser->addVisitor(new ParentConnectingVisitor());
|
||||
|
||||
$ignorePaths = IgnoreRulesResolver::getIgnoredPaths($path);
|
||||
$rii = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path));
|
||||
foreach ($rii as $file) {
|
||||
$relativePath = str_replace($path . '/', '', $file->getPathname());
|
||||
|
||||
if (
|
||||
$file->isDir() ||
|
||||
$file->getExtension() !== 'php' ||
|
||||
IgnoreRulesResolver::shouldIgnore($relativePath, $ignorePaths)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$code = file_get_contents($file->getPathname());
|
||||
$ignoredLines = InlineIgnoreResolver::extractIgnoredLines($code);
|
||||
|
||||
$visitor = new SymbolExtractorVisitor($usedSymbols, $varMap, $ignoredLines);
|
||||
$traverser->addVisitor($visitor);
|
||||
|
||||
try {
|
||||
$stmts = $parser->parse($code);
|
||||
$traverser->traverse($stmts);
|
||||
} catch (\Exception $e) {
|
||||
// Add error handling
|
||||
}
|
||||
|
||||
$traverser->removeVisitor($visitor);
|
||||
}
|
||||
|
||||
return array_unique($usedSymbols);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace WP_Since\Scanner;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
use WP_Since\Scanner\SymbolHandlers\SymbolHandlerInterface;
|
||||
|
||||
class SymbolExtractorVisitor extends NodeVisitorAbstract
|
||||
{
|
||||
private array $usedSymbols;
|
||||
private array $varMap;
|
||||
private array $ignoredLines;
|
||||
|
||||
/** @var SymbolHandlerInterface[] */
|
||||
private array $handlers;
|
||||
|
||||
public function __construct(array &$usedSymbols, array &$varMap, array $ignoredLines = [])
|
||||
{
|
||||
$this->usedSymbols = &$usedSymbols;
|
||||
$this->varMap = &$varMap;
|
||||
$this->ignoredLines = $ignoredLines;
|
||||
|
||||
$this->handlers = [
|
||||
new SymbolHandlers\FunctionCallHandler(),
|
||||
new SymbolHandlers\NewClassHandler(),
|
||||
new SymbolHandlers\StaticCallHandler(),
|
||||
new SymbolHandlers\MethodCallHandler(),
|
||||
];
|
||||
}
|
||||
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if (in_array($node->getStartLine(), $this->ignoredLines, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($this->handlers as $handler) {
|
||||
if ($handler->supports($node)) {
|
||||
$symbols = $handler->extract($node, $this->varMap);
|
||||
foreach ($symbols as $symbol) {
|
||||
$this->usedSymbols[] = $symbol;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace WP_Since\Scanner\SymbolHandlers;
|
||||
|
||||
use PhpParser\Node;
|
||||
|
||||
class FunctionCallHandler implements SymbolHandlerInterface
|
||||
{
|
||||
public function supports(Node $node): bool
|
||||
{
|
||||
return $node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name;
|
||||
}
|
||||
|
||||
public function extract(Node $node, array &$varMap = []): array
|
||||
{
|
||||
$symbols = [(string) $node->name];
|
||||
|
||||
if (in_array((string) $node->name, ['do_action', 'apply_filters'], true)) {
|
||||
$hookNameNode = $node->args[0]->value ?? null;
|
||||
if ($hookNameNode instanceof Node\Scalar\String_) {
|
||||
$symbols[] = $hookNameNode->value;
|
||||
}
|
||||
}
|
||||
|
||||
return $symbols;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace WP_Since\Scanner\SymbolHandlers;
|
||||
|
||||
use PhpParser\Node;
|
||||
|
||||
class MethodCallHandler implements SymbolHandlerInterface
|
||||
{
|
||||
public function supports(Node $node): bool
|
||||
{
|
||||
return $node instanceof Node\Expr\MethodCall &&
|
||||
$node->var instanceof Node\Expr\Variable &&
|
||||
$node->name instanceof Node\Identifier;
|
||||
}
|
||||
|
||||
public function extract(Node $node, array &$varMap = []): array
|
||||
{
|
||||
$varName = $node->var->name;
|
||||
$method = (string) $node->name;
|
||||
|
||||
if (is_string($varName) && isset($varMap[$varName])) {
|
||||
$class = $varMap[$varName];
|
||||
return ["$class::$method"];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace WP_Since\Scanner\SymbolHandlers;
|
||||
|
||||
use PhpParser\Node;
|
||||
|
||||
class NewClassHandler implements SymbolHandlerInterface
|
||||
{
|
||||
public function supports(Node $node): bool
|
||||
{
|
||||
return $node instanceof Node\Expr\New_ && $node->class instanceof Node\Name;
|
||||
}
|
||||
|
||||
public function extract(Node $node, array &$varMap = []): array
|
||||
{
|
||||
$symbols = [(string) $node->class];
|
||||
|
||||
$parent = $node->getAttribute('parent');
|
||||
if (
|
||||
$parent instanceof Node\Expr\Assign &&
|
||||
$parent->var instanceof Node\Expr\Variable &&
|
||||
is_string($parent->var->name)
|
||||
) {
|
||||
$varMap[$parent->var->name] = (string) $node->class;
|
||||
}
|
||||
|
||||
return $symbols;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace WP_Since\Scanner\SymbolHandlers;
|
||||
|
||||
use PhpParser\Node;
|
||||
|
||||
class StaticCallHandler implements SymbolHandlerInterface
|
||||
{
|
||||
public function supports(Node $node): bool
|
||||
{
|
||||
return $node instanceof Node\Expr\StaticCall &&
|
||||
$node->class instanceof Node\Name &&
|
||||
$node->name instanceof Node\Identifier;
|
||||
}
|
||||
|
||||
public function extract(Node $node, array &$varMap = []): array
|
||||
{
|
||||
$class = (string) $node->class;
|
||||
$method = (string) $node->name;
|
||||
return ["$class::$method"];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace WP_Since\Scanner\SymbolHandlers;
|
||||
|
||||
use PhpParser\Node;
|
||||
|
||||
interface SymbolHandlerInterface
|
||||
{
|
||||
public function supports(Node $node): bool;
|
||||
|
||||
/**
|
||||
* @return string[] Symbols extracted from the node
|
||||
*/
|
||||
public function extract(Node $node, array &$varMap = []): array;
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace WP_Since\Utils;
|
||||
|
||||
class TablePrinter
|
||||
{
|
||||
public static function render(array $rows, array $headers): void
|
||||
{
|
||||
$widths = [];
|
||||
foreach ($headers as $i => $header) {
|
||||
$widths[$i] = strlen($header);
|
||||
}
|
||||
|
||||
foreach ($rows as $row) {
|
||||
foreach ($row as $i => $cell) {
|
||||
$widths[$i] = max($widths[$i], strlen($cell));
|
||||
}
|
||||
}
|
||||
|
||||
$drawLine = function ($left, $middle, $right, $fill = '─') use ($widths) {
|
||||
echo $left;
|
||||
foreach ($widths as $i => $w) {
|
||||
echo str_repeat($fill, $w + 2);
|
||||
echo $i < count($widths) - 1 ? $middle : $right;
|
||||
}
|
||||
echo "\n";
|
||||
};
|
||||
|
||||
$drawRow = function ($row, $sep = '│') use ($widths) {
|
||||
echo $sep;
|
||||
foreach ($row as $i => $cell) {
|
||||
echo ' ' . str_pad($cell, $widths[$i]) . ' ' . $sep;
|
||||
}
|
||||
echo "\n";
|
||||
};
|
||||
|
||||
$drawLine('┌', '┬', '┐');
|
||||
$drawRow($headers);
|
||||
$drawLine('├', '┼', '┤');
|
||||
foreach ($rows as $row) {
|
||||
$drawRow($row);
|
||||
}
|
||||
$drawLine('└', '┴', '┘');
|
||||
echo "\n";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace WP_Since\Utils;
|
||||
|
||||
class VersionHelper
|
||||
{
|
||||
/**
|
||||
* Normaliza uma versão para o formato x.y.z (ex: 5.5 → 5.5.0)
|
||||
*/
|
||||
public static function normalize(string $version): string
|
||||
{
|
||||
$parts = explode('.', $version);
|
||||
while (count($parts) < 3) {
|
||||
$parts[] = '0';
|
||||
}
|
||||
return implode('.', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compara duas versões, normalizando ambas.
|
||||
*
|
||||
* @param string $versionA
|
||||
* @param string $versionB
|
||||
* @return int Retorna -1 se A < B, 0 se A == B, 1 se A > B
|
||||
*/
|
||||
public static function compare(string $versionA, string $versionB): int
|
||||
{
|
||||
return version_compare(
|
||||
self::normalize($versionA),
|
||||
self::normalize($versionB)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace WP_Since\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use WP_Since\Checker\CompatibilityChecker;
|
||||
|
||||
class CompatibilityCheckerTest extends TestCase
|
||||
{
|
||||
public function testDetectsIncompatibleSymbols()
|
||||
{
|
||||
$sinceMap = [
|
||||
'register_setting' => ['since' => '5.5.0'],
|
||||
'WP_Query' => ['since' => '3.0.0'],
|
||||
'MyClass::boot' => ['since' => '6.2.0'],
|
||||
'custom_hook' => ['since' => '6.0.0'],
|
||||
];
|
||||
|
||||
$symbols = [
|
||||
'register_setting',
|
||||
'WP_Query',
|
||||
'MyClass::boot',
|
||||
'custom_hook',
|
||||
];
|
||||
|
||||
$declaredVersion = '5.5';
|
||||
|
||||
$checker = new CompatibilityChecker($sinceMap);
|
||||
$incompatible = $checker->check($symbols, $declaredVersion);
|
||||
|
||||
$this->assertArrayHasKey('MyClass::boot', $incompatible);
|
||||
$this->assertArrayHasKey('custom_hook', $incompatible);
|
||||
$this->assertArrayNotHasKey('register_setting', $incompatible);
|
||||
$this->assertArrayNotHasKey('WP_Query', $incompatible);
|
||||
$this->assertEquals('6.2.0', $incompatible['MyClass::boot']);
|
||||
}
|
||||
|
||||
public function testAllSymbolsCompatible()
|
||||
{
|
||||
$sinceMap = [
|
||||
'function_one' => ['since' => '5.1.0'],
|
||||
'function_two' => ['since' => '5.0.0'],
|
||||
];
|
||||
|
||||
$symbols = ['function_one', 'function_two'];
|
||||
$declaredVersion = '5.5.0';
|
||||
|
||||
$checker = new CompatibilityChecker($sinceMap);
|
||||
$incompatible = $checker->check($symbols, $declaredVersion);
|
||||
|
||||
$this->assertEmpty($incompatible);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace WP_Since\Tests\Integration;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use WP_Since\Resolver\VersionResolver;
|
||||
use WP_Since\Scanner\PluginScanner;
|
||||
use WP_Since\Checker\CompatibilityChecker;
|
||||
|
||||
class FullCompatibilityFlowTest extends TestCase
|
||||
{
|
||||
public function testDetectsAllTypesOfSymbolsCorrectly()
|
||||
{
|
||||
$pluginPath = __DIR__ . '/../fixtures/plugin-full-test';
|
||||
|
||||
$declaredVersion = VersionResolver::resolve($pluginPath);
|
||||
$symbols = PluginScanner::scan($pluginPath);
|
||||
|
||||
$this->assertNotNull($declaredVersion, 'Declared version should not be null');
|
||||
$this->assertEquals('5.5', $declaredVersion['version']);
|
||||
|
||||
$sinceMap = [
|
||||
'add_option' => ['since' => '2.0.0'],
|
||||
'WP_Query' => ['since' => '3.0.0'],
|
||||
'WP_Filesystem::get_contents' => ['since' => '5.1.0'],
|
||||
'WP_User::add_cap' => ['since' => '5.7.0'],
|
||||
'my_custom_hook' => ['since' => '6.0.0'],
|
||||
'my_filter_hook' => ['since' => '5.3.0'],
|
||||
];
|
||||
|
||||
$checker = new CompatibilityChecker($sinceMap);
|
||||
$incompatible = $checker->check($symbols, $declaredVersion['version']);
|
||||
|
||||
$this->assertArrayHasKey('WP_User::add_cap', $incompatible);
|
||||
$this->assertArrayHasKey('my_custom_hook', $incompatible);
|
||||
|
||||
$this->assertArrayNotHasKey('add_option', $incompatible);
|
||||
$this->assertArrayNotHasKey('WP_Query', $incompatible);
|
||||
$this->assertArrayNotHasKey('WP_Filesystem::get_contents', $incompatible);
|
||||
$this->assertArrayNotHasKey('my_filter_hook', $incompatible);
|
||||
|
||||
$expected = array_keys($sinceMap);
|
||||
foreach ($expected as $symbol) {
|
||||
$this->assertContains($symbol, $symbols, "Missing symbol: {$symbol}");
|
||||
|
||||
// phpcs:disable Generic.Files.LineLength.TooLong
|
||||
$this->assertNotContains('some_ignored_func_folder', $symbols, 'Should ignore folder from /ignored-folder/');
|
||||
$this->assertNotContains('some_ignored_func_file', $symbols, 'Should ignore specific file /ignore-this.php');
|
||||
$this->assertNotContains('some_ignored_func_noslash', $symbols, 'Should ignore folder from ignored-no-slash/');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace WP_Since\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use WP_Since\Scanner\PluginScanner;
|
||||
|
||||
final class PluginScannerTest extends TestCase
|
||||
{
|
||||
public function testDetectsAllSymbols()
|
||||
{
|
||||
$pluginPath = __DIR__ . '/fixtures/plugin-full-test';
|
||||
$symbols = PluginScanner::scan($pluginPath);
|
||||
|
||||
$expected = [
|
||||
'add_option',
|
||||
'WP_Query',
|
||||
'WP_Filesystem::get_contents',
|
||||
'WP_User::add_cap',
|
||||
'my_custom_hook',
|
||||
'my_filter_hook',
|
||||
];
|
||||
|
||||
foreach ($expected as $symbol) {
|
||||
$this->assertContains($symbol, $symbols, "Missing: $symbol");
|
||||
}
|
||||
}
|
||||
|
||||
public function testIgnoresSymbolsMarkedWithIgnoreComment()
|
||||
{
|
||||
$path = __DIR__ . '/fixtures/plugin-ignore-comment';
|
||||
$symbols = PluginScanner::scan($path);
|
||||
|
||||
$this->assertNotContains('add_option', $symbols, 'Should ignore symbol with @wp-since ignore');
|
||||
$this->assertNotContains('should_be_ignored', $symbols, 'Should ignore symbolwith @wp-since ignore');
|
||||
$this->assertNotContains('wp_is_block_theme', $symbols, 'Should ignore symbol with @wp-since ignore');
|
||||
$this->assertNotContains('should_be_ignored_space', $symbols, 'Should ignore symbol with @wp-since ignore');
|
||||
$this->assertContains('do_action', $symbols, 'Should detect function call without ignore comment');
|
||||
$this->assertContains('my_custom_hook', $symbols, 'Should detect function call without ignore comment');
|
||||
$this->assertContains('register_setting', $symbols, 'Should detect function call without ignore comment');
|
||||
$this->assertContains('wp_detected_function', $symbols, 'Should detect function call without ignore comment');
|
||||
$this->assertContains('need_detect', $symbols, 'Should detect function call without ignore comment');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace WP_Since\Tests\Resolver;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use WP_Since\Resolver\IgnoreRulesResolver;
|
||||
|
||||
class IgnoreRulesResolverTest extends TestCase
|
||||
{
|
||||
public function testShouldIgnoreExactMatch()
|
||||
{
|
||||
$ignorePaths = ['tests/', 'assets/', 'example.php'];
|
||||
|
||||
$this->assertTrue(IgnoreRulesResolver::shouldIgnore('tests/helper.php', $ignorePaths));
|
||||
$this->assertTrue(IgnoreRulesResolver::shouldIgnore('assets/img/logo.png', $ignorePaths));
|
||||
$this->assertTrue(IgnoreRulesResolver::shouldIgnore('example.php', $ignorePaths));
|
||||
}
|
||||
|
||||
public function testShouldNotIgnoreNonMatchingPath()
|
||||
{
|
||||
$ignorePaths = ['tests/', 'vendor/', 'docs/readme.txt'];
|
||||
|
||||
$this->assertFalse(IgnoreRulesResolver::shouldIgnore('src/Plugin.php', $ignorePaths));
|
||||
$this->assertFalse(IgnoreRulesResolver::shouldIgnore('includes/functions.php', $ignorePaths));
|
||||
}
|
||||
|
||||
public function testShouldIgnoreNestedPaths()
|
||||
{
|
||||
$ignorePaths = ['admin/'];
|
||||
|
||||
$this->assertTrue(IgnoreRulesResolver::shouldIgnore('admin/settings/page.php', $ignorePaths));
|
||||
$this->assertTrue(IgnoreRulesResolver::shouldIgnore('admin/page.php', $ignorePaths));
|
||||
}
|
||||
|
||||
public function testShouldIgnoreWithOrWithoutLeadingSlash()
|
||||
{
|
||||
$ignorePaths = ['/build/', 'temp/'];
|
||||
|
||||
$this->assertTrue(IgnoreRulesResolver::shouldIgnore('build/bundle.js', $ignorePaths));
|
||||
$this->assertTrue(IgnoreRulesResolver::shouldIgnore('temp/cache.php', $ignorePaths));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace WP_Since\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use WP_Since\Utils\TablePrinter;
|
||||
|
||||
class TablePrinterTest extends TestCase
|
||||
{
|
||||
public function testRenderOutputsCorrectTable()
|
||||
{
|
||||
$headers = ['Name', 'Version'];
|
||||
$rows = [
|
||||
['register_setting', '5.5.0'],
|
||||
['some_function', '6.0.0'],
|
||||
];
|
||||
|
||||
ob_start();
|
||||
TablePrinter::render($rows, $headers);
|
||||
$output = ob_get_clean();
|
||||
|
||||
$this->assertStringContainsString('register_setting', $output);
|
||||
$this->assertStringContainsString('some_function', $output);
|
||||
$this->assertStringContainsString('┌', $output);
|
||||
$this->assertStringContainsString('┴', $output);
|
||||
$this->assertStringContainsString('│ Name', $output);
|
||||
$this->assertStringContainsString('│ Version', $output);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace WP_Since\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use WP_Since\Utils\VersionHelper;
|
||||
|
||||
class VersionHelperTest extends TestCase
|
||||
{
|
||||
public function testNormalizeShortVersions()
|
||||
{
|
||||
$this->assertEquals('5.5.0', VersionHelper::normalize('5.5'));
|
||||
$this->assertEquals('5.0.0', VersionHelper::normalize('5'));
|
||||
$this->assertEquals('6.1.2', VersionHelper::normalize('6.1.2'));
|
||||
}
|
||||
|
||||
public function testCompareVersions()
|
||||
{
|
||||
$this->assertSame(0, VersionHelper::compare('5.5', '5.5.0'));
|
||||
$this->assertSame(0, VersionHelper::compare('6.1.0', '6.1'));
|
||||
$this->assertSame(1, VersionHelper::compare('6.2', '6.1.5'));
|
||||
$this->assertSame(-1, VersionHelper::compare('5.9', '6.0'));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace WP_Since\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use WP_Since\Resolver\VersionResolver;
|
||||
|
||||
class VersionResolverTest extends TestCase
|
||||
{
|
||||
public function testExtractsVersionFromMainPluginFile()
|
||||
{
|
||||
$path = __DIR__ . '/fixtures/plugin-with-header';
|
||||
$resolved = VersionResolver::resolve($path);
|
||||
|
||||
$this->assertEquals('6.2', $resolved['version']);
|
||||
$this->assertEquals('main plugin file header', $resolved['source']);
|
||||
}
|
||||
|
||||
public function testExtractsVersionFromReadmeIfNoHeader()
|
||||
{
|
||||
$path = __DIR__ . '/fixtures/plugin-with-readme-only';
|
||||
$resolved = VersionResolver::resolve($path);
|
||||
|
||||
$this->assertEquals('5.8', $resolved['version']);
|
||||
$this->assertEquals('readme', $resolved['source']);
|
||||
}
|
||||
|
||||
public function testReturnsNullIfVersionNotFoundAnywhere()
|
||||
{
|
||||
$path = __DIR__ . '/fixtures/plugin-without-version';
|
||||
$resolved = VersionResolver::resolve($path);
|
||||
|
||||
$this->assertNull($resolved);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<?php
|
||||
|
||||
do_action('init');
|
||||
register_setting('group', 'option');
|
||||
new WP_Query();
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
<?php
|
||||
|
||||
apply_filters('custom_filter', $value);
|
||||
MyClass::boot();
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
/ignored-folder/
|
||||
/ignore-this.php
|
||||
ignored-no-slash/
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<?php
|
||||
|
||||
function ignored_function_file()
|
||||
{
|
||||
some_ignored_func_file();
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<?php
|
||||
|
||||
function ignored_function_folder()
|
||||
{
|
||||
some_ignored_func_folder();
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<?php
|
||||
|
||||
function ignored_function_noslash()
|
||||
{
|
||||
some_ignored_func_noslash();
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Plugin Name: My plugin
|
||||
* Plugin URI: https://myddelivery.com/
|
||||
* Description: Simple test plugin.
|
||||
* Author: EduardoVillao.me
|
||||
* Author URI: https://eduardovillao.me/
|
||||
* Version: 1.0
|
||||
* Requires PHP: 7.4
|
||||
* Requires at least: 5.5
|
||||
* Text Domain: test-plugin
|
||||
* Domain Path: /languages
|
||||
* License: GPL-2.0+
|
||||
* License URI: https://www.gnu.org/licenses/gpl-2.0.txt
|
||||
*/
|
||||
|
||||
add_option('foo', 'bar');
|
||||
|
||||
$query = new WP_Query();
|
||||
|
||||
WP_Filesystem::get_contents('/some/path');
|
||||
|
||||
$user = new WP_User();
|
||||
$user->add_cap('edit_posts');
|
||||
|
||||
do_action('my_custom_hook', 'param');
|
||||
apply_filters('my_filter_hook', 'value');
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
=== Test Plugin ===
|
||||
Contributors: evcode
|
||||
Donate link: https://eduardovillao.me/
|
||||
Tags: delivery, wordpress delivery, delivery whatsapp
|
||||
Requires at least: 5.5
|
||||
Tested up to: 6.7
|
||||
Stable tag: 2.0
|
||||
Requires PHP: 7.4
|
||||
License: GPLv2License
|
||||
URI:https://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
add_option('should_be_ignored'); // @wp-since ignore
|
||||
|
||||
do_action('should_be_ignored_space'); // @wp-since ignore
|
||||
|
||||
do_action('need_detect'); // simple comment
|
||||
|
||||
do_action('my_custom_hook');
|
||||
|
||||
register_setting('mygroup', 'myoption');
|
||||
|
||||
function isBlockTheme()
|
||||
{
|
||||
if (function_exists( 'wp_is_block_theme')) {
|
||||
return wp_is_block_theme(); // @wp-since ignore
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function exampleWithoutIgnore()
|
||||
{
|
||||
if (function_exists('wp_detected_function')) {
|
||||
return wp_detected_function();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Plugin Name: My plugin
|
||||
* Plugin URI: https://myddelivery.com/
|
||||
* Description: Simple test plugin.
|
||||
* Author: EduardoVillao.me
|
||||
* Author URI: https://eduardovillao.me/
|
||||
* Version: 1.0
|
||||
* Requires PHP: 7.4
|
||||
* Requires at least: 6.2
|
||||
* Text Domain: test-plugin
|
||||
* Domain Path: /languages
|
||||
* License: GPL-2.0+
|
||||
* License URI: https://www.gnu.org/licenses/gpl-2.0.txt
|
||||
*/
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Plugin Name: My plugin
|
||||
* Plugin URI: https://myddelivery.com/
|
||||
* Description: Simple test plugin.
|
||||
* Author: EduardoVillao.me
|
||||
* Author URI: https://eduardovillao.me/
|
||||
* Version: 1.0
|
||||
* Requires PHP: 7.4
|
||||
* Text Domain: test-plugin
|
||||
* Domain Path: /languages
|
||||
* License: GPL-2.0+
|
||||
* License URI: https://www.gnu.org/licenses/gpl-2.0.txt
|
||||
*/
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
=== My Plugin ===
|
||||
Contributors: evcode
|
||||
Donate link: https://eduardovillao.me/
|
||||
Tags: test plugin
|
||||
Requires at least: 5.8
|
||||
Tested up to: 6.7
|
||||
Stable tag: 1.0
|
||||
Requires PHP: 7.4
|
||||
License: GPLv2License
|
||||
URI:https://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Plugin Name: My plugin
|
||||
* Plugin URI: https://myddelivery.com/
|
||||
* Description: Simple test plugin.
|
||||
* Author: EduardoVillao.me
|
||||
* Author URI: https://eduardovillao.me/
|
||||
* Version: 1.0
|
||||
* Requires PHP: 7.4
|
||||
* Text Domain: test-plugin
|
||||
* Domain Path: /languages
|
||||
* License: GPL-2.0+
|
||||
* License URI: https://www.gnu.org/licenses/gpl-2.0.txt
|
||||
*/
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
=== My Plugin ===
|
||||
Contributors: evcode
|
||||
Donate link: https://eduardovillao.me/
|
||||
Tags: test plugin
|
||||
Tested up to: 6.7
|
||||
Stable tag: 1.0
|
||||
Requires PHP: 7.4
|
||||
License: GPLv2License
|
||||
URI:https://www.gnu.org/licenses/gpl-2.0.html
|
||||
41271
vendor/eduardovillao/wp-since/wp-since.json
vendored
41271
vendor/eduardovillao/wp-since/wp-since.json
vendored
File diff suppressed because it is too large
Load diff
765
vendor/league/commonmark/CHANGELOG.md
vendored
765
vendor/league/commonmark/CHANGELOG.md
vendored
|
|
@ -1,765 +0,0 @@
|
|||
# Change Log
|
||||
All notable changes to this project will be documented in this file.
|
||||
Updates should follow the [Keep a CHANGELOG](https://keepachangelog.com/) principles.
|
||||
|
||||
**Upgrading from 1.x?** See <https://commonmark.thephpleague.com/2.0/upgrading/> for additional information.
|
||||
|
||||
## [Unreleased][unreleased]
|
||||
|
||||
## [2.8.0] - 2025-11-26
|
||||
|
||||
### Added
|
||||
- Added a new `HighlightExtension` for marking important text using `==` syntax (#1100)
|
||||
|
||||
### Fixed
|
||||
- Fixed `AutolinkExtension` incorrectly matching URLs after invalid `www.` prefix (#1095, #1103)
|
||||
|
||||
## [2.7.1] - 2025-07-20
|
||||
|
||||
### Changed
|
||||
- Optimized several regular expressions in `RegexHelper` to improve performance (#674, #1086)
|
||||
|
||||
### Fixed
|
||||
- `EmbedProcessor` no longer calls `updateEmbeds()` when there are no embeds to update (#1081)
|
||||
- Fixed missing `benchmark.php` CSV path validation for non-existent files (#1068, #1085)
|
||||
|
||||
## [2.7.0] - 2025-05-05
|
||||
|
||||
This is a **security release** to address a potential cross-site scripting (XSS) vulnerability when using the `AttributesExtension` with untrusted user input.
|
||||
|
||||
### Added
|
||||
- Added `attributes/allow` config option to specify which attributes users are allowed to set on elements (default allows virtually all attributes)
|
||||
|
||||
### Changed
|
||||
- The `AttributesExtension` blocks all attributes starting with `on` unless explicitly allowed via the `attributes/allow` config option
|
||||
- The `allow_unsafe_links` option is now respected by the `AttributesExtension` when users specify `href` and `src` attributes
|
||||
|
||||
## [2.6.2] - 2025-04-18
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed Attributes extension parsing regression (#1071)
|
||||
|
||||
## [2.6.1] - 2024-12-29
|
||||
|
||||
### Fixed
|
||||
|
||||
- Rendered list items should only add newlines around block-level children (#1059, #1061)
|
||||
|
||||
## [2.6.0] - 2024-12-07
|
||||
|
||||
This is a **security release** to address potential denial of service attacks when parsing specially crafted,
|
||||
malicious input from untrusted sources (like user input).
|
||||
|
||||
### Added
|
||||
|
||||
- Added `max_delimiters_per_line` config option to prevent denial of service attacks when parsing malicious input
|
||||
- Added `table/max_autocompleted_cells` config option to prevent denial of service attacks when parsing large tables
|
||||
- The `AttributesExtension` now supports attributes without values (#985, #986)
|
||||
- The `AutolinkExtension` exposes two new configuration options to override the default behavior (#969, #987):
|
||||
- `autolink/allowed_protocols` - an array of protocols to allow autolinking for
|
||||
- `autolink/default_protocol` - the default protocol to use when none is specified
|
||||
- Added `RegexHelper::isWhitespace()` method to check if a given character is an ASCII whitespace character
|
||||
- Added `CacheableDelimiterProcessorInterface` to ensure linear complexity for dynamic delimiter processing
|
||||
- Added `Bracket` delimiter type to optimize bracket parsing
|
||||
|
||||
### Changed
|
||||
|
||||
- `[` and `]` are no longer added as `Delimiter` objects on the stack; a new `Bracket` type with its own stack is used instead
|
||||
- `UrlAutolinkParser` no longer parses URLs with more than 127 subdomains
|
||||
- Expanded reference links can no longer exceed 100kb, or the size of the input document (whichever is greater)
|
||||
- Delimiters should always provide a non-null value via `DelimiterInterface::getIndex()`
|
||||
- We'll attempt to infer the index based on surrounding delimiters where possible
|
||||
- The `DelimiterStack` now accepts integer positions for any `$stackBottom` argument
|
||||
- Several small performance optimizations
|
||||
|
||||
## [2.5.3] - 2024-08-16
|
||||
|
||||
### Changed
|
||||
|
||||
- Made compatible with CommonMark spec 0.31.1, including:
|
||||
- Remove `source`, add `search` to list of recognized block tags
|
||||
|
||||
## [2.5.2] - 2024-08-14
|
||||
|
||||
### Changed
|
||||
|
||||
- Boolean attributes now require an explicit `true` value (#1040)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed regression where text could be misinterpreted as an attribute (#1040)
|
||||
|
||||
## [2.5.1] - 2024-07-24
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed attribute parsing incorrectly parsing mustache-like syntax (#1035)
|
||||
- Fixed incorrect `Table` start line numbers (#1037)
|
||||
|
||||
## [2.5.0] - 2024-07-22
|
||||
|
||||
### Added
|
||||
|
||||
- The `AttributesExtension` now supports attributes without values (#985, #986)
|
||||
- The `AutolinkExtension` exposes two new configuration options to override the default behavior (#969, #987):
|
||||
- `autolink/allowed_protocols` - an array of protocols to allow autolinking for
|
||||
- `autolink/default_protocol` - the default protocol to use when none is specified
|
||||
|
||||
### Changed
|
||||
|
||||
- Made compatible with CommonMark spec 0.31.0, including:
|
||||
- Allow closing fence to be followed by tabs
|
||||
- Remove restrictive limitation on inline comments
|
||||
- Unicode symbols now treated like punctuation (for purposes of flankingness)
|
||||
- Trailing tabs on the last line of indented code blocks will be excluded
|
||||
- Improved HTML comment matching
|
||||
- `Paragraph`s only containing link reference definitions will be kept in the AST until the `Document` is finalized
|
||||
- (These were previously removed immediately after parsing the `Paragraph`)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed list tightness not being determined properly in some edge cases
|
||||
- Fixed incorrect ending line numbers for several block types in various scenarios
|
||||
- Fixed lowercase inline HTML declarations not being accepted
|
||||
|
||||
## [2.4.4] - 2024-07-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed SmartPunct extension changing already-formatted quotation marks (#1030)
|
||||
|
||||
## [2.4.3] - 2024-07-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed the Attributes extension not supporting CSS level 3 selectors (#1013)
|
||||
- Fixed `UrlAutolinkParser` incorrectly parsing text containing `www` anywhere before an autolink (#1025)
|
||||
|
||||
|
||||
## [2.4.2] - 2024-02-02
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed declaration parser being too strict
|
||||
- `FencedCodeRenderer`: don't add `language-` to class if already prefixed
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Returning dynamic values from `DelimiterProcessorInterface::getDelimiterUse()` is deprecated
|
||||
- You should instead implement `CacheableDelimiterProcessorInterface` to help the engine perform caching to avoid performance issues.
|
||||
- Failing to set a delimiter's index (or returning `null` from `DelimiterInterface::getIndex()`) is deprecated and will not be supported in 3.0
|
||||
- Deprecated `DelimiterInterface::isActive()` and `DelimiterInterface::setActive()`, as these are no longer used by the engine
|
||||
- Deprecated `DelimiterStack::removeEarlierMatches()` and `DelimiterStack::searchByCharacter()`, as these are no longer used by the engine
|
||||
- Passing a `DelimiterInterface` as the `$stackBottom` argument to `DelimiterStack::processDelimiters()` or `::removeAll()` is deprecated and will not be supported in 3.0; pass the integer position instead.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed NUL characters not being replaced in the input
|
||||
- Fixed quadratic complexity parsing unclosed inline links
|
||||
- Fixed quadratic complexity parsing emphasis and strikethrough delimiters
|
||||
- Fixed issue where having 500,000+ delimiters could trigger a [known segmentation fault issue in PHP's garbage collection](https://bugs.php.net/bug.php?id=68606)
|
||||
- Fixed quadratic complexity deactivating link openers
|
||||
- Fixed quadratic complexity parsing long backtick code spans with no matching closers
|
||||
- Fixed catastrophic backtracking when parsing link labels/titles
|
||||
|
||||
## [2.4.1] - 2023-08-30
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `ExternalLinkProcessor` not fully disabling the `rel` attribute when configured to do so (#992)
|
||||
|
||||
## [2.4.0] - 2023-03-24
|
||||
|
||||
### Added
|
||||
|
||||
- Added generic `CommonMarkException` marker interface for all exceptions thrown by the library
|
||||
- Added several new specific exception types implementing that marker interface:
|
||||
- `AlreadyInitializedException`
|
||||
- `InvalidArgumentException`
|
||||
- `IOException`
|
||||
- `LogicException`
|
||||
- `MissingDependencyException`
|
||||
- `NoMatchingRendererException`
|
||||
- `ParserLogicException`
|
||||
- Added more configuration options to the Heading Permalinks extension (#939):
|
||||
- `heading_permalink/apply_id_to_heading` - When `true`, the `id` attribute will be applied to the heading element itself instead of the `<a>` tag
|
||||
- `heading_permalink/heading_class` - class to apply to the heading element
|
||||
- `heading_permalink/insert` - now accepts `none` to prevent the creation of the `<a>` link
|
||||
- Added new `table/alignment_attributes` configuration option to control how table cell alignment is rendered (#959)
|
||||
|
||||
### Changed
|
||||
|
||||
- Change several thrown exceptions from `RuntimeException` to `LogicException` (or something extending it), including:
|
||||
- `CallbackGenerator`s that fail to set a URL or return an expected value
|
||||
- `MarkdownParser` when deactivating the last block parser or attempting to get an active block parser when they've all been closed
|
||||
- Adding items to an already-initialized `Environment`
|
||||
- Rendering a `Node` when no renderer has been registered for it
|
||||
- `HeadingPermalinkProcessor` now throws `InvalidConfigurationException` instead of `RuntimeException` when invalid config values are given.
|
||||
- `HtmlElement::setAttribute()` no longer requires the second parameter for boolean attributes
|
||||
- Several small micro-optimizations
|
||||
- Changed Strikethrough to only allow 1 or 2 tildes per the updated GFM spec
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed inaccurate `@throws` docblocks throughout the codebase, including `ConverterInterface`, `MarkdownConverter`, and `MarkdownConverterInterface`.
|
||||
- These previously suggested that only `\RuntimeException`s were thrown, which was inaccurate as `\LogicException`s were also possible.
|
||||
|
||||
## [2.3.9] - 2023-02-15
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed autolink extension not detecting some URIs with underscores (#956)
|
||||
|
||||
## [2.3.8] - 2022-12-10
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed parsing issues when `mb_internal_encoding()` is set to something other than `UTF-8` (#951)
|
||||
|
||||
## [2.3.7] - 2022-11-03
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `TaskListItemMarkerRenderer` not including HTML attributes set on the node by other extensions (#947)
|
||||
|
||||
## [2.3.6] - 2022-10-30
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed unquoted attribute parsing when closing curly brace is followed by certain characters (like a `.`) (#943)
|
||||
|
||||
## [2.3.5] - 2022-07-29
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed error using `InlineParserEngine` when no inline parsers are registered in the `Environment` (#908)
|
||||
|
||||
## [2.3.4] - 2022-07-17
|
||||
|
||||
### Changed
|
||||
|
||||
- Made a number of small tweaks to the embed extension's parsing behavior to fix #898:
|
||||
- Changed `EmbedStartParser` to always capture embed-like lines in container blocks, regardless of parent block type
|
||||
- Changed `EmbedProcessor` to also remove `Embed` blocks that aren't direct children of the `Document`
|
||||
- Increased the priority of `EmbedProcessor` to `1010`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `EmbedExtension` not parsing embeds following a list block (#898)
|
||||
|
||||
## [2.3.3] - 2022-06-07
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `DomainFilteringAdapter` not reindexing the embed list (#884, #885)
|
||||
|
||||
## [2.3.2] - 2022-06-03
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed FootnoteExtension stripping extra characters from tab-indented footnotes (#881)
|
||||
|
||||
## [2.2.5] - 2022-06-03
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed FootnoteExtension stripping extra characters from tab-indented footnotes (#881)
|
||||
|
||||
## [2.3.1] - 2022-05-14
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed AutolinkExtension not ignoring trailing strikethrough syntax (#867)
|
||||
|
||||
## [2.2.4] - 2022-05-14
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed AutolinkExtension not ignoring trailing strikethrough syntax (#867)
|
||||
|
||||
## [2.3.0] - 2022-04-07
|
||||
|
||||
### Added
|
||||
|
||||
- Added new `EmbedExtension` (#805)
|
||||
- Added `DocumentRendererInterface` as a replacement for the now-deprecated `MarkdownRendererInterface`
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Deprecated `MarkdownRendererInterface`; use `DocumentRendererInterface` instead
|
||||
|
||||
## [2.2.3] - 2022-02-26
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed front matter parsing with Windows line endings (#821)
|
||||
|
||||
## [2.1.3] - 2022-02-26
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed front matter parsing with Windows line endings (#821)
|
||||
|
||||
## [2.0.4] - 2022-02-26
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed front matter parsing with Windows line endings (#821)
|
||||
|
||||
## [2.2.2] - 2022-02-13
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed double-escaping of image alt text (#806, #810)
|
||||
- Fixed Psalm typehints for event class names
|
||||
|
||||
## [2.2.1] - 2022-01-25
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `symfony/deprecation-contracts` constraint
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed deprecation trigger from `MarkdownConverterInterface` to reduce noise
|
||||
|
||||
## [2.2.0] - 2022-01-22
|
||||
|
||||
### Added
|
||||
|
||||
- Added new `ConverterInterface`
|
||||
- Added new `MarkdownToXmlConverter` class
|
||||
- Added new `HtmlDecorator` class which can wrap existing renderers with additional HTML tags
|
||||
- Added new `table/wrap` config to apply an optional wrapping/container element around a table (#780)
|
||||
|
||||
### Changed
|
||||
|
||||
- `HtmlElement` contents can now consist of any `Stringable`, not just `HtmlElement` and `string`
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Deprecated `MarkdownConverterInterface` and its `convertToHtml()` method; use `ConverterInterface` and `convert()` instead
|
||||
|
||||
## [2.1.2] - 2022-02-13
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed double-escaping of image alt text (#806, #810)
|
||||
- Fixed Psalm typehints for event class names
|
||||
|
||||
## [2.1.1] - 2022-01-02
|
||||
|
||||
### Added
|
||||
|
||||
- Added missing return type to `Environment::dispatch()` to fix deprecation warning (#778)
|
||||
|
||||
## [2.1.0] - 2021-12-05
|
||||
|
||||
### Added
|
||||
|
||||
- Added support for ext-yaml in FrontMatterExtension (#715)
|
||||
- Added support for symfony/yaml v6.0 in FrontMatterExtension (#739)
|
||||
- Added new `heading_permalink/aria_hidden` config option (#741)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed PHP 8.1 deprecation warning (#759, #762)
|
||||
|
||||
## [2.0.3] - 2022-02-13
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed double-escaping of image alt text (#806, #810)
|
||||
- Fixed Psalm typehints for event class names
|
||||
|
||||
## [2.0.2] - 2021-08-14
|
||||
|
||||
### Changed
|
||||
|
||||
- Bumped minimum version of league/config to support PHP 8.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed ability to register block parsers that identify lines starting with letters (#706)
|
||||
|
||||
## [2.0.1] - 2021-07-31
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed nested autolinks (#689)
|
||||
- Fixed description lists being parsed incorrectly (#692)
|
||||
- Fixed Table of Contents not respecting Heading Permalink prefixes (#690)
|
||||
|
||||
## [2.0.0] - 2021-07-24
|
||||
|
||||
No changes were introduced since the previous RC2 release.
|
||||
See all entries below for a list of changes between 1.x and 2.0.
|
||||
|
||||
## [2.0.0-rc2] - 2021-07-17
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed Mentions inside of links creating nested links against the spec's rules (#688)
|
||||
|
||||
## [2.0.0-rc1] - 2021-07-10
|
||||
|
||||
No changes were introduced since the previous release.
|
||||
|
||||
## [2.0.0-beta3] - 2021-07-03
|
||||
|
||||
### Changed
|
||||
|
||||
- Any leading UTF-8 BOM will be stripped from the input
|
||||
- The `getEnvironment()` method of `CommonMarkConverter` and `GithubFlavoredMarkdownConverter` will always return the concrete, configurable `Environment` for upgrading convenience
|
||||
- Optimized AST iteration
|
||||
- Lots of small micro-optimizations
|
||||
|
||||
## [2.0.0-beta2] - 2021-06-27
|
||||
|
||||
### Added
|
||||
|
||||
- Added new `Node::iterator()` method and `NodeIterator` class for faster AST iteration (#683, #684)
|
||||
|
||||
### Changed
|
||||
|
||||
- Made compatible with CommonMark spec 0.30.0
|
||||
- Optimized link label parsing
|
||||
- Optimized AST iteration for a 50% performance boost in some event listeners (#683, #684)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed processing instructions with EOLs
|
||||
- Fixed case-insensitive matching for HTML tag types
|
||||
- Fixed type 7 HTML blocks incorrectly interrupting lazy paragraphs
|
||||
- Fixed newlines in reference labels not collapsing into spaces
|
||||
- Fixed link label normalization with escaped newlines
|
||||
- Fixed unnecessary AST iteration when no default attributes are configured
|
||||
|
||||
## [2.0.0-beta1] - 2021-06-20
|
||||
|
||||
### Added
|
||||
|
||||
- **Added three new extensions:**
|
||||
- `FrontMatterExtension` ([see documentation](https://commonmark.thephpleague.com/extensions/front-matter/))
|
||||
- `DescriptionListExtension` ([see documentation](https://commonmark.thephpleague.com/extensions/description-lists/))
|
||||
- `DefaultAttributesExtension` ([see documentation](https://commonmark.thephpleague.com/extensions/default-attributes/))
|
||||
- **Added new `XmlRenderer` to simplify AST debugging** ([see documentation](https://commonmark.thephpleague.com/xml/)) (#431)
|
||||
- **Added the ability to configure disallowed raw HTML tags** (#507)
|
||||
- **Added the ability for Mentions to use multiple characters for their symbol** (#514, #550)
|
||||
- **Added the ability to delegate event dispatching to PSR-14 compliant event dispatcher libraries**
|
||||
- **Added new configuration options:**
|
||||
- Added `heading_permalink/min_heading_level` and `heading_permalink/max_heading_level` options to control which headings get permalinks (#519)
|
||||
- Added `heading_permalink/fragment_prefix` to allow customizing the URL fragment prefix (#602)
|
||||
- Added `footnote/backref_symbol` option for customizing backreference link appearance (#522)
|
||||
- Added `slug_normalizer/max_length` option to control the maximum length of generated URL slugs
|
||||
- Added `slug_normalizer/unique` option to control whether unique slugs should be generated per-document or per-environment
|
||||
- **Added purity markers throughout the codebase** (verified with Psalm)
|
||||
- Added `Query` class to simplify Node traversal when looking to take action on certain Nodes
|
||||
- Added new `HtmlFilter` and `StringContainerHelper` utility classes
|
||||
- Added new `AbstractBlockContinueParser` class to simplify the creation of custom block parsers
|
||||
- Added several new classes and interfaces:
|
||||
- `BlockContinue`
|
||||
- `BlockContinueParserInterface`
|
||||
- `BlockContinueParserWithInlinesInterface`
|
||||
- `BlockStart`
|
||||
- `BlockStartParserInterface`
|
||||
- `ChildNodeRendererInterface`
|
||||
- `ConfigurableExtensionInterface`
|
||||
- `CursorState`
|
||||
- `DashParser` (extracted from `PunctuationParser`)
|
||||
- `DelimiterParser`
|
||||
- `DocumentBlockParser`
|
||||
- `DocumentPreRenderEvent`
|
||||
- `DocumentRenderedEvent`
|
||||
- `EllipsesParser` (extracted from `PunctuationParser`)
|
||||
- `ExpressionInterface`
|
||||
- `FallbackNodeXmlRenderer`
|
||||
- `InlineParserEngineInterface`
|
||||
- `InlineParserMatch`
|
||||
- `MarkdownParserState`
|
||||
- `MarkdownParserStateInterface`
|
||||
- `MarkdownRendererInterface`
|
||||
- `Query`
|
||||
- `RawMarkupContainerInterface`
|
||||
- `ReferenceableInterface`
|
||||
- `RenderedContent`
|
||||
- `RenderedContentInterface`
|
||||
- `ReplaceUnpairedQuotesListener`
|
||||
- `SpecReader`
|
||||
- `TableOfContentsRenderer`
|
||||
- `UniqueSlugNormalizer`
|
||||
- `UniqueSlugNormalizerInterface`
|
||||
- `XmlRenderer`
|
||||
- `XmlNodeRendererInterface`
|
||||
- Added several new methods:
|
||||
- `Cursor::getCurrentCharacter()`
|
||||
- `Environment::createDefaultConfiguration()`
|
||||
- `Environment::setEventDispatcher()`
|
||||
- `EnvironmentInterface::getExtensions()`
|
||||
- `EnvironmentInterface::getInlineParsers()`
|
||||
- `EnvironmentInterface::getSlugNormalizer()`
|
||||
- `FencedCode::setInfo()`
|
||||
- `Heading::setLevel()`
|
||||
- `HtmlRenderer::renderDocument()`
|
||||
- `InlineParserContext::getFullMatch()`
|
||||
- `InlineParserContext::getFullMatchLength()`
|
||||
- `InlineParserContext::getMatches()`
|
||||
- `InlineParserContext::getSubMatches()`
|
||||
- `LinkParserHelper::parsePartialLinkLabel()`
|
||||
- `LinkParserHelper::parsePartialLinkTitle()`
|
||||
- `Node::assertInstanceOf()`
|
||||
- `RegexHelper::isLetter()`
|
||||
- `StringContainerInterface::setLiteral()`
|
||||
- `TableCell::getType()`
|
||||
- `TableCell::setType()`
|
||||
- `TableCell::getAlign()`
|
||||
- `TableCell::setAlign()`
|
||||
|
||||
### Changed
|
||||
|
||||
- **Changed the converter return type**
|
||||
- `CommonMarkConverter::convertToHtml()` now returns an instance of `RenderedContentInterface`. This can be cast to a string for backward compatibility with 1.x.
|
||||
- **Table of Contents items are no longer wrapped with `<p>` tags** (#613)
|
||||
- **Heading Permalinks now link to element IDs instead of using `name` attributes** (#602)
|
||||
- **Heading Permalink IDs and URL fragments now have a `content` prefix by default** (#602)
|
||||
- **Changes to configuration options:**
|
||||
- `enable_em` has been renamed to `commonmark/enable_em`
|
||||
- `enable_strong` has been renamed to `commonmark/enable_strong`
|
||||
- `use_asterisk` has been renamed to `commonmark/use_asterisk`
|
||||
- `use_underscore` has been renamed to `commonmark/use_underscore`
|
||||
- `unordered_list_markers` has been renamed to `commonmark/unordered_list_markers`
|
||||
- `mentions/*/symbol` has been renamed to `mentions/*/prefix`
|
||||
- `mentions/*/regex` has been renamed to `mentions/*/pattern` and requires partial regular expressions (without delimiters or flags)
|
||||
- `max_nesting_level` now defaults to `PHP_INT_MAX` and no longer supports floats
|
||||
- `heading_permalink/slug_normalizer` has been renamed to `slug_normalizer/instance`
|
||||
- **Event dispatching is now fully PSR-14 compliant**
|
||||
- **Moved and renamed several classes** - [see the full list here](https://commonmark.thephpleague.com/2.0/upgrading/#classesnamespaces-renamed)
|
||||
- The `HeadingPermalinkExtension` and `FootnoteExtension` were modified to ensure they never produce a slug which conflicts with slugs created by the other extension
|
||||
- `SlugNormalizer::normalizer()` now supports optional prefixes and max length options passed in via the `$context` argument
|
||||
- The `AbstractBlock::$data` and `AbstractInline::$data` arrays were replaced with a `Data` array-like object on the base `Node` class
|
||||
- **Implemented a new approach to block parsing.** This was a massive change, so here are the highlights:
|
||||
- Functionality previously found in block parsers and node elements has moved to block parser factories and block parsers, respectively ([more details](https://commonmark.thephpleague.com/2.0/upgrading/#new-block-parsing-approach))
|
||||
- `ConfigurableEnvironmentInterface::addBlockParser()` is now `EnvironmentBuilderInterface::addBlockParserFactory()`
|
||||
- `ReferenceParser` was re-implemented and works completely different than before
|
||||
- The paragraph parser no longer needs to be added manually to the environment
|
||||
- **Implemented a new approach to inline parsing** where parsers can now specify longer strings or regular expressions they want to parse (instead of just single characters):
|
||||
- `InlineParserInterface::getCharacters()` is now `getMatchDefinition()` and returns an instance of `InlineParserMatch`
|
||||
- `InlineParserContext::__construct()` now requires the contents to be provided as a `Cursor` instead of a `string`
|
||||
- **Implemented delimiter parsing as a special type of inline parser** (via the new `DelimiterParser` class)
|
||||
- **Changed block and inline rendering to use common methods and interfaces**
|
||||
- `BlockRendererInterface` and `InlineRendererInterface` were replaced by `NodeRendererInterface` with slightly different parameters. All core renderers now implement this interface.
|
||||
- `ConfigurableEnvironmentInterface::addBlockRenderer()` and `addInlineRenderer()` were combined into `EnvironmentBuilderInterface::addRenderer()`
|
||||
- `EnvironmentInterface::getBlockRenderersForClass()` and `getInlineRenderersForClass()` are now just `getRenderersForClass()`
|
||||
- **Completely refactored the Configuration implementation**
|
||||
- All configuration-specific classes have been moved into a new `league/config` package with a new namespace
|
||||
- `Configuration` objects must now be configured with a schema and all options must match that schema - arbitrary keys are no longer permitted
|
||||
- `Configuration::__construct()` no longer accepts the default configuration values - use `Configuration::merge()` instead
|
||||
- `ConfigurationInterface` now only contains a `get(string $key)`; this method no longer allows arbitrary default values to be returned if the option is missing
|
||||
- `ConfigurableEnvironmentInterface` was renamed to `EnvironmentBuilderInterface`
|
||||
- `ExtensionInterface::register()` now requires an `EnvironmentBuilderInterface` param instead of `ConfigurableEnvironmentInterface`
|
||||
- **Added missing return types to virtually every class and interface method**
|
||||
- Re-implemented the GFM Autolink extension using the new inline parser approach instead of document processors
|
||||
- `EmailAutolinkProcessor` is now `EmailAutolinkParser`
|
||||
- `UrlAutolinkProcessor` is now `UrlAutolinkParser`
|
||||
- `HtmlElement` can now properly handle array (i.e. `class`) and boolean (i.e. `checked`) attribute values
|
||||
- `HtmlElement` automatically flattens any attributes with array values into space-separated strings, removing duplicate entries
|
||||
- Combined separate classes/interfaces into one:
|
||||
- `DisallowedRawHtmlRenderer` replaces `DisallowedRawHtmlBlockRenderer` and `DisallowedRawHtmlInlineRenderer`
|
||||
- `NodeRendererInterface` replaces `BlockRendererInterface` and `InlineRendererInterface`
|
||||
- Renamed the following methods:
|
||||
- `Environment` and `ConfigurableEnvironmentInterface`:
|
||||
- `addBlockParser()` is now `addBlockStartParser()`
|
||||
- `ReferenceMap` and `ReferenceMapInterface`:
|
||||
- `addReference()` is now `add()`
|
||||
- `getReference()` is now `get()`
|
||||
- `listReferences()` is now `getIterator()`
|
||||
- Various node (block/inline) classes:
|
||||
- `getContent()` is now `getLiteral()`
|
||||
- `setContent()` is now `setLiteral()`
|
||||
- Moved and renamed the following constants:
|
||||
- `EnvironmentInterface::HTML_INPUT_ALLOW` is now `HtmlFilter::ALLOW`
|
||||
- `EnvironmentInterface::HTML_INPUT_ESCAPE` is now `HtmlFilter::ESCAPE`
|
||||
- `EnvironmentInterface::HTML_INPUT_STRIP` is now `HtmlFilter::STRIP`
|
||||
- `TableCell::TYPE_HEAD` is now `TableCell::TYPE_HEADER`
|
||||
- `TableCell::TYPE_BODY` is now `TableCell::TYPE_DATA`
|
||||
- Changed the visibility of the following properties:
|
||||
- `AttributesInline::$attributes` is now `private`
|
||||
- `AttributesInline::$block` is now `private`
|
||||
- `TableCell::$align` is now `private`
|
||||
- `TableCell::$type` is now `private`
|
||||
- `TableSection::$type` is now `private`
|
||||
- Several methods which previously returned `$this` now return `void`
|
||||
- `Delimiter::setPrevious()`
|
||||
- `Node::replaceChildren()`
|
||||
- `Context::setTip()`
|
||||
- `Context::setContainer()`
|
||||
- `Context::setBlocksParsed()`
|
||||
- `AbstractStringContainer::setContent()`
|
||||
- `AbstractWebResource::setUrl()`
|
||||
- Several classes are now marked `final`:
|
||||
- `ArrayCollection`
|
||||
- `Emphasis`
|
||||
- `FencedCode`
|
||||
- `Heading`
|
||||
- `HtmlBlock`
|
||||
- `HtmlElement`
|
||||
- `HtmlInline`
|
||||
- `IndentedCode`
|
||||
- `Newline`
|
||||
- `Strikethrough`
|
||||
- `Strong`
|
||||
- `Text`
|
||||
- `Heading` nodes no longer directly contain a copy of their inner text
|
||||
- `StringContainerInterface` can now be used for inlines, not just blocks
|
||||
- `ArrayCollection` only supports integer keys
|
||||
- `HtmlElement` now implements `Stringable`
|
||||
- `Cursor::saveState()` and `Cursor::restoreState()` now use `CursorState` objects instead of arrays
|
||||
- `NodeWalker::next()` now enters, traverses any children, and leaves all elements which may have children (basically all blocks plus any inlines with children). Previously, it only did this for elements explicitly marked as "containers".
|
||||
- `InvalidOptionException` was removed
|
||||
- Anything with a `getReference(): ReferenceInterface` method now implements `ReferencableInterface`
|
||||
- The `SmartPunct` extension now replaces all unpaired `Quote` elements with `Text` elements towards the end of parsing, making the `QuoteRenderer` unnecessary
|
||||
- Several changes made to the Footnote extension:
|
||||
- Footnote identifiers can no longer contain spaces
|
||||
- Anonymous footnotes can now span subsequent lines
|
||||
- Footnotes can now contain multiple lines of content, including sub-blocks, by indenting them
|
||||
- Footnote event listeners now have numbered priorities (but still execute in the same order)
|
||||
- Footnotes must now be separated from previous content by a blank line
|
||||
- The line numbers (keys) returned via `MarkdownInput::getLines()` now start at 1 instead of 0
|
||||
- `DelimiterProcessorCollectionInterface` now extends `Countable`
|
||||
- `RegexHelper::PARTIAL_` constants must always be used in case-insensitive contexts
|
||||
- `HeadingPermalinkProcessor` no longer accepts text normalizers via the constructor - these must be provided via configuration instead
|
||||
- Blocks which can't contain inlines will no longer be asked to render inlines
|
||||
- `AnonymousFootnoteRefParser` and `HeadingPermalinkProcessor` now implement `EnvironmentAwareInterface` instead of `ConfigurationAwareInterface`
|
||||
- The second argument to `TextNormalizerInterface::normalize()` must now be an array
|
||||
- The `title` attribute for `Link` and `Image` nodes is now stored using a dedicated property instead of stashing it in `$data`
|
||||
- `ListData::$delimiter` now returns either `ListBlock::DELIM_PERIOD` or `ListBlock::DELIM_PAREN` instead of the literal delimiter
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Fixed parsing of footnotes without content**
|
||||
- **Fixed rendering of orphaned footnotes and footnote refs**
|
||||
- **Fixed some URL autolinks breaking too early** (#492)
|
||||
- Fixed `AbstractStringContainer` not actually being `abstract`
|
||||
|
||||
### Removed
|
||||
|
||||
- **Removed support for PHP 7.1, 7.2, and 7.3** (#625, #671)
|
||||
- **Removed all previously-deprecated functionality:**
|
||||
- Removed the ability to pass custom `Environment` instances into the `CommonMarkConverter` and `GithubFlavoredMarkdownConverter` constructors
|
||||
- Removed the `Converter` class and `ConverterInterface`
|
||||
- Removed the `bin/commonmark` script
|
||||
- Removed the `Html5Entities` utility class
|
||||
- Removed the `InlineMentionParser` (use `MentionParser` instead)
|
||||
- Removed `DefaultSlugGenerator` and `SlugGeneratorInterface` from the `Extension/HeadingPermalink/Slug` sub-namespace (use the new ones under `./SlugGenerator` instead)
|
||||
- Removed the following `ArrayCollection` methods:
|
||||
- `add()`
|
||||
- `set()`
|
||||
- `get()`
|
||||
- `remove()`
|
||||
- `isEmpty()`
|
||||
- `contains()`
|
||||
- `indexOf()`
|
||||
- `containsKey()`
|
||||
- `replaceWith()`
|
||||
- `removeGaps()`
|
||||
- Removed the `ConfigurableEnvironmentInterface::setConfig()` method
|
||||
- Removed the `ListBlock::TYPE_UNORDERED` constant
|
||||
- Removed the `CommonMarkConverter::VERSION` constant
|
||||
- Removed the `HeadingPermalinkRenderer::DEFAULT_INNER_CONTENTS` constant
|
||||
- Removed the `heading_permalink/inner_contents` configuration option
|
||||
- **Removed now-unused classes:**
|
||||
- `AbstractStringContainerBlock`
|
||||
- `BlockRendererInterface`
|
||||
- `Context`
|
||||
- `ContextInterface`
|
||||
- `Converter`
|
||||
- `ConverterInterface`
|
||||
- `InlineRendererInterface`
|
||||
- `PunctuationParser` (was split into two classes: `DashParser` and `EllipsesParser`)
|
||||
- `QuoteRenderer`
|
||||
- `UnmatchedBlockCloser`
|
||||
- Removed the following methods, properties, and constants:
|
||||
- `AbstractBlock::$open`
|
||||
- `AbstractBlock::$lastLineBlank`
|
||||
- `AbstractBlock::isContainer()`
|
||||
- `AbstractBlock::canContain()`
|
||||
- `AbstractBlock::isCode()`
|
||||
- `AbstractBlock::matchesNextLine()`
|
||||
- `AbstractBlock::endsWithBlankLine()`
|
||||
- `AbstractBlock::setLastLineBlank()`
|
||||
- `AbstractBlock::shouldLastLineBeBlank()`
|
||||
- `AbstractBlock::isOpen()`
|
||||
- `AbstractBlock::finalize()`
|
||||
- `AbstractBlock::getData()`
|
||||
- `AbstractInline::getData()`
|
||||
- `ConfigurableEnvironmentInterface::addBlockParser()`
|
||||
- `ConfigurableEnvironmentInterface::mergeConfig()`
|
||||
- `Delimiter::setCanClose()`
|
||||
- `EnvironmentInterface::getConfig()`
|
||||
- `EnvironmentInterface::getInlineParsersForCharacter()`
|
||||
- `EnvironmentInterface::getInlineParserCharacterRegex()`
|
||||
- `HtmlRenderer::renderBlock()`
|
||||
- `HtmlRenderer::renderBlocks()`
|
||||
- `HtmlRenderer::renderInline()`
|
||||
- `HtmlRenderer::renderInlines()`
|
||||
- `Node::isContainer()`
|
||||
- `RegexHelper::matchAll()` (use the new `matchFirst()` method instead)
|
||||
- `RegexHelper::REGEX_WHITESPACE`
|
||||
- Removed the second `$contents` argument from the `Heading` constructor
|
||||
|
||||
### Deprecated
|
||||
|
||||
**The following things have been deprecated and will not be supported in v3.0:**
|
||||
|
||||
- `Environment::mergeConfig()` (set configuration before instantiation instead)
|
||||
- `Environment::createCommonMarkEnvironment()` and `Environment::createGFMEnvironment()`
|
||||
- Alternative 1: Use `CommonMarkConverter` or `GithubFlavoredMarkdownConverter` if you don't need to customize the environment
|
||||
- Alternative 2: Instantiate a new `Environment` and add the necessary extensions yourself
|
||||
|
||||
[unreleased]: https://github.com/thephpleague/commonmark/compare/2.8.0...HEAD
|
||||
[2.8.0]: https://github.com/thephpleague/commonmark/compare/2.7.1...2.8.0
|
||||
[2.7.1]: https://github.com/thephpleague/commonmark/compare/2.7.0...2.7.1
|
||||
[2.7.0]: https://github.com/thephpleague/commonmark/compare/2.6.2...2.7.0
|
||||
[2.6.2]: https://github.com/thephpleague/commonmark/compare/2.6.1...2.6.2
|
||||
[2.6.1]: https://github.com/thephpleague/commonmark/compare/2.6.0...2.6.1
|
||||
[2.6.0]: https://github.com/thephpleague/commonmark/compare/2.5.3...2.6.0
|
||||
[2.5.3]: https://github.com/thephpleague/commonmark/compare/2.5.2...2.5.3
|
||||
[2.5.2]: https://github.com/thephpleague/commonmark/compare/2.5.1...2.5.2
|
||||
[2.5.1]: https://github.com/thephpleague/commonmark/compare/2.5.0...2.5.1
|
||||
[2.5.0]: https://github.com/thephpleague/commonmark/compare/2.4.4...2.5.0
|
||||
[2.4.4]: https://github.com/thephpleague/commonmark/compare/2.4.3...2.4.4
|
||||
[2.4.3]: https://github.com/thephpleague/commonmark/compare/2.4.2...2.4.3
|
||||
[2.4.2]: https://github.com/thephpleague/commonmark/compare/2.4.1...2.4.2
|
||||
[2.4.1]: https://github.com/thephpleague/commonmark/compare/2.4.0...2.4.1
|
||||
[2.4.0]: https://github.com/thephpleague/commonmark/compare/2.3.9...2.4.0
|
||||
[2.3.9]: https://github.com/thephpleague/commonmark/compare/2.3.8...2.3.9
|
||||
[2.3.8]: https://github.com/thephpleague/commonmark/compare/2.3.7...2.3.8
|
||||
[2.3.7]: https://github.com/thephpleague/commonmark/compare/2.3.6...2.3.7
|
||||
[2.3.6]: https://github.com/thephpleague/commonmark/compare/2.3.5...2.3.6
|
||||
[2.3.5]: https://github.com/thephpleague/commonmark/compare/2.3.4...2.3.5
|
||||
[2.3.4]: https://github.com/thephpleague/commonmark/compare/2.3.3...2.3.4
|
||||
[2.3.3]: https://github.com/thephpleague/commonmark/compare/2.3.2...2.3.3
|
||||
[2.3.2]: https://github.com/thephpleague/commonmark/compare/2.3.2...main
|
||||
[2.3.1]: https://github.com/thephpleague/commonmark/compare/2.3.0...2.3.1
|
||||
[2.3.0]: https://github.com/thephpleague/commonmark/compare/2.2.3...2.3.0
|
||||
[2.2.5]: https://github.com/thephpleague/commonmark/compare/2.2.4...2.2.5
|
||||
[2.2.4]: https://github.com/thephpleague/commonmark/compare/2.2.3...2.2.4
|
||||
[2.2.3]: https://github.com/thephpleague/commonmark/compare/2.2.2...2.2.3
|
||||
[2.2.2]: https://github.com/thephpleague/commonmark/compare/2.2.1...2.2.2
|
||||
[2.2.1]: https://github.com/thephpleague/commonmark/compare/2.2.0...2.2.1
|
||||
[2.2.0]: https://github.com/thephpleague/commonmark/compare/2.1.1...2.2.0
|
||||
[2.1.3]: https://github.com/thephpleague/commonmark/compare/2.1.2...2.1.3
|
||||
[2.1.2]: https://github.com/thephpleague/commonmark/compare/2.1.1...2.1.2
|
||||
[2.1.1]: https://github.com/thephpleague/commonmark/compare/2.0.2...2.1.1
|
||||
[2.1.0]: https://github.com/thephpleague/commonmark/compare/2.0.2...2.1.0
|
||||
[2.0.4]: https://github.com/thephpleague/commonmark/compare/2.0.3...2.0.4
|
||||
[2.0.3]: https://github.com/thephpleague/commonmark/compare/2.0.2...2.0.3
|
||||
[2.0.2]: https://github.com/thephpleague/commonmark/compare/2.0.1...2.0.2
|
||||
[2.0.1]: https://github.com/thephpleague/commonmark/compare/2.0.0...2.0.1
|
||||
[2.0.0]: https://github.com/thephpleague/commonmark/compare/2.0.0-rc2...2.0.0
|
||||
[2.0.0-rc2]: https://github.com/thephpleague/commonmark/compare/2.0.0-rc1...2.0.0-rc2
|
||||
[2.0.0-rc1]: https://github.com/thephpleague/commonmark/compare/2.0.0-beta3...2.0.0-rc1
|
||||
[2.0.0-beta3]: https://github.com/thephpleague/commonmark/compare/2.0.0-beta2...2.0.0-beta3
|
||||
[2.0.0-beta2]: https://github.com/thephpleague/commonmark/compare/2.0.0-beta1...2.0.0-beta2
|
||||
[2.0.0-beta1]: https://github.com/thephpleague/commonmark/compare/1.6...2.0.0-beta1
|
||||
225
vendor/league/commonmark/README.md
vendored
225
vendor/league/commonmark/README.md
vendored
|
|
@ -1,225 +0,0 @@
|
|||
# league/commonmark
|
||||
|
||||
[](https://packagist.org/packages/league/commonmark)
|
||||
[](https://packagist.org/packages/league/commonmark)
|
||||
[](LICENSE)
|
||||
[](https://github.com/thephpleague/commonmark/actions?query=workflow%3ATests+branch%3Amain)
|
||||
[](https://scrutinizer-ci.com/g/thephpleague/commonmark/code-structure)
|
||||
[](https://scrutinizer-ci.com/g/thephpleague/commonmark)
|
||||
[](https://shepherd.dev/github/thephpleague/commonmark)
|
||||
[](https://bestpractices.coreinfrastructure.org/projects/126)
|
||||
[](https://www.colinodell.com/sponsor)
|
||||
|
||||

|
||||
|
||||
**league/commonmark** is a highly-extensible PHP Markdown parser created by [Colin O'Dell][@colinodell] which supports the full [CommonMark] spec and [GitHub-Flavored Markdown]. It is based on the [CommonMark JS reference implementation][commonmark.js] by [John MacFarlane] \([@jgm]\).
|
||||
|
||||
## 📦 Installation & Basic Usage
|
||||
|
||||
This project requires PHP 7.4 or higher with the `mbstring` extension. To install it via [Composer] simply run:
|
||||
|
||||
``` bash
|
||||
$ composer require league/commonmark
|
||||
```
|
||||
|
||||
The `CommonMarkConverter` class provides a simple wrapper for converting CommonMark to HTML:
|
||||
|
||||
```php
|
||||
use League\CommonMark\CommonMarkConverter;
|
||||
|
||||
$converter = new CommonMarkConverter([
|
||||
'html_input' => 'strip',
|
||||
'allow_unsafe_links' => false,
|
||||
]);
|
||||
|
||||
echo $converter->convert('# Hello World!');
|
||||
|
||||
// <h1>Hello World!</h1>
|
||||
```
|
||||
|
||||
Or if you want GitHub-Flavored Markdown, use the `GithubFlavoredMarkdownConverter` class instead:
|
||||
|
||||
```php
|
||||
use League\CommonMark\GithubFlavoredMarkdownConverter;
|
||||
|
||||
$converter = new GithubFlavoredMarkdownConverter([
|
||||
'html_input' => 'strip',
|
||||
'allow_unsafe_links' => false,
|
||||
]);
|
||||
|
||||
echo $converter->convert('# Hello World!');
|
||||
|
||||
// <h1>Hello World!</h1>
|
||||
```
|
||||
|
||||
Please note that only UTF-8 and ASCII encodings are supported. If your Markdown uses a different encoding please convert it to UTF-8 before running it through this library.
|
||||
|
||||
> [!CAUTION]
|
||||
> If you will be parsing untrusted input from users, please consider setting the `html_input` and `allow_unsafe_links` options per the example above. See <https://commonmark.thephpleague.com/security/> for more details. If you also do choose to allow raw HTML input from untrusted users, consider using a library (like [HTML Purifier](https://github.com/ezyang/htmlpurifier)) to provide additional HTML filtering.
|
||||
|
||||
## 📓 Documentation
|
||||
|
||||
Full documentation on advanced usage, configuration, and customization can be found at [commonmark.thephpleague.com][docs].
|
||||
|
||||
## ⏫ Upgrading
|
||||
|
||||
Information on how to upgrade to newer versions of this library can be found at <https://commonmark.thephpleague.com/releases>.
|
||||
|
||||
## 💻 GitHub-Flavored Markdown
|
||||
|
||||
The `GithubFlavoredMarkdownConverter` shown earlier is a drop-in replacement for the `CommonMarkConverter` which adds additional features found in the GFM spec:
|
||||
|
||||
- Autolinks
|
||||
- Disallowed raw HTML
|
||||
- Strikethrough
|
||||
- Tables
|
||||
- Task Lists
|
||||
|
||||
See the [Extensions documentation](https://commonmark.thephpleague.com/customization/extensions/) for more details on how to include only certain GFM features if you don't want them all.
|
||||
|
||||
## 🗃️ Related Packages
|
||||
|
||||
### Integrations
|
||||
|
||||
- [CakePHP 3](https://github.com/gourmet/common-mark)
|
||||
- [Drupal](https://www.drupal.org/project/markdown)
|
||||
- [Laravel 4+](https://github.com/GrahamCampbell/Laravel-Markdown)
|
||||
- [Sculpin](https://github.com/bcremer/sculpin-commonmark-bundle)
|
||||
- [Symfony 2 & 3](https://github.com/webuni/commonmark-bundle)
|
||||
- [Symfony 4](https://github.com/avensome/commonmark-bundle)
|
||||
- [Twig Markdown extension](https://github.com/twigphp/markdown-extension)
|
||||
- [Twig filter and tag](https://github.com/aptoma/twig-markdown)
|
||||
- [Laravel CommonMark Blog](https://github.com/spekulatius/laravel-commonmark-blog)
|
||||
|
||||
### Included Extensions
|
||||
|
||||
See [our extension documentation](https://commonmark.thephpleague.com/extensions/overview) for a full list of extensions bundled with this library.
|
||||
|
||||
### Community Extensions
|
||||
|
||||
Custom parsers/renderers can be bundled into extensions which extend CommonMark. Here are some that you may find interesting:
|
||||
|
||||
- [Emoji extension](https://github.com/ElGigi/CommonMarkEmoji) - UTF-8 emoji extension with Github tag.
|
||||
- [Sup Sub extensions](https://github.com/OWS/commonmark-sup-sub-extensions) - Adds support of superscript and subscript (`<sup>` and `<sub>` HTML tags).
|
||||
- [YouTube iframe extension](https://github.com/zoonru/commonmark-ext-youtube-iframe) - Replaces youtube link with iframe.
|
||||
- [Lazy Image extension](https://github.com/simonvomeyser/commonmark-ext-lazy-image) - Adds various options for lazy loading of images.
|
||||
- [Marker Extension](https://github.com/noah1400/commonmark-marker-extension) - Adds support of highlighted text (`<mark>` HTML tag).
|
||||
- [Pygments Highlighter extension](https://github.com/DanielEScherzer/commonmark-ext-pygments-highlighter) - Adds support for highlighting code with the Pygments library.
|
||||
- [LatexRenderer extension](https://github.com/samwilson/commonmark-latex) - For rendering Markdown to LaTeX.
|
||||
|
||||
Others can be found on [Packagist under the `commonmark-extension` package type](https://packagist.org/packages/league/commonmark?type=commonmark-extension).
|
||||
|
||||
If you build your own, feel free to submit a PR to add it to this list!
|
||||
|
||||
### Others
|
||||
|
||||
Check out the other cool things people are doing with `league/commonmark`: <https://packagist.org/packages/league/commonmark/dependents>
|
||||
|
||||
## 🏷️ Versioning
|
||||
|
||||
[SemVer](http://semver.org/) is followed closely. Minor and patch releases should not introduce breaking changes to the codebase; however, they might change the resulting AST or HTML output of parsed Markdown (due to bug fixes, spec changes, etc.) As a result, you might get slightly different HTML, but any custom code built onto this library should still function correctly.
|
||||
|
||||
Any classes or methods marked `@internal` are not intended for use outside of this library and are subject to breaking changes at any time, so please avoid using them.
|
||||
|
||||
## 🛠️ Maintenance & Support
|
||||
|
||||
When a new **minor** version (e.g. `2.0` -> `2.1`) is released, the previous one (`2.0`) will continue to receive security and critical bug fixes for *at least* 3 months.
|
||||
|
||||
When a new **major** version is released (e.g. `1.6` -> `2.0`), the previous one (`1.6`) will receive critical bug fixes for *at least* 3 months and security updates for 6 months after that new release comes out.
|
||||
|
||||
(This policy may change in the future and exceptions may be made on a case-by-case basis.)
|
||||
|
||||
**Professional support, including notification of new releases and security updates, is available through a [Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-league-commonmark?utm_source=packagist-league-commonmark&utm_medium=referral&utm_campaign=readme).**
|
||||
|
||||
## 👷♀️ Contributing
|
||||
|
||||
To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure with us.
|
||||
|
||||
If you encounter a bug in the spec, please report it to the [CommonMark] project. Any resulting fix will eventually be implemented in this project as well.
|
||||
|
||||
Contributions to this library are **welcome**, especially ones that:
|
||||
|
||||
* Improve usability or flexibility without compromising our ability to adhere to the [CommonMark spec]
|
||||
* Mirror fixes made to the [reference implementation][commonmark.js]
|
||||
* Optimize performance
|
||||
* Fix issues with adhering to the [CommonMark spec]
|
||||
|
||||
Major refactoring to core parsing logic should be avoided if possible so that we can easily follow updates made to [the reference implementation][commonmark.js]. That being said, we will absolutely consider changes which don't deviate too far from the reference spec or which are favored by other popular CommonMark implementations.
|
||||
|
||||
Please see [CONTRIBUTING](https://github.com/thephpleague/commonmark/blob/main/.github/CONTRIBUTING.md) for additional details.
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
``` bash
|
||||
$ composer test
|
||||
```
|
||||
|
||||
This will also test league/commonmark against the latest supported spec.
|
||||
|
||||
## 🚀 Performance Benchmarks
|
||||
|
||||
You can compare the performance of **league/commonmark** to other popular parsers by running the included benchmark tool:
|
||||
|
||||
``` bash
|
||||
$ ./tests/benchmark/benchmark.php
|
||||
```
|
||||
|
||||
## 👥 Credits & Acknowledgements
|
||||
|
||||
This code was originally based on the [CommonMark JS reference implementation][commonmark.js] which is written, maintained, and copyrighted by [John MacFarlane]. This project simply wouldn't exist without his work.
|
||||
|
||||
And a huge thanks to all of our amazing contributors:
|
||||
|
||||
<a href="https://github.com/thephpleague/commonmark/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=thephpleague/commonmark" />
|
||||
</a>
|
||||
|
||||
### Sponsors
|
||||
|
||||
We'd also like to extend our sincere thanks the following sponsors who support ongoing development of this project:
|
||||
|
||||
- [Tidelift](https://tidelift.com/subscription/pkg/packagist-league-commonmark?utm_source=packagist-league-commonmark&utm_medium=referral&utm_campaign=readme) for offering support to both the maintainers and end-users through their [professional support](https://tidelift.com/subscription/pkg/packagist-league-commonmark?utm_source=packagist-league-commonmark&utm_medium=referral&utm_campaign=readme) program
|
||||
- [Blackfire](https://www.blackfire.io/) for providing an Open-Source Profiler subscription
|
||||
- [JetBrains](https://www.jetbrains.com/) for supporting this project with complimentary [PhpStorm](https://www.jetbrains.com/phpstorm/) licenses
|
||||
|
||||
Are you interested in sponsoring development of this project? See <https://www.colinodell.com/sponsor> for a list of ways to contribute.
|
||||
|
||||
## 📄 License
|
||||
|
||||
**league/commonmark** is licensed under the BSD-3 license. See the [`LICENSE`](LICENSE) file for more details.
|
||||
|
||||
## 🏛️ Governance
|
||||
|
||||
This project is primarily maintained by [Colin O'Dell][@colinodell]. Members of the [PHP League] Leadership Team may occasionally assist with some of these duties.
|
||||
|
||||
## 🗺️ Who Uses It?
|
||||
|
||||
This project is used by [Drupal](https://www.drupal.org/project/markdown), [Laravel Framework](https://laravel.com/), [Cachet](https://cachethq.io/), [Firefly III](https://firefly-iii.org/), [Neos](https://www.neos.io/), [Daux.io](https://daux.io/), and [more](https://packagist.org/packages/league/commonmark/dependents)!
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<b>
|
||||
<a href="https://tidelift.com/subscription/pkg/packagist-league-commonmark?utm_source=packagist-league-commonmark&utm_medium=referral&utm_campaign=readme">Get professional support for league/commonmark with a Tidelift subscription</a>
|
||||
</b>
|
||||
<br>
|
||||
<sub>
|
||||
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
|
||||
</sub>
|
||||
</div>
|
||||
|
||||
[CommonMark]: http://commonmark.org/
|
||||
[CommonMark spec]: http://spec.commonmark.org/
|
||||
[commonmark.js]: https://github.com/jgm/commonmark.js
|
||||
[GitHub-Flavored Markdown]: https://github.github.com/gfm/
|
||||
[John MacFarlane]: http://johnmacfarlane.net
|
||||
[docs]: https://commonmark.thephpleague.com/
|
||||
[docs-examples]: https://commonmark.thephpleague.com/customization/overview/#examples
|
||||
[docs-example-twitter]: https://commonmark.thephpleague.com/customization/inline-parsing#example-1---twitter-handles
|
||||
[docs-example-smilies]: https://commonmark.thephpleague.com/customization/inline-parsing#example-2---emoticons
|
||||
[All Contributors]: https://github.com/thephpleague/commonmark/contributors
|
||||
[@colinodell]: https://www.twitter.com/colinodell
|
||||
[@jgm]: https://github.com/jgm
|
||||
[jgm/stmd]: https://github.com/jgm/stmd
|
||||
[Composer]: https://getcomposer.org/
|
||||
[PHP League]: https://thephpleague.com
|
||||
129
vendor/league/commonmark/composer.json
vendored
129
vendor/league/commonmark/composer.json
vendored
|
|
@ -1,129 +0,0 @@
|
|||
{
|
||||
"name": "league/commonmark",
|
||||
"type": "library",
|
||||
"description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)",
|
||||
"keywords": ["markdown","parser","commonmark","gfm","github","flavored","github-flavored","md"],
|
||||
"homepage": "https://commonmark.thephpleague.com",
|
||||
"license": "BSD-3-Clause",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Colin O'Dell",
|
||||
"email": "colinodell@gmail.com",
|
||||
"homepage": "https://www.colinodell.com",
|
||||
"role": "Lead Developer"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://commonmark.thephpleague.com/",
|
||||
"forum": "https://github.com/thephpleague/commonmark/discussions",
|
||||
"issues": "https://github.com/thephpleague/commonmark/issues",
|
||||
"rss": "https://github.com/thephpleague/commonmark/releases.atom",
|
||||
"source": "https://github.com/thephpleague/commonmark"
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0",
|
||||
"ext-mbstring": "*",
|
||||
"league/config": "^1.1.1",
|
||||
"psr/event-dispatcher": "^1.0",
|
||||
"symfony/deprecation-contracts": "^2.1 || ^3.0",
|
||||
"symfony/polyfill-php80": "^1.16"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-json": "*",
|
||||
"cebe/markdown": "^1.0",
|
||||
"commonmark/cmark": "0.31.1",
|
||||
"commonmark/commonmark.js": "0.31.1",
|
||||
"composer/package-versions-deprecated": "^1.8",
|
||||
"embed/embed": "^4.4",
|
||||
"erusev/parsedown": "^1.0",
|
||||
"github/gfm": "0.29.0",
|
||||
"michelf/php-markdown": "^1.4 || ^2.0",
|
||||
"nyholm/psr7": "^1.5",
|
||||
"phpstan/phpstan": "^1.8.2",
|
||||
"phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0",
|
||||
"scrutinizer/ocular": "^1.8.1",
|
||||
"symfony/finder": "^5.3 | ^6.0 | ^7.0",
|
||||
"symfony/process": "^5.4 | ^6.0 | ^7.0",
|
||||
"symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0",
|
||||
"unleashedtech/php-coding-standard": "^3.1.1",
|
||||
"vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0"
|
||||
},
|
||||
"minimum-stability": "beta",
|
||||
"suggest": {
|
||||
"symfony/yaml": "v2.3+ required if using the Front Matter extension"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "commonmark/commonmark.js",
|
||||
"version": "0.31.1",
|
||||
"dist": {
|
||||
"url": "https://github.com/commonmark/commonmark.js/archive/0.31.1.zip",
|
||||
"type": "zip"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "commonmark/cmark",
|
||||
"version": "0.31.1",
|
||||
"dist": {
|
||||
"url": "https://github.com/commonmark/cmark/archive/0.31.1.zip",
|
||||
"type": "zip"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "github/gfm",
|
||||
"version": "0.29.0",
|
||||
"dist": {
|
||||
"url": "https://github.com/github/cmark-gfm/archive/0.29.0.gfm.13.zip",
|
||||
"type": "zip"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"League\\CommonMark\\": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"League\\CommonMark\\Tests\\Unit\\": "tests/unit",
|
||||
"League\\CommonMark\\Tests\\Functional\\": "tests/functional",
|
||||
"League\\CommonMark\\Tests\\PHPStan\\": "tests/phpstan"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"phpcs": "phpcs",
|
||||
"phpcbf": "phpcbf",
|
||||
"phpstan": "phpstan analyse",
|
||||
"phpunit": "phpunit --no-coverage",
|
||||
"psalm": "psalm --stats",
|
||||
"pathological": "tests/pathological/test.php",
|
||||
"test": [
|
||||
"@phpcs",
|
||||
"@phpstan",
|
||||
"@psalm",
|
||||
"@phpunit",
|
||||
"@pathological"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "2.9-dev"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"composer/package-versions-deprecated": true,
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
},
|
||||
"sort-packages": true
|
||||
}
|
||||
}
|
||||
|
|
@ -45,7 +45,7 @@ final class DisallowedRawHtmlRenderer implements NodeRendererInterface, Configur
|
|||
return $rendered;
|
||||
}
|
||||
|
||||
$regex = \sprintf('/<(\/?(?:%s)[ \/>])/i', \implode('|', \array_map('preg_quote', $tags)));
|
||||
$regex = \sprintf('/<(\/?(?:%s)[\s\/>])/i', \implode('|', \array_map('preg_quote', $tags)));
|
||||
|
||||
// Match these types of tags: <title> </title> <title x="sdf"> <title/> <title />
|
||||
return \preg_replace($regex, '<$1', $rendered);
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ class DomainFilteringAdapter implements EmbedAdapterInterface
|
|||
{
|
||||
private EmbedAdapterInterface $decorated;
|
||||
|
||||
/** @psalm-var non-empty-string */
|
||||
private string $regex;
|
||||
/** @var string[] */
|
||||
private array $allowedDomains;
|
||||
|
||||
/**
|
||||
* @param string[] $allowedDomains
|
||||
|
|
@ -26,7 +26,7 @@ class DomainFilteringAdapter implements EmbedAdapterInterface
|
|||
public function __construct(EmbedAdapterInterface $decorated, array $allowedDomains)
|
||||
{
|
||||
$this->decorated = $decorated;
|
||||
$this->regex = self::createRegex($allowedDomains);
|
||||
$this->allowedDomains = \array_map('strtolower', $allowedDomains);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -34,20 +34,29 @@ class DomainFilteringAdapter implements EmbedAdapterInterface
|
|||
*/
|
||||
public function updateEmbeds(array $embeds): void
|
||||
{
|
||||
$this->decorated->updateEmbeds(\array_values(\array_filter($embeds, function (Embed $embed): bool {
|
||||
return \preg_match($this->regex, $embed->getUrl()) === 1;
|
||||
})));
|
||||
$this->decorated->updateEmbeds(\array_values(\array_filter($embeds, [$this, 'isAllowed'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $allowedDomains
|
||||
*
|
||||
* @psalm-return non-empty-string
|
||||
*/
|
||||
private static function createRegex(array $allowedDomains): string
|
||||
private function isAllowed(Embed $embed): bool
|
||||
{
|
||||
$allowedDomains = \array_map('preg_quote', $allowedDomains);
|
||||
$url = $embed->getUrl();
|
||||
$scheme = \parse_url($url, \PHP_URL_SCHEME);
|
||||
if ($scheme === null || $scheme === false) {
|
||||
// Bare domain (no scheme) - assume https:// so parse_url can extract the host
|
||||
$url = 'https://' . $url;
|
||||
} elseif (\strtolower($scheme) !== 'http' && \strtolower($scheme) !== 'https') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return '/^(?:https?:\/\/)?(?:[^.]+\.)*(' . \implode('|', $allowedDomains) . ')/';
|
||||
$host = \parse_url($url, \PHP_URL_HOST);
|
||||
$host = \strtolower(\rtrim((string) $host, '.'));
|
||||
|
||||
foreach ($this->allowedDomains as $domain) {
|
||||
if ($host === $domain || \str_ends_with($host, '.' . $domain)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ final class UrlEncoder
|
|||
}
|
||||
}
|
||||
|
||||
if (\ord($code) < 128) {
|
||||
if (\strlen($code) === 1 && \ord($code) < 128) {
|
||||
$result .= self::ENCODE_CACHE[\ord($code)];
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
42
vendor/league/config/CHANGELOG.md
vendored
42
vendor/league/config/CHANGELOG.md
vendored
|
|
@ -1,42 +0,0 @@
|
|||
# Change Log
|
||||
All notable changes to this project will be documented in this file.
|
||||
Updates should follow the [Keep a CHANGELOG](https://keepachangelog.com/) principles.
|
||||
|
||||
## [Unreleased][unreleased]
|
||||
|
||||
## [1.2.0] - 2022-12-11
|
||||
|
||||
### Changed
|
||||
|
||||
- Values can now be set prior to the corresponding schema being registered.
|
||||
- `exists()` and `get()` now only trigger validation for the relevant schema, not the entire config at once.
|
||||
|
||||
## [1.1.1] - 2021-08-14
|
||||
|
||||
### Changed
|
||||
|
||||
- Bumped the minimum version of dflydev/dot-access-data for PHP 8.1 support
|
||||
|
||||
## [1.1.0] - 2021-06-19
|
||||
|
||||
### Changed
|
||||
|
||||
- Bumped the minimum PHP version to 7.4+
|
||||
- Bumped the minimum version of nette/schema to 1.2.0
|
||||
|
||||
## [1.0.1] - 2021-05-31
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed the `ConfigurationExceptionInterface` marker interface not extending `Throwable` (#2)
|
||||
|
||||
## [1.0.0] - 2021-05-31
|
||||
|
||||
Initial release! 🎉
|
||||
|
||||
[unreleased]: https://github.com/thephpleague/config/compare/v1.2.0...main
|
||||
[1.2.0]: https://github.com/thephpleague/config/compare/v1.1.1...v.1.2.0
|
||||
[1.1.1]: https://github.com/thephpleague/config/compare/v1.1.0...v1.1.1
|
||||
[1.1.0]: https://github.com/thephpleague/config/compare/v1.0.1...v1.1.0
|
||||
[1.0.1]: https://github.com/thephpleague/config/compare/v1.0.0...v1.0.1
|
||||
[1.0.0]: https://github.com/thephpleague/config/releases/tag/v1.0.0
|
||||
153
vendor/league/config/README.md
vendored
153
vendor/league/config/README.md
vendored
|
|
@ -1,153 +0,0 @@
|
|||
# league/config
|
||||
|
||||
[](https://packagist.org/packages/league/config)
|
||||
[](https://packagist.org/packages/league/config)
|
||||
[](LICENSE)
|
||||
[](https://github.com/thephpleague/config/actions?query=workflow%3ATests+branch%3Amain)
|
||||
[](https://scrutinizer-ci.com/g/thephpleague/config/code-structure)
|
||||
[](https://scrutinizer-ci.com/g/thephpleague/config)
|
||||
[](https://www.colinodell.com/sponsor)
|
||||
|
||||
**league/config** helps you define nested configuration arrays with strict schemas and access configuration values with dot notation. It was created by [Colin O'Dell][@colinodell].
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
This project requires PHP 7.4 or higher. To install it via [Composer] simply run:
|
||||
|
||||
```bash
|
||||
composer require league/config
|
||||
```
|
||||
|
||||
## 🧰️ Basic Usage
|
||||
|
||||
The `Configuration` class provides everything you need to define the configuration structure and fetch values:
|
||||
|
||||
```php
|
||||
use League\Config\Configuration;
|
||||
use Nette\Schema\Expect;
|
||||
|
||||
// Define your configuration schema
|
||||
$config = new Configuration([
|
||||
'database' => Expect::structure([
|
||||
'driver' => Expect::anyOf('mysql', 'postgresql', 'sqlite')->required(),
|
||||
'host' => Expect::string()->default('localhost'),
|
||||
'port' => Expect::int()->min(1)->max(65535),
|
||||
'ssl' => Expect::bool(),
|
||||
'database' => Expect::string()->required(),
|
||||
'username' => Expect::string()->required(),
|
||||
'password' => Expect::string()->nullable(),
|
||||
]),
|
||||
'logging' => Expect::structure([
|
||||
'enabled' => Expect::bool()->default($_ENV['DEBUG'] == true),
|
||||
'file' => Expect::string()->deprecated("use logging.path instead"),
|
||||
'path' => Expect::string()->assert(function ($path) { return \is_writeable($path); })->required(),
|
||||
]),
|
||||
]);
|
||||
|
||||
// Set the values, either all at once with `merge()`:
|
||||
$config->merge([
|
||||
'database' => [
|
||||
'driver' => 'mysql',
|
||||
'port' => 3306,
|
||||
'database' => 'mydb',
|
||||
'username' => 'user',
|
||||
'password' => 'secret',
|
||||
],
|
||||
]);
|
||||
|
||||
// Or one-at-a-time with `set()`:
|
||||
$config->set('logging.path', '/var/log/myapp.log');
|
||||
|
||||
// You can now retrieve those values with `get()`.
|
||||
// Validation and defaults will be applied for you automatically
|
||||
$config->get('database'); // Fetches the entire "database" section as an array
|
||||
$config->get('database.driver'); // Fetch a specific nested value with dot notation
|
||||
$config->get('database/driver'); // Fetch a specific nested value with slash notation
|
||||
$config->get('database.host'); // Returns the default value "localhost"
|
||||
$config->get('logging.path'); // Guaranteed to be writeable thanks to the assertion in the schema
|
||||
|
||||
// If validation fails an `InvalidConfigurationException` will be thrown:
|
||||
$config->set('database.driver', 'mongodb');
|
||||
$config->get('database.driver'); // InvalidConfigurationException
|
||||
|
||||
// Attempting to fetch a non-existent key will result in an `InvalidConfigurationException`
|
||||
$config->get('foo.bar');
|
||||
|
||||
// You could avoid this by checking whether that item exists:
|
||||
$config->exists('foo.bar'); // Returns `false`
|
||||
```
|
||||
|
||||
## 📓 Documentation
|
||||
|
||||
Full documentation can be found at [config.thephpleague.com][docs].
|
||||
|
||||
## 💭 Philosophy
|
||||
|
||||
This library aims to provide a **simple yet opinionated** approach to configuration with the following goals:
|
||||
|
||||
- The configuration should operate on **arrays with nested values** which are easily accessible
|
||||
- The configuration structure should be **defined with strict schemas** defining the overall structure, allowed types, and allowed values
|
||||
- Schemas should be defined using a **simple, fluent interface**
|
||||
- You should be able to **add and combine schemas but never modify existing ones**
|
||||
- Both the configuration values and the schema should be **defined and managed with PHP code**
|
||||
- Schemas should be **immutable**; they should never change once they are set
|
||||
- Configuration values should never define or influence the schemas
|
||||
|
||||
As a result, this library will likely **never** support features like:
|
||||
|
||||
- Loading and/or exporting configuration values or schemas using YAML, XML, or other files
|
||||
- Parsing configuration values from a command line or other user interface
|
||||
- Dynamically changing the schema, allowed values, or default values based on other configuration values
|
||||
|
||||
If you need that functionality you should check out other libraries like:
|
||||
|
||||
- [symfony/config]
|
||||
- [symfony/options-resolver]
|
||||
- [hassankhan/config]
|
||||
- [consolidation/config]
|
||||
- [laminas/laminas-config]
|
||||
|
||||
## 🏷️ Versioning
|
||||
|
||||
[SemVer](http://semver.org/) is followed closely. Minor and patch releases should not introduce breaking changes to the codebase.
|
||||
|
||||
Any classes or methods marked `@internal` are not intended for use outside this library and are subject to breaking changes at any time, so please avoid using them.
|
||||
|
||||
## 🛠️ Maintenance & Support
|
||||
|
||||
When a new **minor** version (e.g. `1.0` -> `1.1`) is released, the previous one (`1.0`) will continue to receive security and critical bug fixes for *at least* 3 months.
|
||||
|
||||
When a new **major** version is released (e.g. `1.1` -> `2.0`), the previous one (`1.1`) will receive critical bug fixes for *at least* 3 months and security updates for 6 months after that new release comes out.
|
||||
|
||||
(This policy may change in the future and exceptions may be made on a case-by-case basis.)
|
||||
|
||||
## 👷️ Contributing
|
||||
|
||||
Contributions to this library are **welcome**! We only ask that you adhere to our [contributor guidelines] and avoid making changes that conflict with our Philosophy above.
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
```bash
|
||||
composer test
|
||||
```
|
||||
|
||||
## 📄 License
|
||||
|
||||
**league/config** is licensed under the BSD-3 license. See the [`LICENSE.md`][license] file for more details.
|
||||
|
||||
## 🗺️ Who Uses It?
|
||||
|
||||
This project is used by [league/commonmark][league-commonmark].
|
||||
|
||||
[docs]: https://config.thephpleague.com/
|
||||
[@colinodell]: https://www.twitter.com/colinodell
|
||||
[Composer]: https://getcomposer.org/
|
||||
[PHP League]: https://thephpleague.com
|
||||
[symfony/config]: https://symfony.com/doc/current/components/config.html
|
||||
[symfony/options-resolver]: https://symfony.com/doc/current/components/options_resolver.html
|
||||
[hassankhan/config]: https://github.com/hassankhan/config
|
||||
[consolidation/config]: https://github.com/consolidation/config
|
||||
[laminas/laminas-config]: https://docs.laminas.dev/laminas-config/
|
||||
[contributor guidelines]: https://github.com/thephpleague/config/blob/main/.github/CONTRIBUTING.md
|
||||
[license]: https://github.com/thephpleague/config/blob/main/LICENSE.md
|
||||
[league-commonmark]: https://commonmark.thephpleague.com
|
||||
69
vendor/league/config/composer.json
vendored
69
vendor/league/config/composer.json
vendored
|
|
@ -1,69 +0,0 @@
|
|||
{
|
||||
"name": "league/config",
|
||||
"type": "library",
|
||||
"description": "Define configuration arrays with strict schemas and access values with dot notation",
|
||||
"keywords": ["configuration","config","schema","array","nested","dot","dot-access"],
|
||||
"homepage": "https://config.thephpleague.com",
|
||||
"license": "BSD-3-Clause",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Colin O'Dell",
|
||||
"email": "colinodell@gmail.com",
|
||||
"homepage": "https://www.colinodell.com",
|
||||
"role": "Lead Developer"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://config.thephpleague.com/",
|
||||
"issues": "https://github.com/thephpleague/config/issues",
|
||||
"rss": "https://github.com/thephpleague/config/releases.atom",
|
||||
"source": "https://github.com/thephpleague/config"
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0",
|
||||
"dflydev/dot-access-data": "^3.0.1",
|
||||
"nette/schema": "^1.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.8.2",
|
||||
"phpunit/phpunit": "^9.5.5",
|
||||
"scrutinizer/ocular": "^1.8.1",
|
||||
"unleashedtech/php-coding-standard": "^3.1",
|
||||
"vimeo/psalm": "^4.7.3"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"League\\Config\\": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"League\\Config\\Tests\\": "tests"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"phpcs": "phpcs",
|
||||
"phpstan": "phpstan analyse",
|
||||
"phpunit": "phpunit --no-coverage",
|
||||
"psalm": "psalm",
|
||||
"test": [
|
||||
"@phpcs",
|
||||
"@phpstan",
|
||||
"@psalm",
|
||||
"@phpunit"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.2-dev"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
20
vendor/myclabs/deep-copy/LICENSE
vendored
20
vendor/myclabs/deep-copy/LICENSE
vendored
|
|
@ -1,20 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 My C-Sense
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
406
vendor/myclabs/deep-copy/README.md
vendored
406
vendor/myclabs/deep-copy/README.md
vendored
|
|
@ -1,406 +0,0 @@
|
|||
# DeepCopy
|
||||
|
||||
DeepCopy helps you create deep copies (clones) of your objects. It is designed to handle cycles in the association graph.
|
||||
|
||||
[](https://packagist.org/packages/myclabs/deep-copy)
|
||||
[](https://github.com/myclabs/DeepCopy/actions/workflows/ci.yaml)
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [How](#how)
|
||||
1. [Why](#why)
|
||||
1. [Using simply `clone`](#using-simply-clone)
|
||||
1. [Overriding `__clone()`](#overriding-__clone)
|
||||
1. [With `DeepCopy`](#with-deepcopy)
|
||||
1. [How it works](#how-it-works)
|
||||
1. [Going further](#going-further)
|
||||
1. [Matchers](#matchers)
|
||||
1. [Property name](#property-name)
|
||||
1. [Specific property](#specific-property)
|
||||
1. [Type](#type)
|
||||
1. [Filters](#filters)
|
||||
1. [`SetNullFilter`](#setnullfilter-filter)
|
||||
1. [`KeepFilter`](#keepfilter-filter)
|
||||
1. [`DoctrineCollectionFilter`](#doctrinecollectionfilter-filter)
|
||||
1. [`DoctrineEmptyCollectionFilter`](#doctrineemptycollectionfilter-filter)
|
||||
1. [`DoctrineProxyFilter`](#doctrineproxyfilter-filter)
|
||||
1. [`ReplaceFilter`](#replacefilter-type-filter)
|
||||
1. [`ShallowCopyFilter`](#shallowcopyfilter-type-filter)
|
||||
1. [Edge cases](#edge-cases)
|
||||
1. [Contributing](#contributing)
|
||||
1. [Tests](#tests)
|
||||
|
||||
|
||||
## How?
|
||||
|
||||
Install with Composer:
|
||||
|
||||
```
|
||||
composer require myclabs/deep-copy
|
||||
```
|
||||
|
||||
Use it:
|
||||
|
||||
```php
|
||||
use DeepCopy\DeepCopy;
|
||||
|
||||
$copier = new DeepCopy();
|
||||
$myCopy = $copier->copy($myObject);
|
||||
```
|
||||
|
||||
|
||||
## Why?
|
||||
|
||||
- How do you create copies of your objects?
|
||||
|
||||
```php
|
||||
$myCopy = clone $myObject;
|
||||
```
|
||||
|
||||
- How do you create **deep** copies of your objects (i.e. copying also all the objects referenced in the properties)?
|
||||
|
||||
You use [`__clone()`](http://www.php.net/manual/en/language.oop5.cloning.php#object.clone) and implement the behavior
|
||||
yourself.
|
||||
|
||||
- But how do you handle **cycles** in the association graph?
|
||||
|
||||
Now you're in for a big mess :(
|
||||
|
||||

|
||||
|
||||
|
||||
### Using simply `clone`
|
||||
|
||||

|
||||
|
||||
|
||||
### Overriding `__clone()`
|
||||
|
||||

|
||||
|
||||
|
||||
### With `DeepCopy`
|
||||
|
||||

|
||||
|
||||
|
||||
## How it works
|
||||
|
||||
DeepCopy recursively traverses all the object's properties and clones them. To avoid cloning the same object twice it
|
||||
keeps a hash map of all instances and thus preserves the object graph.
|
||||
|
||||
To use it:
|
||||
|
||||
```php
|
||||
use function DeepCopy\deep_copy;
|
||||
|
||||
$copy = deep_copy($var);
|
||||
```
|
||||
|
||||
Alternatively, you can create your own `DeepCopy` instance to configure it differently for example:
|
||||
|
||||
```php
|
||||
use DeepCopy\DeepCopy;
|
||||
|
||||
$copier = new DeepCopy(true);
|
||||
|
||||
$copy = $copier->copy($var);
|
||||
```
|
||||
|
||||
You may want to roll your own deep copy function:
|
||||
|
||||
```php
|
||||
namespace Acme;
|
||||
|
||||
use DeepCopy\DeepCopy;
|
||||
|
||||
function deep_copy($var)
|
||||
{
|
||||
static $copier = null;
|
||||
|
||||
if (null === $copier) {
|
||||
$copier = new DeepCopy(true);
|
||||
}
|
||||
|
||||
return $copier->copy($var);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Going further
|
||||
|
||||
You can add filters to customize the copy process.
|
||||
|
||||
The method to add a filter is `DeepCopy\DeepCopy::addFilter($filter, $matcher)`,
|
||||
with `$filter` implementing `DeepCopy\Filter\Filter`
|
||||
and `$matcher` implementing `DeepCopy\Matcher\Matcher`.
|
||||
|
||||
We provide some generic filters and matchers.
|
||||
|
||||
|
||||
### Matchers
|
||||
|
||||
- `DeepCopy\Matcher` applies on a object attribute.
|
||||
- `DeepCopy\TypeMatcher` applies on any element found in graph, including array elements.
|
||||
|
||||
|
||||
#### Property name
|
||||
|
||||
The `PropertyNameMatcher` will match a property by its name:
|
||||
|
||||
```php
|
||||
use DeepCopy\Matcher\PropertyNameMatcher;
|
||||
|
||||
// Will apply a filter to any property of any objects named "id"
|
||||
$matcher = new PropertyNameMatcher('id');
|
||||
```
|
||||
|
||||
|
||||
#### Specific property
|
||||
|
||||
The `PropertyMatcher` will match a specific property of a specific class:
|
||||
|
||||
```php
|
||||
use DeepCopy\Matcher\PropertyMatcher;
|
||||
|
||||
// Will apply a filter to the property "id" of any objects of the class "MyClass"
|
||||
$matcher = new PropertyMatcher('MyClass', 'id');
|
||||
```
|
||||
|
||||
|
||||
#### Type
|
||||
|
||||
The `TypeMatcher` will match any element by its type (instance of a class or any value that could be parameter of
|
||||
[gettype()](http://php.net/manual/en/function.gettype.php) function):
|
||||
|
||||
```php
|
||||
use DeepCopy\TypeMatcher\TypeMatcher;
|
||||
|
||||
// Will apply a filter to any object that is an instance of Doctrine\Common\Collections\Collection
|
||||
$matcher = new TypeMatcher('Doctrine\Common\Collections\Collection');
|
||||
```
|
||||
|
||||
|
||||
### Filters
|
||||
|
||||
- `DeepCopy\Filter` applies a transformation to the object attribute matched by `DeepCopy\Matcher`
|
||||
- `DeepCopy\TypeFilter` applies a transformation to any element matched by `DeepCopy\TypeMatcher`
|
||||
|
||||
By design, matching a filter will stop the chain of filters (i.e. the next ones will not be applied).
|
||||
Using the ([`ChainableFilter`](#chainablefilter-filter)) won't stop the chain of filters.
|
||||
|
||||
|
||||
#### `SetNullFilter` (filter)
|
||||
|
||||
Let's say for example that you are copying a database record (or a Doctrine entity), so you want the copy not to have
|
||||
any ID:
|
||||
|
||||
```php
|
||||
use DeepCopy\DeepCopy;
|
||||
use DeepCopy\Filter\SetNullFilter;
|
||||
use DeepCopy\Matcher\PropertyNameMatcher;
|
||||
|
||||
$object = MyClass::load(123);
|
||||
echo $object->id; // 123
|
||||
|
||||
$copier = new DeepCopy();
|
||||
$copier->addFilter(new SetNullFilter(), new PropertyNameMatcher('id'));
|
||||
|
||||
$copy = $copier->copy($object);
|
||||
|
||||
echo $copy->id; // null
|
||||
```
|
||||
|
||||
|
||||
#### `KeepFilter` (filter)
|
||||
|
||||
If you want a property to remain untouched (for example, an association to an object):
|
||||
|
||||
```php
|
||||
use DeepCopy\DeepCopy;
|
||||
use DeepCopy\Filter\KeepFilter;
|
||||
use DeepCopy\Matcher\PropertyMatcher;
|
||||
|
||||
$copier = new DeepCopy();
|
||||
$copier->addFilter(new KeepFilter(), new PropertyMatcher('MyClass', 'category'));
|
||||
|
||||
$copy = $copier->copy($object);
|
||||
// $copy->category has not been touched
|
||||
```
|
||||
|
||||
|
||||
#### `ChainableFilter` (filter)
|
||||
|
||||
If you use cloning on proxy classes, you might want to apply two filters for:
|
||||
1. loading the data
|
||||
2. applying a transformation
|
||||
|
||||
You can use the `ChainableFilter` as a decorator of the proxy loader filter, which won't stop the chain of filters (i.e.
|
||||
the next ones may be applied).
|
||||
|
||||
|
||||
```php
|
||||
use DeepCopy\DeepCopy;
|
||||
use DeepCopy\Filter\ChainableFilter;
|
||||
use DeepCopy\Filter\Doctrine\DoctrineProxyFilter;
|
||||
use DeepCopy\Filter\SetNullFilter;
|
||||
use DeepCopy\Matcher\Doctrine\DoctrineProxyMatcher;
|
||||
use DeepCopy\Matcher\PropertyNameMatcher;
|
||||
|
||||
$copier = new DeepCopy();
|
||||
$copier->addFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher());
|
||||
$copier->addFilter(new SetNullFilter(), new PropertyNameMatcher('id'));
|
||||
|
||||
$copy = $copier->copy($object);
|
||||
|
||||
echo $copy->id; // null
|
||||
```
|
||||
|
||||
|
||||
#### `DoctrineCollectionFilter` (filter)
|
||||
|
||||
If you use Doctrine and want to copy an entity, you will need to use the `DoctrineCollectionFilter`:
|
||||
|
||||
```php
|
||||
use DeepCopy\DeepCopy;
|
||||
use DeepCopy\Filter\Doctrine\DoctrineCollectionFilter;
|
||||
use DeepCopy\Matcher\PropertyTypeMatcher;
|
||||
|
||||
$copier = new DeepCopy();
|
||||
$copier->addFilter(new DoctrineCollectionFilter(), new PropertyTypeMatcher('Doctrine\Common\Collections\Collection'));
|
||||
|
||||
$copy = $copier->copy($object);
|
||||
```
|
||||
|
||||
|
||||
#### `DoctrineEmptyCollectionFilter` (filter)
|
||||
|
||||
If you use Doctrine and want to copy an entity who contains a `Collection` that you want to be reset, you can use the
|
||||
`DoctrineEmptyCollectionFilter`
|
||||
|
||||
```php
|
||||
use DeepCopy\DeepCopy;
|
||||
use DeepCopy\Filter\Doctrine\DoctrineEmptyCollectionFilter;
|
||||
use DeepCopy\Matcher\PropertyMatcher;
|
||||
|
||||
$copier = new DeepCopy();
|
||||
$copier->addFilter(new DoctrineEmptyCollectionFilter(), new PropertyMatcher('MyClass', 'myProperty'));
|
||||
|
||||
$copy = $copier->copy($object);
|
||||
|
||||
// $copy->myProperty will return an empty collection
|
||||
```
|
||||
|
||||
|
||||
#### `DoctrineProxyFilter` (filter)
|
||||
|
||||
If you use Doctrine and use cloning on lazy loaded entities, you might encounter errors mentioning missing fields on a
|
||||
Doctrine proxy class (...\\\_\_CG\_\_\Proxy).
|
||||
You can use the `DoctrineProxyFilter` to load the actual entity behind the Doctrine proxy class.
|
||||
**Make sure, though, to put this as one of your very first filters in the filter chain so that the entity is loaded
|
||||
before other filters are applied!**
|
||||
We recommend to decorate the `DoctrineProxyFilter` with the `ChainableFilter` to allow applying other filters to the
|
||||
cloned lazy loaded entities.
|
||||
|
||||
```php
|
||||
use DeepCopy\DeepCopy;
|
||||
use DeepCopy\Filter\Doctrine\DoctrineProxyFilter;
|
||||
use DeepCopy\Matcher\Doctrine\DoctrineProxyMatcher;
|
||||
|
||||
$copier = new DeepCopy();
|
||||
$copier->addFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher());
|
||||
|
||||
$copy = $copier->copy($object);
|
||||
|
||||
// $copy should now contain a clone of all entities, including those that were not yet fully loaded.
|
||||
```
|
||||
|
||||
|
||||
#### `ReplaceFilter` (type filter)
|
||||
|
||||
1. If you want to replace the value of a property:
|
||||
|
||||
```php
|
||||
use DeepCopy\DeepCopy;
|
||||
use DeepCopy\Filter\ReplaceFilter;
|
||||
use DeepCopy\Matcher\PropertyMatcher;
|
||||
|
||||
$copier = new DeepCopy();
|
||||
$callback = function ($currentValue) {
|
||||
return $currentValue . ' (copy)'
|
||||
};
|
||||
$copier->addFilter(new ReplaceFilter($callback), new PropertyMatcher('MyClass', 'title'));
|
||||
|
||||
$copy = $copier->copy($object);
|
||||
|
||||
// $copy->title will contain the data returned by the callback, e.g. 'The title (copy)'
|
||||
```
|
||||
|
||||
2. If you want to replace whole element:
|
||||
|
||||
```php
|
||||
use DeepCopy\DeepCopy;
|
||||
use DeepCopy\TypeFilter\ReplaceFilter;
|
||||
use DeepCopy\TypeMatcher\TypeMatcher;
|
||||
|
||||
$copier = new DeepCopy();
|
||||
$callback = function (MyClass $myClass) {
|
||||
return get_class($myClass);
|
||||
};
|
||||
$copier->addTypeFilter(new ReplaceFilter($callback), new TypeMatcher('MyClass'));
|
||||
|
||||
$copy = $copier->copy([new MyClass, 'some string', new MyClass]);
|
||||
|
||||
// $copy will contain ['MyClass', 'some string', 'MyClass']
|
||||
```
|
||||
|
||||
|
||||
The `$callback` parameter of the `ReplaceFilter` constructor accepts any PHP callable.
|
||||
|
||||
|
||||
#### `ShallowCopyFilter` (type filter)
|
||||
|
||||
Stop *DeepCopy* from recursively copying element, using standard `clone` instead:
|
||||
|
||||
```php
|
||||
use DeepCopy\DeepCopy;
|
||||
use DeepCopy\TypeFilter\ShallowCopyFilter;
|
||||
use DeepCopy\TypeMatcher\TypeMatcher;
|
||||
use Mockery as m;
|
||||
|
||||
$this->deepCopy = new DeepCopy();
|
||||
$this->deepCopy->addTypeFilter(
|
||||
new ShallowCopyFilter,
|
||||
new TypeMatcher(m\MockInterface::class)
|
||||
);
|
||||
|
||||
$myServiceWithMocks = new MyService(m::mock(MyDependency1::class), m::mock(MyDependency2::class));
|
||||
// All mocks will be just cloned, not deep copied
|
||||
```
|
||||
|
||||
|
||||
## Edge cases
|
||||
|
||||
The following structures cannot be deep-copied with PHP Reflection. As a result they are shallow cloned and filters are
|
||||
not applied. There is two ways for you to handle them:
|
||||
|
||||
- Implement your own `__clone()` method
|
||||
- Use a filter with a type matcher
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
DeepCopy is distributed under the MIT license.
|
||||
|
||||
|
||||
### Tests
|
||||
|
||||
Running the tests is simple:
|
||||
|
||||
```php
|
||||
vendor/bin/phpunit
|
||||
```
|
||||
|
||||
### Support
|
||||
|
||||
Get professional support via [the Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-myclabs-deep-copy?utm_source=packagist-myclabs-deep-copy&utm_medium=referral&utm_campaign=readme).
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue