Initial commit: WordPress wp-content (themes, plugins, languages)

- Theme: momentry (custom theme with REST API routes)
- Plugins: code-snippets (contains all API proxies)
- Languages: zh_TW translations
- Excludes: cache, backups, uploads, logs
This commit is contained in:
OpenCode
2026-05-29 19:07:56 +08:00
commit 09ef1f000f
6521 changed files with 867163 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017-2020 Typist Tech <info@typist.tech>
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.

View File

@@ -0,0 +1,199 @@
<div align="center">
# Imposter Plugin
</div>
<div align="center">
[![Packagist](https://img.shields.io/packagist/v/typisttech/imposter-plugin.svg?style=flat-square)](https://packagist.org/packages/typisttech/imposter-plugin)
[![Packagist](https://img.shields.io/packagist/dt/typisttech/imposter-plugin.svg?style=flat-square)](https://packagist.org/packages/typisttech/imposter-plugin)
[![PHP from Packagist](https://img.shields.io/packagist/php-v/TypistTech/imposter-plugin?style=flat-square)](https://packagist.org/packages/typisttech/imposter-plugin)
[![CircleCI](https://img.shields.io/circleci/build/gh/TypistTech/imposter-plugin?style=flat-square)](https://circleci.com/gh/TypistTech/imposter-plugin)
[![license](https://img.shields.io/github/license/TypistTech/imposter-plugin.svg?style=flat-square)](https://github.com/TypistTech/imposter-plugin/blob/master/LICENSE)
[![Twitter Follow @TangRufus](https://img.shields.io/twitter/follow/TangRufus?style=flat-square&color=1da1f2&logo=twitter)](https://twitter.com/tangrufus)
[![Hire Typist Tech](https://img.shields.io/badge/Hire-Typist%20Tech-ff69b4.svg?style=flat-square)](https://www.typist.tech/contact/)
</div>
<p align="center">
<strong>Composer plugin that wraps all composer vendor packages inside your own namespace. Intended for WordPress plugins.</strong>
<br />
<br />
Built with ♥ by <a href="https://www.typist.tech/">Typist Tech</a>
</p>
---
**Imposter Plugin** is an open source project and completely free to use.
However, the amount of effort needed to maintain and develop new features is not sustainable without proper financial backing. If you have the capability, please consider donating using the links below:
<div align="center">
[![GitHub via Sponsor](https://img.shields.io/badge/Sponsor-GitHub-ea4aaa?style=flat-square&logo=github)](https://github.com/sponsors/TangRufus)
[![Sponsor via PayPal](https://img.shields.io/badge/Sponsor-PayPal-blue.svg?style=flat-square&logo=paypal)](https://typist.tech/go/paypal-donate/)
[![More Sponsorship Information](https://img.shields.io/badge/Sponsor-More%20Details-ff69b4?style=flat-square)](https://typist.tech/donate/imposter-plugin/)
</div>
---
Wrapping all composer vendor packages inside your own namespace. Intended for WordPress plugins. Imposter Plugin is a composer plugin wrapper for [Imposter](https://github.com/TypistTech/imposter/).
## Why?
Because of the lack of dependency management in WordPress, if two plugins bundled conflicting versions of the same package, hard-to-reproduce bugs arise.
Monkey patching composer vendor packages, wrapping them inside your own namespace is a less-than-ideal solution to avoid such conflicts.
See:
- [A Narrative of Using Composer in a WordPress Plugin](https://wptavern.com/a-narrative-of-using-composer-in-a-wordpress-plugin)
- [A Warning About Using Composer With WordPress](https://wppusher.com/blog/a-warning-about-using-composer-with-wordpress/)
## Install
Installation should be done via composer, details of how to install composer can be found at [https://getcomposer.org/](https://getcomposer.org/).
First, add Imposter configuration in your `composer.json`
```json
"extra": {
"imposter": {
"namespace": "My\\App\\Vendor",
"excludes": [
"dummy/dummy-excluded"
]
}
}
```
Then, install via composer cli
```bash
composer require typisttech/imposter-plugin
```
See: [Imposter readme](https://github.com/Typisttech/imposter#config) for details.
---
<p align="center">
<strong>Typist Tech is ready to build your next awesome WordPress site. <a href="https://typist.tech/contact/">Hire us!</a></strong>
</p>
---
## Usage
### Sit Back and Relax
Once installed, this plugin hooks into `composer install`, `composer update` and `composer dump-autoload`, automatically run [imposter](https://github.com/TypistTech/imposter/) for you.
Besides, imposter plugin autoloads all modified files as [classmap](https://getcomposer.org/doc/04-schema.md#classmap).
When those events triggered, this plugin:
1. looks for `/path/to/project/root/composer.json`
2. finds out [vendor-dir](https://getcomposer.org/doc/06-config.md#vendor-dir)
3. finds out all [required packages](https://getcomposer.org/doc/04-schema.md#require), including those required by dependencies
4. finds out all [autoload paths](https://getcomposer.org/doc/04-schema.md#autoload) for all required packages
5. prefixes all namespaces with the imposter-plugin namespace defined in your `composer.json`
Learn more on [imposter's readme](https://github.com/TypistTech/imposter#usage).
## Known Issues
Help wanted. Pull requests are welcomed.
1. Imposter run twice when `composer install` and `composer update`
1. Traits are not transformed
1. Virtual packages are not supported
## Frequently Asked Questions
### What can I find more information?
Learn more on [imposter's readme](https://github.com/TypistTech/imposter/) for more details.
### How about not hooking into composer commands?
Use [imposter](https://github.com/TypistTech/imposter/) directly.
### Do you have real life examples that use this composer plugin?
Here you go:
* [Sunny](https://github.com/Typisttech/sunny)
* [WP Cloudflare Guard](https://github.com/TypistTech/wp-cloudflare-guard)
*Add your own [here](https://github.com/TypistTech/imposter-plugin/edit/master/README.md)*
### Which composer versions are supported?
Both v1 and v2.
### Will you add support for older PHP versions?
Never! This plugin will only work on [actively supported PHP versions](https://secure.php.net/supported-versions.php).
Don't use it on **end of life** or **security fixes only** PHP versions.
### It looks awesome. Where can I find some more goodies like this
- Articles on [Typist Tech's blog](https://typist.tech)
- [Tang Rufus' WordPress plugins](https://profiles.wordpress.org/tangrufus#content-plugins) on wp.org
- More projects on [Typist Tech's GitHub profile](https://github.com/TypistTech)
- Stay tuned on [Typist Tech's newsletter](https://typist.tech/go/newsletter)
- Follow [Tang Rufus' Twitter account](https://twitter.com/TangRufus)
- **Hire [Tang Rufus](https://typist.tech/contact) to build your next awesome site**
### Where can I give 5-star reviews?
Thanks! Glad you like it. It's important to let me knows somebody is using this project. Please consider:
- [tweet](https://twitter.com/intent/tweet?url=https%3A%2F%2Fgithub.com%2FTypistTech%2Fimposter-plugin&via=tangrufus&text=Imposter%20Plugin%20-%20Composer%20plugin%20that%20wraps%20all%20%23composer%20vendor%20packages%20inside%20your%20own%20namespace.%20Intended%20for%20%23WordPress%20plugins) something good with mentioning [@TangRufus](https://twitter.com/tangrufus)
- ★ star [the Github repo](https://github.com/TypistTech/imposter-plugin)
- [👀 watch](https://github.com/TypistTech/imposter-plugin/subscription) the Github repo
- write tutorials and blog posts
- **[hire](https://www.typist.tech/contact/) Typist Tech**
## Testing
```bash
composer test
composer style:check
```
## Alternatives
Here is a list of alternatives that I found. However, none of these satisfied my requirements.
*If you know other similar projects, feel free to edit this section!*
* [Mozart](https://github.com/coenjacobs/mozart) by Coen Jacobs
- Works with PSR0 and PSR4
- Dependency packages store in a different directory
* [PHP Scoper](https://github.com/humbug/php-scoper)
- Prefixes all PHP namespaces in a file/directory to isolate the code bundled in PHARs
## Feedback
**Please provide feedback!** We want to make this project as useful as possible.
Please [submit an issue](https://github.com/TypistTech/imposter-plugin/issues/new) and point out what you do and don't like, or fork the project and [send pull requests](https://github.com/TypistTech/imposter-plugin/pulls/).
**No issue is too small.**
## Security Vulnerabilities
If you discover a security vulnerability within this project, please email us at [imposter-plugin@typist.tech](mailto:imposter-plugin@typist.tech).
All security vulnerabilities will be promptly addressed.
## Credits
[Imposter Plugin](https://github.com/TypistTech/imposter-plugin) is a [Typist Tech](https://typist.tech) project and maintained by [Tang Rufus](https://twitter.com/TangRufus), freelance developer for [hire](https://www.typist.tech/contact/).
Full list of contributors can be found [here](https://github.com/TypistTech/imposter-plugin/graphs/contributors).
## License
[Imposter Plugin](https://github.com/TypistTech/imposter-plugin) is released under the [MIT License](https://opensource.org/licenses/MIT).

View File

@@ -0,0 +1,66 @@
{
"name": "typisttech/imposter-plugin",
"type": "composer-plugin",
"description": "Composer plugin that wraps all composer vendor packages inside your own namespace. Intended for WordPress plugins.",
"keywords": [
"composer",
"composer-plugin",
"dependency",
"monkey-patching",
"namespace",
"wordpress"
],
"homepage": "https://github.com/TypistTech/imposter-plugin",
"license": "MIT",
"authors": [
{
"name": "Typist Tech",
"email": "imposter-plugin@typist.tech",
"homepage": "https://www.typist.tech"
},
{
"name": "Tang Rufus",
"email": "tangrufus@gmail.com",
"homepage": "https://www.typist.tech",
"role": "Developer"
}
],
"require": {
"php": "^7.3 || ^8.0",
"composer-plugin-api": "^1.1 || ^2.0",
"typisttech/imposter": "^0.6.1"
},
"require-dev": {
"codeception/codeception": "^4.1",
"codeception/module-asserts": "^1.3",
"codeception/module-cli": "^1.1",
"codeception/module-filesystem": "^1.0",
"composer/composer": "^1.10.19 || ^2.0",
"squizlabs/php_codesniffer": "^3.5",
"typisttech/codeception-composer-project-module": "^0.1.1"
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "0.6.x-dev"
},
"class": "TypistTech\\Imposter\\Plugin\\ImposterPlugin"
},
"autoload": {
"psr-4": {
"TypistTech\\Imposter\\Plugin\\": "src"
}
},
"scripts": {
"style:check": "phpcs",
"style:fix": "phpcbf",
"test": "codecept run --debug"
},
"support": {
"email": "imposter-plugin@typist.tech",
"issues": "https://github.com/TypistTech/imposter-plugin/issues",
"source": "https://github.com/TypistTech/imposter-plugin"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace TypistTech\Imposter\Plugin;
use Composer\Package\RootPackageInterface;
use RuntimeException;
use TypistTech\Imposter\ImposterFactory;
class AutoloadMerger
{
public static function run(RootPackageInterface $package): void
{
$autoload = $package->getAutoload();
$autoload = array_merge_recursive($autoload, [
'classmap' => static::getImposterAutoloads(),
]);
$package->setAutoload($autoload);
}
/**
* @return string[]
* @todo [Help Wanted] Think of a better way to handle file not found during installation
*/
protected static function getImposterAutoloads(): array
{
try {
$cwd = getcwd();
$imposter = ImposterFactory::forProject($cwd, ['typisttech/imposter-plugin']);
return array_map(function ($path) use ($cwd): string {
return str_replace($cwd . '/', '', $path);
}, $imposter->getAutoloads());
} catch (RuntimeException $exception) {
return [];
}
}
}

View File

@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace TypistTech\Imposter\Plugin;
use Composer\Composer;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\IO\IOInterface;
use Composer\Package\CompletePackage;
use Composer\Package\RootPackageInterface;
use Composer\Plugin\PluginInterface;
use Composer\Script\Event;
use Composer\Script\ScriptEvents;
class ImposterPlugin implements PluginInterface, EventSubscriberInterface
{
/**
* {@inheritDoc}
*/
public function activate(Composer $composer, IOInterface $io)
{
$package = $composer->getPackage();
if ($package instanceof RootPackageInterface) {
AutoloadMerger::run($package);
}
if ($package instanceof CompletePackage) {
$scripts = array_merge_recursive([
ScriptEvents::POST_INSTALL_CMD => [
'@composer dump-autoload --optimize',
],
ScriptEvents::POST_UPDATE_CMD => [
'@composer dump-autoload --optimize',
],
], $package->getScripts());
$package->setScripts($scripts);
}
}
/**
* {@inheritDoc}
*/
public static function getSubscribedEvents()
{
return [
ScriptEvents::PRE_AUTOLOAD_DUMP => [
['transform', PHP_INT_MAX - 1000],
],
];
}
public function transform(Event $event): void
{
Transformer::run(
$event->getIO()
);
}
public function deactivate(Composer $composer, IOInterface $io)
{
// Do nothing.
}
public function uninstall(Composer $composer, IOInterface $io)
{
// Do nothing.
}
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace TypistTech\Imposter\Plugin;
use Composer\IO\IOInterface;
use TypistTech\Imposter\ImposterFactory;
class Transformer
{
public static function run(IOInterface $io): void
{
// Print an empty line to separate imposter outputs.
$io->write('', true);
$io->write('', true);
$io->write('<info>Running Imposter...</info>', true);
$io->write('<info>======================</info>', true);
$io->write('Loading package information from <comment>' . getcwd() . '/composer.json</comment>', true);
$imposter = ImposterFactory::forProject(getcwd(), ['typisttech/imposter-plugin']);
$autoloads = $imposter->getAutoloads();
$count = count($autoloads);
$index = 1;
foreach ($autoloads as $autoload) {
$io->write(" - <comment>$index/$count</comment>: Transforming $autoload", true);
$imposter->transform($autoload);
$index++;
}
$io->write('<info>Success: Imposter transformed vendor files.</info>', true);
$invalidAutoloads = $imposter->getInvalidAutoloads();
if (! empty($invalidAutoloads)) {
$invalidAutoloadsCount = count($invalidAutoloads);
$io->writeError('', true);
$io->writeError(
// phpcs:ignore Generic.Files.LineLength.TooLong
"<warning>Warning: Imposter failed to transformed $invalidAutoloadsCount of the autoload path(s).</warning>",
true
);
foreach ($invalidAutoloads as $invalidAutoload) {
$io->writeError(" - $invalidAutoload", true);
}
}
// Print empty lines to separate imposter outputs.
$io->write('', true);
$io->write('', true);
}
}

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017-2020 Typist Tech <info@typist.tech>
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.

View File

@@ -0,0 +1,320 @@
<div align="center">
# Imposter
</div>
<div align="center">
[![Packagist Version](https://img.shields.io/packagist/v/typisttech/imposter.svg?style=flat-square)](https://packagist.org/packages/typisttech/imposter)
[![Packagist Downloads](https://img.shields.io/packagist/dt/typisttech/imposter.svg?style=flat-square)](https://packagist.org/packages/typisttech/imposter)
[![PHP from Packagist](https://img.shields.io/packagist/php-v/TypistTech/imposter?style=flat-square)](https://packagist.org/packages/typisttech/imposter)
[![CircleCI](https://img.shields.io/circleci/build/gh/TypistTech/imposter?style=flat-square)](https://circleci.com/gh/TypistTech/imposter)
[![Codecov](https://img.shields.io/codecov/c/gh/typisttech/imposter?style=flat-square)](https://codecov.io/gh/TypistTech/imposter)
[![License](https://img.shields.io/github/license/TypistTech/imposter.svg?style=flat-square)](https://github.com/TypistTech/imposter/blob/master/LICENSE)
[![Twitter Follow @TangRufus](https://img.shields.io/twitter/follow/TangRufus?style=flat-square&color=1da1f2&logo=twitter)](https://twitter.com/tangrufus)
[![Hire Typist Tech](https://img.shields.io/badge/Hire-Typist%20Tech-ff69b4.svg?style=flat-square)](https://www.typist.tech/contact/)
</div>
<p align="center">
<strong>Wrapping all composer vendor packages inside your own namespace. Intended for WordPress plugins.</strong>
<br />
<br />
Built with ♥ by <a href="https://www.typist.tech/">Typist Tech</a>
</p>
---
**Imposter** is an open source project and completely free to use.
However, the amount of effort needed to maintain and develop new features is not sustainable without proper financial backing. If you have the capability, please consider donating using the links below:
<div align="center">
[![GitHub via Sponsor](https://img.shields.io/badge/Sponsor-GitHub-ea4aaa?style=flat-square&logo=github)](https://github.com/sponsors/TangRufus)
[![Sponsor via PayPal](https://img.shields.io/badge/Sponsor-PayPal-blue.svg?style=flat-square&logo=paypal)](https://typist.tech/go/paypal-donate/)
[![More Sponsorship Information](https://img.shields.io/badge/Sponsor-More%20Details-ff69b4?style=flat-square)](https://typist.tech/donate/imposter/)
</div>
---
Wrapping all composer vendor packages inside your own namespace. Intended for WordPress plugins.
## Why?
Because of the lack of dependency management in WordPress, if two plugins bundled conflicting versions of the same package, hard-to-reproduce bugs arise.
Monkey patching composer vendor packages, wrapping them inside your own namespace is a less-than-ideal solution to avoid such conflicts.
See:
- [A Narrative of Using Composer in a WordPress Plugin](https://wptavern.com/a-narrative-of-using-composer-in-a-wordpress-plugin)
- [A Warning About Using Composer With WordPress](https://wppusher.com/blog/a-warning-about-using-composer-with-wordpress/)
## Install
> If you want to hook Imposter into [composer command events](https://getcomposer.org/doc/articles/scripts.md#command-events), install [Imposter Plugin](https://github.com/TypistTech/imposter-plugin) instead.
> See: [How can I integrate Imposter with composer?](#how-can-i-integrate-imposter-with-composer)
Installation should be done via composer, details of how to install composer can be found at [https://getcomposer.org/](https://getcomposer.org/).
```bash
composer require typisttech/imposter
```
## Config
In your `composer.json`:
```json
"extra": {
"imposter": {
"namespace": "My\\App\\Vendor",
"excludes": [
"dummy/dummy-excluded"
]
}
}
```
### extra.imposter.namespace
*Required* String
This is the namespace prefix to be added to vendor packages.
### extra.imposter.excludes
*Optional* Array of strings
Vendor packages which needs to be excluded from namespace prefixing.
All [composer-made packages](https://packagist.org/packages/composer/) are excluded by default.
Besides, anything under the `Composer` namespace will be excluded.
## Usage
After every `$ composer install` and `$ composer update`:
```php
<?php
use TypistTech\Imposter\ImposterFactory;
$imposter = ImposterFactory::forProject('/path/to/project/root');
$imposter->run();
```
The above snippet:
1. Look for `/path/to/project/root/composer.json`
1. Find out [vendor-dir](https://getcomposer.org/doc/06-config.md#vendor-dir)
1. Find out all [required packages](https://getcomposer.org/doc/04-schema.md#require), including those required by dependencies
1. Find out all [autoload paths](https://getcomposer.org/doc/04-schema.md#autoload) for all required packages
1. Prefix all namespaces with the imposter namespace defined in your `composer.json`
Before:
```php
<?php
namespace Dummy\File;
use AnotherDummy\{
SubAnotherDummy, SubOtherDummy
};
use Composer;
use Composer\Plugin\PluginInterface;
use Dummy\SubOtherDummy;
use OtherDummy\SubOtherDummy;
use RuntimeException;
use \UnexpectedValueException;
use function OtherVendor\myFunc;
use const OtherVendor\MY_MAGIC_NUMBER;
$closure = function () use ($aaa) {
// Just testing.
};
class DummyClass
{
public function useClosure()
{
array_map(function () use ($xxx) {
// Just testing.
}, []);
}
}
function dummyFunction(string $namespace = null, string $use = null): array
{
if (! is_null($namespace) && $namespace === 'dummy string' && $use === 'dummy string') {
// Just testing.
}
return [];
}
foreach ([] as $namespace => $prefix) {
$aaaa = '{' . $namespace . '}' . $prefix;
}
/** Just a comment for testing $namespace transformation */
```
After:
```php
<?php
namespace MyPlugin\Vendor\Dummy\File;
use MyPlugin\Vendor\AnotherDummy\{
SubAnotherDummy, SubOtherDummy
};
use Composer;
use Composer\Plugin\PluginInterface;
use MyPlugin\Vendor\Dummy\SubOtherDummy;
use MyPlugin\Vendor\OtherDummy\SubOtherDummy;
use RuntimeException;
use \UnexpectedValueException;
use function MyPlugin\Vendor\OtherVendor\myFunc;
use const MyPlugin\Vendor\OtherVendor\MY_MAGIC_NUMBER;
$closure = function () use ($aaa) {
// Just testing.
};
class DummyClass
{
public function useClosure()
{
array_map(function () use ($xxx) {
// Just testing.
}, []);
}
}
function dummyFunction(string $namespace = null, string $use = null): array
{
if (! is_null($namespace) && $namespace === 'dummy string' && $use === 'dummy string') {
// Just testing.
}
return [];
}
foreach ([] as $namespace => $prefix) {
$aaaa = '{' . $namespace . '}' . $prefix;
}
/** Just a comment for testing $namespace transformation */
```
## Known Issues
**Help Wanted.** Pull requests are welcomed.
1. Traits are not transformed
1. Virtual packages are not supported
## Frequently Asked Questions
### How can I integrate imposter with composer?
Use [Imposter Plugin](https://github.com/TypistTech/imposter-plugin) instead.
It hooks imposter into [composer command events](https://getcomposer.org/doc/articles/scripts.md#command-events).
### Does imposter support `PSR4`, `PSR0`, `Classmap` and `Files`?
Yes for all. PSR-4 and PSR-0 autoloading, classmap generation and files includes are supported.
### Can I exclude some of the packages from imposter?
Yes, see [`extra.imposter.excludes`](#extraimposterexcludes).
All [composer made packages](https://packagist.org/packages/composer/) are excluded by default.
### How about `require-dev` packages?
Imposter do nothing on `require-dev` packages because imposter is intended for avoiding production environment., not for development environment.
### How about PHP built-in classes?
Imposter skips classes that on global namespace, for example: `\ArrayObject`, `\RuntimeException`
### How about packages that don't use namespaces?
Not for now.
Tell me your idea by [opening an issue](https://github.com/TypistTech/imposter/issues/new).
### How about packages that use fully qualified name?
Not for now. We need a better regex(or something better than regex) in the [Transformer](src/Transformer.php) class.
Tell me your idea by [opening an issue](https://github.com/TypistTech/imposter/issues/new)
### Which composer versions are supported?
Both v1 and v2.
### Will you add support for older PHP versions?
Never! This plugin will only work on [actively supported PHP versions](https://secure.php.net/supported-versions.php).
Don't use it on **end of life** or **security fixes only** PHP versions.
### It looks awesome. Where can I find some more goodies like this
- Articles on [Typist Tech's blog](https://typist.tech)
- [Tang Rufus' WordPress plugins](https://profiles.wordpress.org/tangrufus#content-plugins) on wp.org
- More projects on [Typist Tech's GitHub profile](https://github.com/TypistTech)
- Stay tuned on [Typist Tech's newsletter](https://typist.tech/go/newsletter)
- Follow [Tang Rufus' Twitter account](https://twitter.com/TangRufus)
- **Hire [Tang Rufus](https://typist.tech/contact) to build your next awesome site**
### Where can I give 5-star reviews?
Thanks! Glad you like it. It's important to let me knows somebody is using this project. Please consider:
- [tweet](https://twitter.com/intent/tweet?url=https%3A%2F%2Fgithub.com%2FTypistTech%2Fimposter&via=tangrufus&text=Wrapping%20all%20%23composer%20vendor%20packages%20inside%20your%20own%20namespace.%20Intended%20for%20%23WordPress%20plugins&hashtags=php) something good with mentioning [@TangRufus](https://twitter.com/tangrufus)
- ★ star [the Github repo](https://github.com/TypistTech/imposter)
- [👀 watch](https://github.com/TypistTech/imposter/subscription) the Github repo
- write tutorials and blog posts
- **[hire](https://www.typist.tech/contact/) Typist Tech**
## Testing
```bash
composer test
composer style:check
```
## Alternatives
Here is a list of alternatives that I found. However, none of these satisfied my requirements.
*If you know other similar projects, feel free to edit this section!*
* [Mozart](https://github.com/coenjacobs/mozart) by Coen Jacobs
- Works with PSR0 and PSR4
- Dependency packages store in a different directory
* [PHP Scoper](https://github.com/humbug/php-scoper)
- Prefixes all PHP namespaces in a file/directory to isolate the code bundled in PHARs
## Feedback
**Please provide feedback!** We want to make this project as useful as possible.
Please [submit an issue](https://github.com/TypistTech/imposter/issues/new) and point out what you do and don't like, or fork the project and [send pull requests](https://github.com/TypistTech/imposter/pulls/).
**No issue is too small.**
## Security Vulnerabilities
If you discover a security vulnerability within this project, please email us at [imposter@typist.tech](mailto:imposter@typist.tech).
All security vulnerabilities will be promptly addressed.
## Credits
[Imposter](https://github.com/TypistTech/imposter) is a [Typist Tech](https://typist.tech) project and maintained by [Tang Rufus](https://twitter.com/TangRufus), freelance developer for [hire](https://www.typist.tech/contact/).
Full list of contributors can be found [here](https://github.com/TypistTech/imposter/graphs/contributors).
## License
[Imposter](https://github.com/TypistTech/imposter) is released under the [MIT License](https://opensource.org/licenses/MIT).

View File

@@ -0,0 +1,69 @@
{
"name": "typisttech/imposter",
"description": "Wrapping all composer vendor packages inside your own namespace. Intended for WordPress plugins.",
"keywords": [
"composer",
"dependency",
"monkey-patching",
"namespace",
"wordpress"
],
"homepage": "https://github.com/TypistTech/imposter",
"license": "MIT",
"authors": [
{
"name": "Typist Tech",
"email": "imposter@typist.tech",
"homepage": "https://typist.tech"
},
{
"name": "Tang Rufus",
"email": "tangrufus@gmail.com",
"homepage": "https://typist.tech",
"role": "Developer"
}
],
"require": {
"php": "^7.3 || ^8.0",
"ext-json": "*"
},
"require-dev": {
"codeception/codeception": "^4.1",
"codeception/mockery-module": "^0.4.0",
"codeception/module-asserts": "^1.3",
"codeception/module-filesystem": "^1.0",
"squizlabs/php_codesniffer": "^3.5"
},
"suggest": {
"typisttech/imposter-plugin": "Composer plugin to integrate composer and imposter"
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "0.6.x-dev"
}
},
"autoload": {
"psr-4": {
"TypistTech\\Imposter\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"TypistTech\\Imposter\\": "tests/unit"
}
},
"scripts": {
"style:check": "phpcs",
"style:fix": "phpcbf",
"test": "codecept run",
"test:coverage": "phpdbg -qrr ./vendor/bin/codecept run --coverage --coverage-html --coverage-xml"
},
"support": {
"email": "imposter@typist.tech",
"issues": "https://github.com/TypistTech/imposter/issues",
"source": "https://github.com/TypistTech/imposter"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace TypistTech\Imposter;
class ArrayUtil
{
public static function flattenMap(callable $callable, array $array): array
{
$map = array_map($callable, $array);
return static::flatten($map);
}
/**
* Flatten array by one level.
*
* @param array $array
*
* @return array
*/
public static function flatten(array $array): array
{
$result = [];
foreach ($array as $item) {
if (is_array($item)) {
$result = array_merge($result, array_values($item));
} else {
$result[] = $item;
}
}
return $result;
}
}

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace TypistTech\Imposter;
class Config implements ConfigInterface
{
/**
* @var string
*/
protected $packageDir;
/**
* @var array
*/
private $config;
public function __construct(string $packageDir, array $config)
{
$this->packageDir = StringUtil::addTrailingSlash($packageDir);
$this->config = $config;
}
/**
* @return string[]
*/
public function getAutoloads(): array
{
return array_map(function (string $autoload): string {
return $this->packageDir . $autoload;
}, array_unique($this->getAutoloadPaths()));
}
/**
* @return string[]
*/
private function getAutoloadPaths(): array
{
$autoloads = $this->get('autoload');
unset($autoloads['exclude-from-classmap']);
return ArrayUtil::flattenMap(function ($autoloadConfig): array {
return $this->normalizeAutoload($autoloadConfig);
}, $autoloads);
}
protected function get(string $key): array
{
return $this->config[$key] ?? [];
}
/**
* @param $autoloadConfigs
*
* @return string[]
*/
private function normalizeAutoload($autoloadConfigs): array
{
if (! is_array($autoloadConfigs)) {
return [$autoloadConfigs];
}
return ArrayUtil::flattenMap(function ($autoloadConfig): array {
return $this->normalizeAutoload($autoloadConfig);
}, $autoloadConfigs);
}
public function getPackageDir(): string
{
return $this->packageDir;
}
/**
* @return string[]
*/
public function getRequires(): array
{
return array_keys($this->get('require'));
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace TypistTech\Imposter;
class ConfigCollection implements ConfigCollectionInterface
{
/**
* @var ConfigInterface[]
*/
private $configs = [];
/**
* @param ConfigInterface $config
*
* @return void
*/
public function add(ConfigInterface $config)
{
$this->configs[$config->getPackageDir()] = $config;
}
/**
* @return string[]
*/
public function getAutoloads(): array
{
$autoloads = ArrayUtil::flattenMap(function (ConfigInterface $config) {
return $config->getAutoloads();
}, $this->all());
return array_unique($autoloads);
}
/**
* @return ConfigInterface[]
*/
public function all(): array
{
return $this->configs;
}
}

View File

@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace TypistTech\Imposter;
class ConfigCollectionFactory
{
public static function forProject(
ProjectConfigInterface $projectConfig,
Filesystem $filesystem
): ConfigCollectionInterface {
return static::addRequiredPackageConfigsRecursively(
new ConfigCollection(),
$projectConfig,
$projectConfig,
$filesystem
);
}
private static function addRequiredPackageConfigsRecursively(
ConfigCollectionInterface $configCollection,
ProjectConfigInterface $projectConfig,
ConfigInterface $config,
Filesystem $filesystem
): ConfigCollectionInterface {
$filteredRequires = static::getFilteredPackages($projectConfig, $config);
foreach ($filteredRequires as $package) {
$packageConfig = ConfigFactory::build(
$projectConfig->getVendorDir() . $package . '/composer.json',
$filesystem
);
$configCollection->add($packageConfig);
static::addRequiredPackageConfigsRecursively(
$configCollection,
$projectConfig,
$packageConfig,
$filesystem
);
}
return $configCollection;
}
/**
* @param ProjectConfigInterface $projectConfig
* @param ConfigInterface $config
*
* @return string[]
*/
private static function getFilteredPackages(ProjectConfigInterface $projectConfig, ConfigInterface $config): array
{
$requiredPackages = array_filter($config->getRequires(), function (string $package) {
return (false !== strpos($package, '/'));
});
$nonComposerPackages = array_filter($requiredPackages, function (string $package) {
return (false === strpos($package, 'composer/'));
});
return array_filter($nonComposerPackages, function (string $package) use ($projectConfig) {
return ! in_array($package, $projectConfig->getExcludes(), true);
});
}
}

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace TypistTech\Imposter;
interface ConfigCollectionInterface
{
/**
* @param ConfigInterface $config
*
* @return void
*/
public function add(ConfigInterface $config);
/**
* @return ConfigInterface[]
*/
public function all(): array;
/**
* @return string[]
*/
public function getAutoloads(): array;
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace TypistTech\Imposter;
class ConfigFactory
{
public static function build(string $path, Filesystem $filesystem): Config
{
return new Config(
$filesystem->dirname($path),
json_decode(
$filesystem->get($path),
true
)
);
}
public static function buildProjectConfig(string $path, Filesystem $filesystem): ProjectConfig
{
return new ProjectConfig(
$filesystem->dirname($path),
json_decode(
$filesystem->get($path),
true
)
);
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace TypistTech\Imposter;
interface ConfigInterface
{
/**
* @return string[]
*/
public function getAutoloads(): array;
public function getPackageDir(): string;
/**
* @return string[]
*/
public function getRequires(): array;
}

View File

@@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace TypistTech\Imposter;
use FilesystemIterator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RuntimeException;
class Filesystem implements FilesystemInterface
{
/**
* @param string $path
*
* @return \SplFileInfo[]
* @throws \UnexpectedValueException
*/
public function allFiles(string $path): array
{
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS)
);
return iterator_to_array($iterator);
}
/**
* Extract the parent directory from a file path.
*
* @param string $path
*
* @return string
*/
public function dirname(string $path): string
{
return pathinfo($path, PATHINFO_DIRNAME);
}
/**
* Get the contents of a file.
*
* @param string $path
*
* @return string
* @throws \RuntimeException
*/
public function get(string $path): string
{
if (! $this->isFile($path)) {
throw new RuntimeException('File does not exist at path ' . $path);
}
return file_get_contents($path);
}
/**
* Determine if the given path is a file.
*
* @param string $path
*
* @return bool
*/
public function isFile(string $path): bool
{
return is_file($path);
}
/**
* Determine if the given path is a directory.
*
* @param string $path
*
* @return bool
*/
public function isDir(string $path): bool
{
return is_dir($path);
}
/**
* Write the contents of a file.
*
* @param string $path
* @param string $contents
*
* @return int|false
*/
public function put(string $path, string $contents)
{
return file_put_contents($path, $contents);
}
}

View File

@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace TypistTech\Imposter;
interface FilesystemInterface
{
/**
* @param string $path
*
* @return \SplFileInfo[]
*/
public function allFiles(string $path): array;
/**
* Extract the parent directory from a file path.
*
* @param string $path
*
* @return string
*/
public function dirname(string $path): string;
/**
* Get the contents of a file.
*
* @param string $path
*
* @return string
*/
public function get(string $path): string;
/**
* Determine if the given path is a file.
*
* @param string $path
*
* @return bool
*/
public function isFile(string $path): bool;
/**
* Determine if the given path is a directory.
*
* @param string $path
*
* @return bool
*/
public function isDir(string $path);
/**
* Write the contents of a file.
*
* @param string $path
* @param string $contents
*
* @return mixed
*/
public function put(string $path, string $contents);
}

View File

@@ -0,0 +1,135 @@
<?php
declare(strict_types=1);
namespace TypistTech\Imposter;
class Imposter implements ImposterInterface
{
/**
* @var string[]
*/
private $autoloads;
/**
* @var string[]
*/
private $invalidAutoloads;
/**
* @var ConfigCollectionInterface
*/
private $configCollection;
/**
* @var TransformerInterface
*/
private $transformer;
/**
* @var FilesystemInterface
*/
private $filesystem;
/**
* Imposter constructor.
*
* @param ConfigCollectionInterface $configCollection
* @param TransformerInterface $transformer
* @param FilesystemInterface $filesystem
*/
public function __construct(
ConfigCollectionInterface $configCollection,
TransformerInterface $transformer,
FilesystemInterface $filesystem
) {
$this->configCollection = $configCollection;
$this->transformer = $transformer;
$this->filesystem = $filesystem;
}
/**
* @return ConfigCollectionInterface
*/
public function getConfigCollection(): ConfigCollectionInterface
{
return $this->configCollection;
}
/**
* @return TransformerInterface
*/
public function getTransformer(): TransformerInterface
{
return $this->transformer;
}
/**
* Transform all autoload files.
*
* @return void
*/
public function run()
{
$autoloads = $this->getAutoloads();
array_walk($autoloads, [$this, 'transform']);
}
/**
* Get all valid (exist) autoload paths.
*
* @return string[]
*/
public function getAutoloads(): array
{
if ($this->autoloads === null) {
$this->setAutoloads();
}
return $this->autoloads;
}
/**
* Get all autoload paths which defined in composer.json but not exist.
*
* @return string[]
*/
public function getInvalidAutoloads(): array
{
if ($this->invalidAutoloads === null) {
$this->setAutoloads();
}
return $this->invalidAutoloads;
}
protected function setAutoloads(): void
{
$this->autoloads = [];
$this->invalidAutoloads = [];
$autoloads = $this->configCollection->getAutoloads();
foreach ($autoloads as $autoload) {
$isValid = $this->filesystem->isFile($autoload) || $this->filesystem->isDir($autoload);
if ($isValid) {
$this->autoloads[] = $autoload;
} else {
$this->invalidAutoloads[] = $autoload;
}
}
}
/**
* Transform a file or directory recursively.
*
* @param string $target Path to the target file or directory.
*
* @return void
*/
public function transform(string $target)
{
$this->transformer->transform($target);
}
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace TypistTech\Imposter;
class ImposterFactory
{
/**
* @param string $projectPath
* @param string[] $extraExcludes
*
* @return Imposter
*/
public static function forProject(string $projectPath, array $extraExcludes = []): Imposter
{
$filesystem = new Filesystem();
$projectConfig = ConfigFactory::buildProjectConfig($projectPath . '/composer.json', $filesystem);
$projectConfig->setExtraExcludes($extraExcludes);
$transformer = new Transformer($projectConfig->getImposterNamespace(), $filesystem);
$configCollection = ConfigCollectionFactory::forProject(
$projectConfig,
$filesystem
);
return new Imposter($configCollection, $transformer, $filesystem);
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace TypistTech\Imposter;
interface ImposterInterface
{
/**
* Get all valid (exist) autoload paths.
*
* @return string[]
*/
public function getAutoloads(): array;
/**
* Get all invalid (not exist) autoload paths.
*
* @return string[]
*/
public function getInvalidAutoloads(): array;
/**
* Transform all autoload files.
*
* @return void
*/
public function run();
/**
* Transform a file or directory recursively.
*
* @param string $target Path to the target file or directory.
*
* @return void
*/
public function transform(string $target);
}

View File

@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace TypistTech\Imposter;
use UnexpectedValueException;
class ProjectConfig extends Config implements ProjectConfigInterface
{
/**
* @var string[]
*/
protected const DEFAULT_EXCLUDES = ['typisttech/imposter'];
/**
* @var string[]
*/
private $extraExcludes = [];
/**
* @return string[]
*/
public function getExcludes(): array
{
$extra = $this->get('extra');
$excludes = $extra['imposter']['excludes'] ?? [];
return array_merge(static::DEFAULT_EXCLUDES, $excludes, $this->extraExcludes);
}
public function getImposterNamespace(): string
{
$extra = $this->get('extra');
if (empty($extra['imposter']['namespace'])) {
throw new UnexpectedValueException('Imposter namespace is empty');
}
return $extra['imposter']['namespace'];
}
public function getVendorDir(): string
{
$config = $this->get('config');
$vendorDir = $config['vendor-dir'] ?? 'vendor';
return StringUtil::addTrailingSlash($this->packageDir . $vendorDir);
}
/**
* @param string[] $extraExcludes
*
* @return void
*/
public function setExtraExcludes(array $extraExcludes)
{
$this->extraExcludes = $extraExcludes;
}
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace TypistTech\Imposter;
interface ProjectConfigInterface extends ConfigInterface
{
/**
* @return string[]
*/
public function getExcludes(): array;
public function getImposterNamespace(): string;
public function getVendorDir(): string;
/**
* @param string[] $extraExcludes
*
* @return void
*/
public function setExtraExcludes(array $extraExcludes);
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace TypistTech\Imposter;
class StringUtil
{
public static function addTrailingSlash(string $string): string
{
return rtrim($string, '/\\') . '/';
}
public static function ensureDoubleBackwardSlash(string $string): string
{
$parts = explode('\\', $string);
$nonEmptyParts = array_filter($parts, function ($part) {
return ! empty($part);
});
return implode('\\\\', $nonEmptyParts) . '\\\\';
}
}

View File

@@ -0,0 +1,166 @@
<?php
declare(strict_types=1);
namespace TypistTech\Imposter;
use SplFileInfo;
class Transformer implements TransformerInterface
{
/**
* @var FilesystemInterface
*/
private $filesystem;
/**
* @var string
*/
private $namespacePrefix;
/**
* Transformer constructor.
*
* @param string $namespacePrefix
* @param FilesystemInterface $filesystem
*/
public function __construct(string $namespacePrefix, FilesystemInterface $filesystem)
{
$this->namespacePrefix = StringUtil::ensureDoubleBackwardSlash($namespacePrefix);
$this->filesystem = $filesystem;
}
/**
* Transform a file or directory recursively.
*
* @todo Skip non-php files.
*
* @param string $target Path to the target file or directory.
*
* @return void
*/
public function transform(string $target)
{
if ($this->filesystem->isFile($target)) {
$this->doTransform($target);
return;
}
$files = $this->filesystem->allFiles($target);
array_walk($files, function (SplFileInfo $file) {
$this->doTransform($file->getRealPath());
});
}
/**
* @param string $targetFile
*
* @return void
*/
private function doTransform(string $targetFile)
{
$this->prefixNamespace($targetFile);
$this->prefixUseConst($targetFile);
$this->prefixUseFunction($targetFile);
$this->prefixUse($targetFile);
}
/**
* Prefix namespace at the given path.
*
* @param string $targetFile
*
* @return void
*/
private function prefixNamespace(string $targetFile)
{
$pattern = sprintf(
'/(\s+)%1$s\\s+(?!(%2$s)|(Composer(\\\\|;)))/',
'namespace',
$this->namespacePrefix
);
$replacement = sprintf('%1$s %2$s', '${1}namespace', $this->namespacePrefix);
$this->replace($pattern, $replacement, $targetFile);
}
/**
* Replace string in the given file.
*
* @param string $pattern
* @param string $replacement
* @param string $targetFile
*
* @return void
*/
private function replace(string $pattern, string $replacement, string $targetFile)
{
$this->filesystem->put(
$targetFile,
preg_replace(
$pattern,
$replacement,
$this->filesystem->get($targetFile)
)
);
}
/**
* Prefix `use const` keywords at the given path.
*
* @param string $targetFile
*
* @return void
*/
private function prefixUseConst(string $targetFile)
{
$pattern = sprintf(
'/%1$s\\s+(?!(%2$s)|(\\\\(?!.*\\\\.*))|(Composer(\\\\|;)|(?!.*\\\\.*)))/',
'use const',
$this->namespacePrefix
);
$replacement = sprintf('%1$s %2$s', 'use const', $this->namespacePrefix);
$this->replace($pattern, $replacement, $targetFile);
}
/**
* Prefix `use function` keywords at the given path.
*
* @param string $targetFile
*
* @return void
*/
private function prefixUseFunction(string $targetFile)
{
$pattern = sprintf(
'/%1$s\\s+(?!(%2$s)|(\\\\(?!.*\\\\.*))|(Composer(\\\\|;)|(?!.*\\\\.*)))/',
'use function',
$this->namespacePrefix
);
$replacement = sprintf('%1$s %2$s', 'use function', $this->namespacePrefix);
$this->replace($pattern, $replacement, $targetFile);
}
/**
* Prefix `use` keywords at the given path.
*
* @param string $targetFile
*
* @return void
*/
private function prefixUse(string $targetFile)
{
$pattern = sprintf(
'/%1$s\\s+(?!(const)|(function)|(%2$s)|(\\\\(?!.*\\\\.*))|(Composer(\\\\|;)|(?!.*\\\\.*)))/',
'use',
$this->namespacePrefix
);
$replacement = sprintf('%1$s %2$s', 'use', $this->namespacePrefix);
$this->replace($pattern, $replacement, $targetFile);
}
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace TypistTech\Imposter;
interface TransformerInterface
{
/**
* Transform a file or directory recursively.
*
* @param string $target Path to the target file or directory.
*
* @return void
*/
public function transform(string $target);
}