Initial commit: apps.supersam24.ru

This commit is contained in:
root committed 2026-10-22 00:04:27 +03:00
commit 72afea602d
2747 files changed
+682150

No files matched your search

File diff suppressed because it is too large. Load diff
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) 2005-present, Cake Software Foundation, Inc. (https://cakefoundation.org)
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.
+91
View File
@@ -0,0 +1,91 @@
<p align="center">
<a href="https://cakephp.org/" target="_blank" >
<img alt="CakePHP" src="https://cakephp.org/v2/img/logos/CakePHP_Logo.svg" width="400" />
</a>
</p>
<p align="center">
<a href="LICENSE" target="_blank">
<img alt="Software License" src="https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square">
</a>
<a href="https://travis-ci.org/cakephp/cakephp" target="_blank">
<img alt="Build Status" src="https://img.shields.io/travis/cakephp/cakephp/master.svg?style=flat-square">
</a>
<a href="https://codecov.io/github/cakephp/cakephp" target="_blank">
<img alt="Coverage Status" src="https://img.shields.io/codecov/c/github/cakephp/cakephp.svg?style=flat-square">
</a>
<a href="https://squizlabs.github.io/PHP_CodeSniffer/analysis/cakephp/cakephp/" target="_blank">
<img alt="Code Consistency" src="https://squizlabs.github.io/PHP_CodeSniffer/analysis/cakephp/cakephp/grade.svg">
</a>
<a href="https://packagist.org/packages/cakephp/cakephp" target="_blank">
<img alt="Total Downloads" src="https://img.shields.io/packagist/dt/cakephp/cakephp.svg?style=flat-square">
</a>
<a href="https://packagist.org/packages/cakephp/cakephp" target="_blank">
<img alt="Latest Stable Version" src="https://img.shields.io/packagist/v/cakephp/cakephp.svg?style=flat-square&label=stable">
</a>
</p>
[CakePHP](https://cakephp.org) is a rapid development framework for PHP which
uses commonly known design patterns like Associative Data
Mapping, Front Controller, and MVC. Our primary goal is to provide a structured
framework that enables PHP users at all levels to rapidly develop robust web
applications, without any loss to flexibility.
## Installing CakePHP via Composer
You can install CakePHP into your project using
[Composer](https://getcomposer.org). If you're starting a new project, we
recommend using the [app skeleton](https://github.com/cakephp/app) as
a starting point. For existing applications you can run the following:
``` bash
$ composer require cakephp/cakephp
```
## Running Tests
Assuming you have PHPUnit installed system wide using one of the methods stated
[here](https://phpunit.de/manual/current/en/installation.html), you can run the
tests for CakePHP by doing the following:
1. Copy `phpunit.xml.dist` to `phpunit.xml`.
2. Add the relevant database credentials to your `phpunit.xml` if you want to run tests against
a non-SQLite datasource.
3. Run `phpunit`.
## Some Handy Links
* [CakePHP](https://cakephp.org) - The rapid development PHP framework.
* [CookBook](https://book.cakephp.org) - The CakePHP user documentation; start learning here!
* [API](https://api.cakephp.org) - A reference to CakePHP's classes.
* [Awesome CakePHP](https://github.com/FriendsOfCake/awesome-cakephp) - A list of featured resources around the framework.
* [Plugins](https://plugins.cakephp.org) - A repository of extensions to the framework.
* [The Bakery](https://bakery.cakephp.org) - Tips, tutorials and articles.
* [Community Center](https://community.cakephp.org) - A source for everything community related.
* [Training](https://training.cakephp.org) - Join a live session and get skilled with the framework.
* [CakeFest](https://cakefest.org) - Don't miss our annual CakePHP conference.
* [Cake Software Foundation](https://cakefoundation.org) - Promoting development related to CakePHP.
## Get Support!
* [Slack](https://cakesf.herokuapp.com/) - Join us on Slack.
* [#cakephp](https://webchat.freenode.net/?channels=#cakephp) on irc.freenode.net - Come chat with us, we have cake.
* [Forum](https://discourse.cakephp.org/) - Official CakePHP forum.
* [GitHub Issues](https://github.com/cakephp/cakephp/issues) - Got issues? Please tell us!
* [Roadmaps](https://github.com/cakephp/cakephp/wiki#roadmaps) - Want to contribute? Get involved!
## Contributing
* [CONTRIBUTING.md](.github/CONTRIBUTING.md) - Quick pointers for contributing to the CakePHP project.
* [CookBook "Contributing" Section](https://book.cakephp.org/3/en/contributing.html) - Details about contributing to the project.
# Security
If you’ve found a security issue in CakePHP, please use the following procedure instead of the normal bug reporting system. Instead of using the bug tracker, mailing list or IRC please send an email to security [at] cakephp.org. Emails sent to this address go to the CakePHP core team on a private mailing list.
For each report, we try to first confirm the vulnerability. Once confirmed, the CakePHP team will take the following actions:
- Acknowledge to the reporter that we’ve received the issue, and are working on a fix. We ask that the reporter keep the issue confidential until we announce it.
- Get a fix/patch prepared.
- Prepare a post describing the vulnerability, and the possible exploits.
- Release new versions of all affected versions.
- Prominently feature the problem in the release announcement.
+30
View File
@@ -0,0 +1,30 @@
# Security Policy
## Supported Versions
We support fixing security issues on the following releases:
| Version | Supported |
| ------- | ------------------ |
| 3.7.x | :white_check_mark: |
| 3.6.x | :white_check_mark: |
| <= 3.5 | :x: |
| 2.10.x | :white_check_mark: |
| <= 2.9 | :x: |
## Reporting a Vulnerability
If you’ve found a security issue in CakePHP, please use the following procedure
instead of the normal bug reporting system. Instead of using the bug tracker,
mailing list or IRC please send an email to security [at] cakephp.org. Emails
sent to this address go to the CakePHP core team on a private mailing list.
For each report, we try to first confirm the vulnerability. Once confirmed,
the CakePHP team will take the following actions:
* Acknowledge to the reporter that we’ve received the issue, and are
working on a fix. We ask that the reporter keep the issue confidential until we announce it.
* Get a fix/patch prepared.
* Prepare a post describing the vulnerability, and the possible exploits.
* Release new versions of all affected versions.
* Prominently feature the problem in the release announcement
+19
View File
@@ -0,0 +1,19 @@
////////////////////////////////////////////////////////////////////////////////////////////////////
// +--------------------------------------------------------------------------------------------+ //
// CakePHP Version
//
// Holds a static string representing the current version of CakePHP
//
// CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
// Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
//
// Licensed under The MIT License
// Redistributions of files must retain the above copyright notice.
//
// @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
// @link https://cakephp.org
// @since CakePHP(tm) v 0.2.9
// @license https://opensource.org/licenses/mit-license.php MIT License
// +--------------------------------------------------------------------------------------------+ //
////////////////////////////////////////////////////////////////////////////////////////////////////
3.8.9
+105
View File
@@ -0,0 +1,105 @@
{
"name": "cakephp/cakephp",
"description": "The CakePHP framework",
"type": "library",
"keywords": [
"framework",
"mvc",
"rapid-development",
"conventions over configuration",
"dry",
"orm",
"form",
"validation",
"psr-7"
],
"homepage": "https://cakephp.org",
"license": "MIT",
"authors": [
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/cakephp/graphs/contributors"
}
],
"support": {
"issues": "https://github.com/cakephp/cakephp/issues",
"forum": "https://stackoverflow.com/tags/cakephp",
"irc": "irc://irc.freenode.org/cakephp",
"source": "https://github.com/cakephp/cakephp"
},
"require": {
"php": ">=5.6.0",
"ext-intl": "*",
"ext-mbstring": "*",
"cakephp/chronos": "^1.0.1",
"aura/intl": "^3.0.0",
"psr/log": "^1.0.0",
"psr/simple-cache": "^1.0.0",
"zendframework/zend-diactoros": "^1.4.0"
},
"suggest": {
"ext-openssl": "To use Security::encrypt() or have secure CSRF token generation.",
"ext-curl": "To enable more efficient network calls in Http\\Client.",
"lib-ICU": "The intl PHP library, to use Text::transliterate() or Text::slug()"
},
"require-dev": {
"cakephp/cakephp-codesniffer": "^3.0",
"cakephp/chronos": "^1.2.1",
"phpunit/phpunit": "^5.7.14|^6.0"
},
"autoload": {
"psr-4": {
"Cake\\": "src/"
},
"files": [
"src/Core/functions.php",
"src/Collection/functions.php",
"src/I18n/functions.php",
"src/Utility/bootstrap.php"
]
},
"autoload-dev": {
"psr-4": {
"Cake\\PHPStan\\": "tests/PHPStan/",
"Cake\\Test\\": "tests/",
"TestApp\\": "tests/test_app/TestApp/",
"TestPlugin\\": "tests/test_app/Plugin/TestPlugin/src/",
"TestPlugin\\Test\\": "tests/test_app/Plugin/TestPlugin/tests/",
"TestPluginTwo\\": "tests/test_app/Plugin/TestPluginTwo/src/",
"Company\\TestPluginThree\\": "tests/test_app/Plugin/Company/TestPluginThree/src/",
"Company\\TestPluginThree\\Test\\": "tests/test_app/Plugin/Company/TestPluginThree/tests/",
"ParentPlugin\\": "tests/test_app/Plugin/ParentPlugin/src/",
"PluginJs\\": "tests/test_app/Plugin/PluginJs/src/"
}
},
"replace": {
"cakephp/cache": "self.version",
"cakephp/collection": "self.version",
"cakephp/core": "self.version",
"cakephp/datasource": "self.version",
"cakephp/database": "self.version",
"cakephp/event": "self.version",
"cakephp/filesystem": "self.version",
"cakephp/form": "self.version",
"cakephp/i18n": "self.version",
"cakephp/log": "self.version",
"cakephp/orm": "self.version",
"cakephp/utility": "self.version",
"cakephp/validation": "self.version"
},
"conflict": {
"phpunit/phpunit": "<5.7"
},
"scripts": {
"check": [
"@cs-check",
"@test"
],
"cs-check": "phpcs --colors --parallel=16 -p src/ tests/",
"cs-fix": "phpcbf --colors --parallel=16 -p src/ tests/",
"test": "phpunit",
"test-coverage": "phpunit --coverage-clover=clover.xml",
"phpstan": "phpstan analyse -c phpstan.neon -l 2 src/",
"phpstan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan-shim:^0.11 && mv composer.backup composer.json"
}
}
+22
View File
@@ -0,0 +1,22 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 0.2.9
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
use Cake\Routing\Router;
define('TIME_START', microtime(true));
require CAKE . 'basics.php';
// Sets the initial router state so future reloads work.
Router::reload();
File diff suppressed because it is too large. Load diff
+20
View File
@@ -0,0 +1,20 @@
<?php
/**
* Core Configurations.
*
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 1.1.11
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
$versionFile = file(CORE_PATH . 'VERSION.txt');
return [
'Cake.version' => trim(array_pop($versionFile)),
];
@@ -0,0 +1,77 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.4.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Auth;
use Cake\Core\InstanceConfigTrait;
/**
* Abstract password hashing class
*/
abstract class AbstractPasswordHasher
{
use InstanceConfigTrait;
/**
* Default config
*
* These are merged with user-provided config when the object is used.
*
* @var array
*/
protected $_defaultConfig = [];
/**
* Constructor
*
* @param array $config Array of config.
*/
public function __construct(array $config = [])
{
$this->setConfig($config);
}
/**
* Generates password hash.
*
* @param string $password Plain text password to hash.
* @return string|false Either the password hash string or false
*/
abstract public function hash($password);
/**
* Check hash. Generate hash from user provided password string or data array
* and check against existing hash.
*
* @param string $password Plain text password to hash.
* @param string $hashedPassword Existing hashed password.
* @return bool True if hashes match else false.
*/
abstract public function check($password, $hashedPassword);
/**
* Returns true if the password need to be rehashed, due to the password being
* created with anything else than the passwords generated by this class.
*
* Returns true by default since the only implementation users should rely
* on is the one provided by default in php 5.5+ or any compatible library
*
* @param string $password The password to verify
* @return bool
*/
public function needsRehash($password)
{
return password_needs_rehash($password, PASSWORD_DEFAULT);
}
}
+282
View File
@@ -0,0 +1,282 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Auth;
use Cake\Controller\ComponentRegistry;
use Cake\Core\InstanceConfigTrait;
use Cake\Event\EventListenerInterface;
use Cake\Http\Response;
use Cake\Http\ServerRequest;
use Cake\ORM\Locator\LocatorAwareTrait;
/**
* Base Authentication class with common methods and properties.
*/
abstract class BaseAuthenticate implements EventListenerInterface
{
use InstanceConfigTrait;
use LocatorAwareTrait;
/**
* Default config for this object.
*
* - `fields` The fields to use to identify a user by.
* - `userModel` The alias for users table, defaults to Users.
* - `finder` The finder method to use to fetch user record. Defaults to 'all'.
* You can set finder name as string or an array where key is finder name and value
* is an array passed to `Table::find()` options.
* E.g. ['finderName' => ['some_finder_option' => 'some_value']]
* - `passwordHasher` Password hasher class. Can be a string specifying class name
* or an array containing `className` key, any other keys will be passed as
* config to the class. Defaults to 'Default'.
* - Options `scope` and `contain` have been deprecated since 3.1. Use custom
* finder instead to modify the query to fetch user record.
*
* @var array
*/
protected $_defaultConfig = [
'fields' => [
'username' => 'username',
'password' => 'password',
],
'userModel' => 'Users',
'scope' => [],
'finder' => 'all',
'contain' => null,
'passwordHasher' => 'Default',
];
/**
* A Component registry, used to get more components.
*
* @var \Cake\Controller\ComponentRegistry
*/
protected $_registry;
/**
* Password hasher instance.
*
* @var \Cake\Auth\AbstractPasswordHasher|null
*/
protected $_passwordHasher;
/**
* Whether or not the user authenticated by this class
* requires their password to be rehashed with another algorithm.
*
* @var bool
*/
protected $_needsPasswordRehash = false;
/**
* Constructor
*
* @param \Cake\Controller\ComponentRegistry $registry The Component registry used on this request.
* @param array $config Array of config to use.
*/
public function __construct(ComponentRegistry $registry, array $config = [])
{
$this->_registry = $registry;
$this->setConfig($config);
if ($this->getConfig('scope') || $this->getConfig('contain')) {
deprecationWarning(
'The `scope` and `contain` options for Authentication are deprecated. ' .
'Use the `finder` option instead to define additional conditions.'
);
}
}
/**
* Find a user record using the username and password provided.
*
* Input passwords will be hashed even when a user doesn't exist. This
* helps mitigate timing attacks that are attempting to find valid usernames.
*
* @param string $username The username/identifier.
* @param string|null $password The password, if not provided password checking is skipped
* and result of find is returned.
* @return array|false Either false on failure, or an array of user data.
*/
protected function _findUser($username, $password = null)
{
$result = $this->_query($username)->first();
if ($result === null) {
// Waste time hashing the password, to prevent
// timing side-channels. However, don't hash
// null passwords as authentication systems
// like digest auth don't use passwords
// and hashing *could* create a timing side-channel.
if ($password !== null) {
$hasher = $this->passwordHasher();
$hasher->hash($password);
}
return false;
}
$passwordField = $this->_config['fields']['password'];
if ($password !== null) {
$hasher = $this->passwordHasher();
$hashedPassword = $result->get($passwordField);
if ($hashedPassword === null || $hashedPassword === '') {
// Waste time hashing the password, to prevent
// timing side-channels to distinguish whether
// user has password or not.
$hasher->hash($password);
return false;
}
if (!$hasher->check($password, $hashedPassword)) {
return false;
}
$this->_needsPasswordRehash = $hasher->needsRehash($hashedPassword);
$result->unsetProperty($passwordField);
}
$hidden = $result->getHidden();
if ($password === null && in_array($passwordField, $hidden, true)) {
$key = array_search($passwordField, $hidden, true);
unset($hidden[$key]);
$result->setHidden($hidden);
}
return $result->toArray();
}
/**
* Get query object for fetching user from database.
*
* @param string $username The username/identifier.
* @return \Cake\ORM\Query
*/
protected function _query($username)
{
$config = $this->_config;
$table = $this->getTableLocator()->get($config['userModel']);
$options = [
'conditions' => [$table->aliasField($config['fields']['username']) => $username],
];
if (!empty($config['scope'])) {
$options['conditions'] = array_merge($options['conditions'], $config['scope']);
}
if (!empty($config['contain'])) {
$options['contain'] = $config['contain'];
}
$finder = $config['finder'];
if (is_array($finder)) {
$options += current($finder);
$finder = key($finder);
}
if (!isset($options['username'])) {
$options['username'] = $username;
}
return $table->find($finder, $options);
}
/**
* Return password hasher object
*
* @return \Cake\Auth\AbstractPasswordHasher Password hasher instance
* @throws \RuntimeException If password hasher class not found or
* it does not extend AbstractPasswordHasher
*/
public function passwordHasher()
{
if ($this->_passwordHasher !== null) {
return $this->_passwordHasher;
}
$passwordHasher = $this->_config['passwordHasher'];
return $this->_passwordHasher = PasswordHasherFactory::build($passwordHasher);
}
/**
* Returns whether or not the password stored in the repository for the logged in user
* requires to be rehashed with another algorithm
*
* @return bool
*/
public function needsPasswordRehash()
{
return $this->_needsPasswordRehash;
}
/**
* Authenticate a user based on the request information.
*
* @param \Cake\Http\ServerRequest $request Request to get authentication information from.
* @param \Cake\Http\Response $response A response object that can have headers added.
* @return array|false Either false on failure, or an array of user data on success.
*/
abstract public function authenticate(ServerRequest $request, Response $response);
/**
* Get a user based on information in the request. Primarily used by stateless authentication
* systems like basic and digest auth.
*
* @param \Cake\Http\ServerRequest $request Request object.
* @return array|false Either false or an array of user information
*/
public function getUser(ServerRequest $request)
{
return false;
}
/**
* Handle unauthenticated access attempt. In implementation valid return values
* can be:
*
* - Null - No action taken, AuthComponent should return appropriate response.
* - Cake\Http\Response - A response object, which will cause AuthComponent to
* simply return that response.
*
* @param \Cake\Http\ServerRequest $request A request object.
* @param \Cake\Http\Response $response A response object.
* @return void
*/
public function unauthenticated(ServerRequest $request, Response $response)
{
}
/**
* Returns a list of all events that this authenticate class will listen to.
*
* An authenticate class can listen to following events fired by AuthComponent:
*
* - `Auth.afterIdentify` - Fired after a user has been identified using one of
* configured authenticate class. The callback function should have signature
* like `afterIdentify(Event $event, array $user)` when `$user` is the
* identified user record.
*
* - `Auth.logout` - Fired when AuthComponent::logout() is called. The callback
* function should have signature like `logout(Event $event, array $user)`
* where `$user` is the user about to be logged out.
*
* @return array List of events this class listens to. Defaults to `[]`.
*/
public function implementedEvents()
{
return [];
}
}
+64
View File
@@ -0,0 +1,64 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Auth;
use Cake\Controller\ComponentRegistry;
use Cake\Core\InstanceConfigTrait;
use Cake\Http\ServerRequest;
/**
* Abstract base authorization adapter for AuthComponent.
*
* @see \Cake\Controller\Component\AuthComponent::$authenticate
*/
abstract class BaseAuthorize
{
use InstanceConfigTrait;
/**
* ComponentRegistry instance for getting more components.
*
* @var \Cake\Controller\ComponentRegistry
*/
protected $_registry;
/**
* Default config for authorize objects.
*
* @var array
*/
protected $_defaultConfig = [];
/**
* Constructor
*
* @param \Cake\Controller\ComponentRegistry $registry The controller for this request.
* @param array $config An array of config. This class does not use any config.
*/
public function __construct(ComponentRegistry $registry, array $config = [])
{
$this->_registry = $registry;
$this->setConfig($config);
}
/**
* Checks user authorization.
*
* @param array|\ArrayAccess $user Active user data
* @param \Cake\Http\ServerRequest $request Request instance.
* @return bool
*/
abstract public function authorize($user, ServerRequest $request);
}
+114
View File
@@ -0,0 +1,114 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Auth;
use Cake\Http\Exception\UnauthorizedException;
use Cake\Http\Response;
use Cake\Http\ServerRequest;
/**
* Basic Authentication adapter for AuthComponent.
*
* Provides Basic HTTP authentication support for AuthComponent. Basic Auth will
* authenticate users against the configured userModel and verify the username
* and passwords match.
*
* ### Using Basic auth
*
* Load `AuthComponent` in your controller's `initialize()` and add 'Basic' in 'authenticate' key
* ```
* $this->loadComponent('Auth', [
* 'authenticate' => ['Basic']
* 'storage' => 'Memory',
* 'unauthorizedRedirect' => false,
* ]);
* ```
*
* You should set `storage` to `Memory` to prevent CakePHP from sending a
* session cookie to the client.
*
* You should set `unauthorizedRedirect` to `false`. This causes `AuthComponent` to
* throw a `ForbiddenException` exception instead of redirecting to another page.
*
* Since HTTP Basic Authentication is stateless you don't need call `setUser()`
* in your controller. The user credentials will be checked on each request. If
* valid credentials are not provided, required authentication headers will be sent
* by this authentication provider which triggers the login dialog in the browser/client.
*
* @see https://book.cakephp.org/3/en/controllers/components/authentication.html
*/
class BasicAuthenticate extends BaseAuthenticate
{
/**
* Authenticate a user using HTTP auth. Will use the configured User model and attempt a
* login using HTTP auth.
*
* @param \Cake\Http\ServerRequest $request The request to authenticate with.
* @param \Cake\Http\Response $response The response to add headers to.
* @return array|false Either false on failure, or an array of user data on success.
*/
public function authenticate(ServerRequest $request, Response $response)
{
return $this->getUser($request);
}
/**
* Get a user based on information in the request. Used by cookie-less auth for stateless clients.
*
* @param \Cake\Http\ServerRequest $request Request object.
* @return array|false Either false or an array of user information
*/
public function getUser(ServerRequest $request)
{
$username = $request->getEnv('PHP_AUTH_USER');
$pass = $request->getEnv('PHP_AUTH_PW');
if (!is_string($username) || $username === '' || !is_string($pass) || $pass === '') {
return false;
}
return $this->_findUser($username, $pass);
}
/**
* Handles an unauthenticated access attempt by sending appropriate login headers
*
* @param \Cake\Http\ServerRequest $request A request object.
* @param \Cake\Http\Response $response A response object.
* @return void
* @throws \Cake\Http\Exception\UnauthorizedException
*/
public function unauthenticated(ServerRequest $request, Response $response)
{
$Exception = new UnauthorizedException();
$Exception->responseHeader($this->loginHeaders($request));
throw $Exception;
}
/**
* Generate the login headers
*
* @param \Cake\Http\ServerRequest $request Request object.
* @return string[] Headers for logging in.
*/
public function loginHeaders(ServerRequest $request)
{
$realm = $this->getConfig('realm') ?: $request->getEnv('SERVER_NAME');
return [
'WWW-Authenticate' => sprintf('Basic realm="%s"', $realm),
];
}
}
+94
View File
@@ -0,0 +1,94 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Auth;
use Cake\Controller\ComponentRegistry;
use Cake\Controller\Controller;
use Cake\Core\Exception\Exception;
use Cake\Http\ServerRequest;
/**
* An authorization adapter for AuthComponent. Provides the ability to authorize
* using a controller callback. Your controller's isAuthorized() method should
* return a boolean to indicate whether or not the user is authorized.
*
* ```
* public function isAuthorized($user)
* {
* if ($this->request->getParam('admin')) {
* return $user['role'] === 'admin';
* }
* return !empty($user);
* }
* ```
*
* The above is simple implementation that would only authorize users of the
* 'admin' role to access admin routing.
*
* @see \Cake\Controller\Component\AuthComponent::$authenticate
*/
class ControllerAuthorize extends BaseAuthorize
{
/**
* Controller for the request.
*
* @var \Cake\Controller\Controller
*/
protected $_Controller;
/**
* {@inheritDoc}
*/
public function __construct(ComponentRegistry $registry, array $config = [])
{
parent::__construct($registry, $config);
$this->controller($registry->getController());
}
/**
* Get/set the controller this authorize object will be working with. Also
* checks that isAuthorized is implemented.
*
* @param \Cake\Controller\Controller|null $controller null to get, a controller to set.
* @return \Cake\Controller\Controller
* @throws \Cake\Core\Exception\Exception If controller does not have method `isAuthorized()`.
*/
public function controller(Controller $controller = null)
{
if ($controller) {
if (!method_exists($controller, 'isAuthorized')) {
throw new Exception(sprintf(
'%s does not implement an isAuthorized() method.',
get_class($controller)
));
}
$this->_Controller = $controller;
}
return $this->_Controller;
}
/**
* Checks user authorization using a controller callback.
*
* @param array|\ArrayAccess $user Active user data
* @param \Cake\Http\ServerRequest $request Request instance.
* @return bool
*/
public function authorize($user, ServerRequest $request)
{
return (bool)$this->_Controller->isAuthorized($user);
}
}
@@ -0,0 +1,78 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Auth;
/**
* Default password hashing class.
*/
class DefaultPasswordHasher extends AbstractPasswordHasher
{
/**
* Default config for this object.
*
* ### Options
*
* - `hashType` - Hashing algo to use. Valid values are those supported by `$algo`
* argument of `password_hash()`. Defaults to `PASSWORD_DEFAULT`
* - `hashOptions` - Associative array of options. Check the PHP manual for
* supported options for each hash type. Defaults to empty array.
*
* @var array
*/
protected $_defaultConfig = [
'hashType' => PASSWORD_DEFAULT,
'hashOptions' => [],
];
/**
* Generates password hash.
*
* @param string $password Plain text password to hash.
* @return string|false Password hash or false on failure
* @link https://book.cakephp.org/3/en/controllers/components/authentication.html#hashing-passwords
*/
public function hash($password)
{
return password_hash(
$password,
$this->_config['hashType'],
$this->_config['hashOptions']
);
}
/**
* Check hash. Generate hash for user provided password and check against existing hash.
*
* @param string $password Plain text password to hash.
* @param string $hashedPassword Existing hashed password.
* @return bool True if hashes match else false.
*/
public function check($password, $hashedPassword)
{
return password_verify($password, $hashedPassword);
}
/**
* Returns true if the password need to be rehashed, due to the password being
* created with anything else than the passwords generated by this class.
*
* @param string $password The password to verify
* @return bool
*/
public function needsRehash($password)
{
return password_needs_rehash($password, $this->_config['hashType'], $this->_config['hashOptions']);
}
}
+288
View File
@@ -0,0 +1,288 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Auth;
use Cake\Controller\ComponentRegistry;
use Cake\Http\ServerRequest;
use Cake\Utility\Security;
/**
* Digest Authentication adapter for AuthComponent.
*
* Provides Digest HTTP authentication support for AuthComponent.
*
* ### Using Digest auth
*
* Load `AuthComponent` in your controller's `initialize()` and add 'Digest' in 'authenticate' key
*
* ```
* $this->loadComponent('Auth', [
* 'authenticate' => ['Digest'],
* 'storage' => 'Memory',
* 'unauthorizedRedirect' => false,
* ]);
* ```
*
* You should set `storage` to `Memory` to prevent CakePHP from sending a
* session cookie to the client.
*
* You should set `unauthorizedRedirect` to `false`. This causes `AuthComponent` to
* throw a `ForbiddenException` exception instead of redirecting to another page.
*
* Since HTTP Digest Authentication is stateless you don't need call `setUser()`
* in your controller. The user credentials will be checked on each request. If
* valid credentials are not provided, required authentication headers will be sent
* by this authentication provider which triggers the login dialog in the browser/client.
*
* ### Generating passwords compatible with Digest authentication.
*
* DigestAuthenticate requires a special password hash that conforms to RFC2617.
* You can generate this password using `DigestAuthenticate::password()`
*
* ```
* $digestPass = DigestAuthenticate::password($username, $password, env('SERVER_NAME'));
* ```
*
* If you wish to use digest authentication alongside other authentication methods,
* it's recommended that you store the digest authentication separately. For
* example `User.digest_pass` could be used for a digest password, while
* `User.password` would store the password hash for use with other methods like
* Basic or Form.
*
* @see https://book.cakephp.org/3/en/controllers/components/authentication.html
*/
class DigestAuthenticate extends BasicAuthenticate
{
/**
* Constructor
*
* Besides the keys specified in BaseAuthenticate::$_defaultConfig,
* DigestAuthenticate uses the following extra keys:
*
* - `secret` The secret to use for nonce validation. Defaults to Security::getSalt().
* - `realm` The realm authentication is for, Defaults to the servername.
* - `qop` Defaults to 'auth', no other values are supported at this time.
* - `opaque` A string that must be returned unchanged by clients.
* Defaults to `md5($config['realm'])`
* - `nonceLifetime` The number of seconds that nonces are valid for. Defaults to 300.
*
* @param \Cake\Controller\ComponentRegistry $registry The Component registry
* used on this request.
* @param array $config Array of config to use.
*/
public function __construct(ComponentRegistry $registry, array $config = [])
{
$this->setConfig([
'nonceLifetime' => 300,
'secret' => Security::getSalt(),
'realm' => null,
'qop' => 'auth',
'opaque' => null,
]);
parent::__construct($registry, $config);
}
/**
* Get a user based on information in the request. Used by cookie-less auth for stateless clients.
*
* @param \Cake\Http\ServerRequest $request Request object.
* @return array|false Either false or an array of user information
*/
public function getUser(ServerRequest $request)
{
$digest = $this->_getDigest($request);
if (empty($digest)) {
return false;
}
$user = $this->_findUser($digest['username']);
if (empty($user)) {
return false;
}
if (!$this->validNonce($digest['nonce'])) {
return false;
}
$field = $this->_config['fields']['password'];
$password = $user[$field];
unset($user[$field]);
$hash = $this->generateResponseHash($digest, $password, $request->getEnv('ORIGINAL_REQUEST_METHOD'));
if (hash_equals($hash, $digest['response'])) {
return $user;
}
return false;
}
/**
* Gets the digest headers from the request/environment.
*
* @param \Cake\Http\ServerRequest $request Request object.
* @return array|bool Array of digest information.
*/
protected function _getDigest(ServerRequest $request)
{
$digest = $request->getEnv('PHP_AUTH_DIGEST');
if (empty($digest) && function_exists('apache_request_headers')) {
$headers = apache_request_headers();
if (!empty($headers['Authorization']) && substr($headers['Authorization'], 0, 7) === 'Digest ') {
$digest = substr($headers['Authorization'], 7);
}
}
if (empty($digest)) {
return false;
}
return $this->parseAuthData($digest);
}
/**
* Parse the digest authentication headers and split them up.
*
* @param string $digest The raw digest authentication headers.
* @return array|null An array of digest authentication headers
*/
public function parseAuthData($digest)
{
if (substr($digest, 0, 7) === 'Digest ') {
$digest = substr($digest, 7);
}
$keys = $match = [];
$req = ['nonce' => 1, 'nc' => 1, 'cnonce' => 1, 'qop' => 1, 'username' => 1, 'uri' => 1, 'response' => 1];
preg_match_all('/(\w+)=([\'"]?)([a-zA-Z0-9\:\#\%\?\&@=\.\/_-]+)\2/', $digest, $match, PREG_SET_ORDER);
foreach ($match as $i) {
$keys[$i[1]] = $i[3];
unset($req[$i[1]]);
}
if (empty($req)) {
return $keys;
}
return null;
}
/**
* Generate the response hash for a given digest array.
*
* @param array $digest Digest information containing data from DigestAuthenticate::parseAuthData().
* @param string $password The digest hash password generated with DigestAuthenticate::password()
* @param string $method Request method
* @return string Response hash
*/
public function generateResponseHash($digest, $password, $method)
{
return md5(
$password .
':' . $digest['nonce'] . ':' . $digest['nc'] . ':' . $digest['cnonce'] . ':' . $digest['qop'] . ':' .
md5($method . ':' . $digest['uri'])
);
}
/**
* Creates an auth digest password hash to store
*
* @param string $username The username to use in the digest hash.
* @param string $password The unhashed password to make a digest hash for.
* @param string $realm The realm the password is for.
* @return string the hashed password that can later be used with Digest authentication.
*/
public static function password($username, $password, $realm)
{
return md5($username . ':' . $realm . ':' . $password);
}
/**
* Generate the login headers
*
* @param \Cake\Http\ServerRequest $request Request object.
* @return string[] Headers for logging in.
*/
public function loginHeaders(ServerRequest $request)
{
$realm = $this->_config['realm'] ?: $request->getEnv('SERVER_NAME');
$options = [
'realm' => $realm,
'qop' => $this->_config['qop'],
'nonce' => $this->generateNonce(),
'opaque' => $this->_config['opaque'] ?: md5($realm),
];
$digest = $this->_getDigest($request);
if ($digest && isset($digest['nonce']) && !$this->validNonce($digest['nonce'])) {
$options['stale'] = true;
}
$opts = [];
foreach ($options as $k => $v) {
if (is_bool($v)) {
$v = $v ? 'true' : 'false';
$opts[] = sprintf('%s=%s', $k, $v);
} else {
$opts[] = sprintf('%s="%s"', $k, $v);
}
}
return [
'WWW-Authenticate' => 'Digest ' . implode(',', $opts),
];
}
/**
* Generate a nonce value that is validated in future requests.
*
* @return string
*/
protected function generateNonce()
{
$expiryTime = microtime(true) + $this->getConfig('nonceLifetime');
$secret = $this->getConfig('secret');
$signatureValue = hash_hmac('sha256', $expiryTime . ':' . $secret, $secret);
$nonceValue = $expiryTime . ':' . $signatureValue;
return base64_encode($nonceValue);
}
/**
* Check the nonce to ensure it is valid and not expired.
*
* @param string $nonce The nonce value to check.
* @return bool
*/
protected function validNonce($nonce)
{
$value = base64_decode($nonce);
if ($value === false) {
return false;
}
$parts = explode(':', $value);
if (count($parts) !== 2) {
return false;
}
list($expires, $checksum) = $parts;
if ($expires < microtime(true)) {
return false;
}
$secret = $this->getConfig('secret');
$check = hash_hmac('sha256', $expires . ':' . $secret, $secret);
return hash_equals($check, $checksum);
}
}
@@ -0,0 +1,104 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Auth;
/**
* A password hasher that can use multiple different hashes where only
* one is the preferred one. This is useful when trying to migrate an
* existing database of users from one password type to another.
*/
class FallbackPasswordHasher extends AbstractPasswordHasher
{
/**
* Default config for this object.
*
* @var array
*/
protected $_defaultConfig = [
'hashers' => [],
];
/**
* Holds the list of password hasher objects that will be used
*
* @var \Cake\Auth\AbstractPasswordHasher[]
*/
protected $_hashers = [];
/**
* Constructor
*
* @param array $config configuration options for this object. Requires the
* `hashers` key to be present in the array with a list of other hashers to be
* used
*/
public function __construct(array $config = [])
{
parent::__construct($config);
foreach ($this->_config['hashers'] as $key => $hasher) {
if (is_array($hasher) && !isset($hasher['className'])) {
$hasher['className'] = $key;
}
$this->_hashers[] = PasswordHasherFactory::build($hasher);
}
}
/**
* Generates password hash.
*
* Uses the first password hasher in the list to generate the hash
*
* @param string $password Plain text password to hash.
* @return string|false Password hash
*/
public function hash($password)
{
return $this->_hashers[0]->hash($password);
}
/**
* Verifies that the provided password corresponds to its hashed version
*
* This will iterate over all configured hashers until one of them returns
* true.
*
* @param string $password Plain text password to hash.
* @param string $hashedPassword Existing hashed password.
* @return bool True if hashes match else false.
*/
public function check($password, $hashedPassword)
{
foreach ($this->_hashers as $hasher) {
if ($hasher->check($password, $hashedPassword)) {
return true;
}
}
return false;
}
/**
* Returns true if the password need to be rehashed, with the first hasher present
* in the list of hashers
*
* @param string $password The password to verify
* @return bool
*/
public function needsRehash($password)
{
return $this->_hashers[0]->needsRehash($password);
}
}
+89
View File
@@ -0,0 +1,89 @@
<?php
/**
*
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Auth;
use Cake\Http\Response;
use Cake\Http\ServerRequest;
/**
* Form authentication adapter for AuthComponent.
*
* Allows you to authenticate users based on form POST data.
* Usually, this is a login form that users enter information into.
*
* ### Using Form auth
*
* Load `AuthComponent` in your controller's `initialize()` and add 'Form' in 'authenticate' key
*
* ```
* $this->loadComponent('Auth', [
* 'authenticate' => [
* 'Form' => [
* 'fields' => ['username' => 'email', 'password' => 'passwd'],
* 'finder' => 'auth',
* ]
* ]
* ]);
* ```
*
* When configuring FormAuthenticate you can pass in config to which fields, model and finder
* are used. See `BaseAuthenticate::$_defaultConfig` for more information.
*
* @see https://book.cakephp.org/3/en/controllers/components/authentication.html
*/
class FormAuthenticate extends BaseAuthenticate
{
/**
* Checks the fields to ensure they are supplied.
*
* @param \Cake\Http\ServerRequest $request The request that contains login information.
* @param array $fields The fields to be checked.
* @return bool False if the fields have not been supplied. True if they exist.
*/
protected function _checkFields(ServerRequest $request, array $fields)
{
foreach ([$fields['username'], $fields['password']] as $field) {
$value = $request->getData($field);
if (empty($value) || !is_string($value)) {
return false;
}
}
return true;
}
/**
* Authenticates the identity contained in a request. Will use the `config.userModel`, and `config.fields`
* to find POST data that is used to find a matching record in the `config.userModel`. Will return false if
* there is no post data, either username or password is missing, or if the scope conditions have not been met.
*
* @param \Cake\Http\ServerRequest $request The request that contains login information.
* @param \Cake\Http\Response $response Unused response object.
* @return array|false False on login failure. An array of User data on success.
*/
public function authenticate(ServerRequest $request, Response $response)
{
$fields = $this->_config['fields'];
if (!$this->_checkFields($request, $fields)) {
return false;
}
return $this->_findUser(
$request->getData($fields['username']),
$request->getData($fields['password'])
);
}
}
@@ -0,0 +1,58 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Auth;
use Cake\Core\App;
use RuntimeException;
/**
* Builds password hashing objects
*/
class PasswordHasherFactory
{
/**
* Returns password hasher object out of a hasher name or a configuration array
*
* @param string|array $passwordHasher Name of the password hasher or an array with
* at least the key `className` set to the name of the class to use
* @return \Cake\Auth\AbstractPasswordHasher Password hasher instance
* @throws \RuntimeException If password hasher class not found or
* it does not extend Cake\Auth\AbstractPasswordHasher
*/
public static function build($passwordHasher)
{
$config = [];
if (is_string($passwordHasher)) {
$class = $passwordHasher;
} else {
$class = $passwordHasher['className'];
$config = $passwordHasher;
unset($config['className']);
}
$className = App::className($class, 'Auth', 'PasswordHasher');
if ($className === false) {
throw new RuntimeException(sprintf('Password hasher class "%s" was not found.', $class));
}
$hasher = new $className($config);
if (!($hasher instanceof AbstractPasswordHasher)) {
throw new RuntimeException('Password hasher must extend AbstractPasswordHasher class.');
}
return $hasher;
}
}
@@ -0,0 +1,77 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.1.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Auth\Storage;
/**
* Memory based non-persistent storage for authenticated user record.
*/
class MemoryStorage implements StorageInterface
{
/**
* User record.
*
* @var \ArrayAccess|array|null
*/
protected $_user;
/**
* Redirect URL.
*
* @var string|array|null
*/
protected $_redirectUrl;
/**
* {@inheritDoc}
*/
public function read()
{
return $this->_user;
}
/**
* {@inheritDoc}
*/
public function write($user)
{
$this->_user = $user;
}
/**
* {@inheritDoc}
*/
public function delete()
{
$this->_user = null;
}
/**
* {@inheritDoc}
*/
public function redirectUrl($url = null)
{
if ($url === null) {
return $this->_redirectUrl;
}
if ($url === false) {
$this->_redirectUrl = null;
return null;
}
$this->_redirectUrl = $url;
}
}
@@ -0,0 +1,137 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.1.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Auth\Storage;
use Cake\Core\InstanceConfigTrait;
use Cake\Http\Response;
use Cake\Http\ServerRequest;
/**
* Session based persistent storage for authenticated user record.
*/
class SessionStorage implements StorageInterface
{
use InstanceConfigTrait;
/**
* User record.
*
* Stores user record array if fetched from session or false if session
* does not have user record.
*
* @var \ArrayAccess|array|false|null
*/
protected $_user;
/**
* Session object.
*
* @var \Cake\Http\Session
*/
protected $_session;
/**
* Default configuration for this class.
*
* Keys:
*
* - `key` - Session key used to store user record.
* - `redirect` - Session key used to store redirect URL.
*
* @var array
*/
protected $_defaultConfig = [
'key' => 'Auth.User',
'redirect' => 'Auth.redirect',
];
/**
* Constructor.
*
* @param \Cake\Http\ServerRequest $request Request instance.
* @param \Cake\Http\Response $response Response instance.
* @param array $config Configuration list.
*/
public function __construct(ServerRequest $request, Response $response, array $config = [])
{
$this->_session = $request->getSession();
$this->setConfig($config);
}
/**
* Read user record from session.
*
* @return array|null User record if available else null.
*/
public function read()
{
if ($this->_user !== null) {
return $this->_user ?: null;
}
$this->_user = $this->_session->read($this->_config['key']) ?: false;
return $this->_user ?: null;
}
/**
* Write user record to session.
*
* The session id is also renewed to help mitigate issues with session replays.
*
* @param array|\ArrayAccess $user User record.
* @return void
*/
public function write($user)
{
$this->_user = $user;
$this->_session->renew();
$this->_session->write($this->_config['key'], $user);
}
/**
* Delete user record from session.
*
* The session id is also renewed to help mitigate issues with session replays.
*
* @return void
*/
public function delete()
{
$this->_user = false;
$this->_session->delete($this->_config['key']);
$this->_session->renew();
}
/**
* {@inheritDoc}
*/
public function redirectUrl($url = null)
{
if ($url === null) {
return $this->_session->read($this->_config['redirect']);
}
if ($url === false) {
$this->_session->delete($this->_config['redirect']);
return null;
}
$this->_session->write($this->_config['redirect'], $url);
}
}
@@ -0,0 +1,53 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.1.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Auth\Storage;
/**
* Describes the methods that any class representing an Auth data storage should
* comply with.
*/
interface StorageInterface
{
/**
* Read user record.
*
* @return \ArrayAccess|array|null
*/
public function read();
/**
* Write user record.
*
* @param array|\ArrayAccess $user User record.
* @return void
*/
public function write($user);
/**
* Delete user record.
*
* @return void
*/
public function delete();
/**
* Get/set redirect URL.
*
* @param mixed $url Redirect URL. If `null` returns current URL. If `false`
* deletes currently set URL.
* @return string|array|null
*/
public function redirectUrl($url = null);
}
+71
View File
@@ -0,0 +1,71 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Auth;
use Cake\Core\Configure;
use Cake\Error\Debugger;
use Cake\Utility\Security;
/**
* Password hashing class that use weak hashing algorithms. This class is
* intended only to be used with legacy databases where passwords have
* not been migrated to a stronger algorithm yet.
*/
class WeakPasswordHasher extends AbstractPasswordHasher
{
/**
* Default config for this object.
*
* @var array
*/
protected $_defaultConfig = [
'hashType' => null,
];
/**
* {@inheritDoc}
*/
public function __construct(array $config = [])
{
if (Configure::read('debug')) {
Debugger::checkSecurityKeys();
}
parent::__construct($config);
}
/**
* Generates password hash.
*
* @param string $password Plain text password to hash.
* @return string Password hash
*/
public function hash($password)
{
return Security::hash($password, $this->_config['hashType'], true);
}
/**
* Check hash. Generate hash for user provided password and check against existing hash.
*
* @param string $password Plain text password to hash.
* @param string $hashedPassword Existing hashed password.
* @return bool True if hashes match else false.
*/
public function check($password, $hashedPassword)
{
return $hashedPassword === $this->hash($password);
}
}
+696
View File
@@ -0,0 +1,696 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 1.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Cache;
use Cake\Cache\Engine\NullEngine;
use Cake\Core\ObjectRegistry;
use Cake\Core\StaticConfigTrait;
use InvalidArgumentException;
use RuntimeException;
/**
* Cache provides a consistent interface to Caching in your application. It allows you
* to use several different Cache engines, without coupling your application to a specific
* implementation. It also allows you to change out cache storage or configuration without effecting
* the rest of your application.
*
* ### Configuring Cache engines
*
* You can configure Cache engines in your application's `Config/cache.php` file.
* A sample configuration would be:
*
* ```
* Cache::config('shared', [
* 'className' => 'Cake\Cache\Engine\ApcuEngine',
* 'prefix' => 'my_app_'
* ]);
* ```
*
* This would configure an APCu cache engine to the 'shared' alias. You could then read and write
* to that cache alias by using it for the `$config` parameter in the various Cache methods.
*
* In general all Cache operations are supported by all cache engines.
* However, Cache::increment() and Cache::decrement() are not supported by File caching.
*
* There are 7 built-in caching engines:
*
* - `ApcuEngine` - Uses the APCu object cache, one of the fastest caching engines.
* - `ArrayEngine` - Uses only memory to store all data, not actually a persistent engine.
* Can be useful in test or CLI environment.
* - `FileEngine` - Uses simple files to store content. Poor performance, but good for
* storing large objects, or things that are not IO sensitive. Well suited to development
* as it is an easy cache to inspect and manually flush.
* - `MemcacheEngine` - Uses the PECL::Memcache extension and Memcached for storage.
* Fast reads/writes, and benefits from memcache being distributed.
* - `RedisEngine` - Uses redis and php-redis extension to store cache data.
* - `WincacheEngine` - Uses Windows Cache Extension for PHP. Supports wincache 1.1.0 and higher.
* This engine is recommended to people deploying on windows with IIS.
* - `XcacheEngine` - Uses the Xcache extension, an alternative to APCu.
*
* See Cache engine documentation for expected configuration keys.
*
* @see config/app.php for configuration settings
*/
class Cache
{
use StaticConfigTrait;
/**
* An array mapping url schemes to fully qualified caching engine
* class names.
*
* @var string[]
*/
protected static $_dsnClassMap = [
'array' => 'Cake\Cache\Engine\ArrayEngine',
'apc' => 'Cake\Cache\Engine\ApcuEngine', // @deprecated Since 3.6. Use apcu instead.
'apcu' => 'Cake\Cache\Engine\ApcuEngine',
'file' => 'Cake\Cache\Engine\FileEngine',
'memcached' => 'Cake\Cache\Engine\MemcachedEngine',
'null' => 'Cake\Cache\Engine\NullEngine',
'redis' => 'Cake\Cache\Engine\RedisEngine',
'wincache' => 'Cake\Cache\Engine\WincacheEngine',
'xcache' => 'Cake\Cache\Engine\XcacheEngine',
];
/**
* Flag for tracking whether or not caching is enabled.
*
* @var bool
*/
protected static $_enabled = true;
/**
* Group to Config mapping
*
* @var array
*/
protected static $_groups = [];
/**
* Cache Registry used for creating and using cache adapters.
*
* @var \Cake\Core\ObjectRegistry
*/
protected static $_registry;
/**
* Returns the Cache Registry instance used for creating and using cache adapters.
*
* @return \Cake\Core\ObjectRegistry
*/
public static function getRegistry()
{
if (!static::$_registry) {
static::$_registry = new CacheRegistry();
}
return static::$_registry;
}
/**
* Sets the Cache Registry instance used for creating and using cache adapters.
*
* Also allows for injecting of a new registry instance.
*
* @param \Cake\Core\ObjectRegistry $registry Injectable registry object.
* @return void
*/
public static function setRegistry(ObjectRegistry $registry)
{
static::$_registry = $registry;
}
/**
* Returns the Cache Registry instance used for creating and using cache adapters.
* Also allows for injecting of a new registry instance.
*
* @param \Cake\Core\ObjectRegistry|null $registry Injectable registry object.
* @return \Cake\Core\ObjectRegistry
* @deprecated Deprecated since 3.5. Use getRegistry() and setRegistry() instead.
*/
public static function registry(ObjectRegistry $registry = null)
{
deprecationWarning('Use Cache::getRegistry() and Cache::setRegistry() instead.');
if ($registry) {
static::setRegistry($registry);
}
return static::getRegistry();
}
/**
* Finds and builds the instance of the required engine class.
*
* @param string $name Name of the config array that needs an engine instance built
* @return void
* @throws \InvalidArgumentException When a cache engine cannot be created.
*/
protected static function _buildEngine($name)
{
$registry = static::getRegistry();
if (empty(static::$_config[$name]['className'])) {
throw new InvalidArgumentException(
sprintf('The "%s" cache configuration does not exist.', $name)
);
}
$config = static::$_config[$name];
try {
$registry->load($name, $config);
} catch (RuntimeException $e) {
if (!array_key_exists('fallback', $config)) {
$registry->set($name, new NullEngine());
trigger_error($e->getMessage(), E_USER_WARNING);
return;
}
if ($config['fallback'] === false) {
throw $e;
}
if ($config['fallback'] === $name) {
throw new InvalidArgumentException(sprintf('"%s" cache configuration cannot fallback to itself.', $name), null, $e);
}
$fallbackEngine = clone static::engine($config['fallback']);
$newConfig = $config + ['groups' => [], 'prefix' => null];
$fallbackEngine->setConfig('groups', $newConfig['groups'], false);
if ($newConfig['prefix']) {
$fallbackEngine->setConfig('prefix', $newConfig['prefix'], false);
}
$registry->set($name, $fallbackEngine);
}
if ($config['className'] instanceof CacheEngine) {
$config = $config['className']->getConfig();
}
if (!empty($config['groups'])) {
foreach ($config['groups'] as $group) {
static::$_groups[$group][] = $name;
static::$_groups[$group] = array_unique(static::$_groups[$group]);
sort(static::$_groups[$group]);
}
}
}
/**
* Fetch the engine attached to a specific configuration name.
*
* If the cache engine & configuration are missing an error will be
* triggered.
*
* @param string $config The configuration name you want an engine for.
* @return \Cake\Cache\CacheEngine When caching is disabled a null engine will be returned.
* @deprecated 3.7.0 Use Cache::pool() instead. In 4.0 all cache engines will implement the
* PSR16 interface and this method does not return objects implementing that interface.
*/
public static function engine($config)
{
if (!static::$_enabled) {
return new NullEngine();
}
$registry = static::getRegistry();
if (isset($registry->{$config})) {
return $registry->{$config};
}
static::_buildEngine($config);
return $registry->{$config};
}
/**
* Get a SimpleCacheEngine object for the named cache pool.
*
* @param string $config The name of the configured cache backend.
* @return \Cake\Cache\SimpleCacheEngine
*/
public static function pool($config)
{
return new SimpleCacheEngine(static::engine($config));
}
/**
* Garbage collection
*
* Permanently remove all expired and deleted data
*
* @param string $config [optional] The config name you wish to have garbage collected. Defaults to 'default'
* @param int|null $expires [optional] An expires timestamp. Defaults to NULL
* @return void
* @deprecated 3.7.0 Will be removed in 4.0
*/
public static function gc($config = 'default', $expires = null)
{
$engine = static::engine($config);
$engine->gc($expires);
}
/**
* Write data for key into cache.
*
* ### Usage:
*
* Writing to the active cache config:
*
* ```
* Cache::write('cached_data', $data);
* ```
*
* Writing to a specific cache config:
*
* ```
* Cache::write('cached_data', $data, 'long_term');
* ```
*
* @param string $key Identifier for the data
* @param mixed $value Data to be cached - anything except a resource
* @param string $config Optional string configuration name to write to. Defaults to 'default'
* @return bool True if the data was successfully cached, false on failure
*/
public static function write($key, $value, $config = 'default')
{
if (is_resource($value)) {
return false;
}
$backend = static::pool($config);
$success = $backend->set($key, $value);
if ($success === false && $value !== '') {
trigger_error(
sprintf(
"%s cache was unable to write '%s' to %s cache",
$config,
$key,
get_class($backend)
),
E_USER_WARNING
);
}
return $success;
}
/**
* Write data for many keys into cache.
*
* ### Usage:
*
* Writing to the active cache config:
*
* ```
* Cache::writeMany(['cached_data_1' => 'data 1', 'cached_data_2' => 'data 2']);
* ```
*
* Writing to a specific cache config:
*
* ```
* Cache::writeMany(['cached_data_1' => 'data 1', 'cached_data_2' => 'data 2'], 'long_term');
* ```
*
* @param array $data An array of data to be stored in the cache
* @param string $config Optional string configuration name to write to. Defaults to 'default'
* @return array of bools for each key provided, indicating true for success or false for fail
* @throws \RuntimeException
*/
public static function writeMany($data, $config = 'default')
{
$engine = static::engine($config);
$return = $engine->writeMany($data);
foreach ($return as $key => $success) {
if ($success === false && $data[$key] !== '') {
throw new RuntimeException(sprintf(
'%s cache was unable to write \'%s\' to %s cache',
$config,
$key,
get_class($engine)
));
}
}
return $return;
}
/**
* Read a key from the cache.
*
* ### Usage:
*
* Reading from the active cache configuration.
*
* ```
* Cache::read('my_data');
* ```
*
* Reading from a specific cache configuration.
*
* ```
* Cache::read('my_data', 'long_term');
* ```
*
* @param string $key Identifier for the data
* @param string $config optional name of the configuration to use. Defaults to 'default'
* @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it
*/
public static function read($key, $config = 'default')
{
// TODO In 4.x this needs to change to use pool()
$engine = static::engine($config);
return $engine->read($key);
}
/**
* Read multiple keys from the cache.
*
* ### Usage:
*
* Reading multiple keys from the active cache configuration.
*
* ```
* Cache::readMany(['my_data_1', 'my_data_2]);
* ```
*
* Reading from a specific cache configuration.
*
* ```
* Cache::readMany(['my_data_1', 'my_data_2], 'long_term');
* ```
*
* @param array $keys an array of keys to fetch from the cache
* @param string $config optional name of the configuration to use. Defaults to 'default'
* @return array An array containing, for each of the given $keys, the cached data or false if cached data could not be
* retrieved.
*/
public static function readMany($keys, $config = 'default')
{
// In 4.x this needs to change to use pool()
$engine = static::engine($config);
return $engine->readMany($keys);
}
/**
* Increment a number under the key and return incremented value.
*
* @param string $key Identifier for the data
* @param int $offset How much to add
* @param string $config Optional string configuration name. Defaults to 'default'
* @return int|false New value, or false if the data doesn't exist, is not integer,
* or if there was an error fetching it.
*/
public static function increment($key, $offset = 1, $config = 'default')
{
$engine = static::pool($config);
if (!is_int($offset) || $offset < 0) {
return false;
}
return $engine->increment($key, $offset);
}
/**
* Decrement a number under the key and return decremented value.
*
* @param string $key Identifier for the data
* @param int $offset How much to subtract
* @param string $config Optional string configuration name. Defaults to 'default'
* @return int|false New value, or false if the data doesn't exist, is not integer,
* or if there was an error fetching it
*/
public static function decrement($key, $offset = 1, $config = 'default')
{
$engine = static::pool($config);
if (!is_int($offset) || $offset < 0) {
return false;
}
return $engine->decrement($key, $offset);
}
/**
* Delete a key from the cache.
*
* ### Usage:
*
* Deleting from the active cache configuration.
*
* ```
* Cache::delete('my_data');
* ```
*
* Deleting from a specific cache configuration.
*
* ```
* Cache::delete('my_data', 'long_term');
* ```
*
* @param string $key Identifier for the data
* @param string $config name of the configuration to use. Defaults to 'default'
* @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed
*/
public static function delete($key, $config = 'default')
{
$backend = static::pool($config);
return $backend->delete($key);
}
/**
* Delete many keys from the cache.
*
* ### Usage:
*
* Deleting multiple keys from the active cache configuration.
*
* ```
* Cache::deleteMany(['my_data_1', 'my_data_2']);
* ```
*
* Deleting from a specific cache configuration.
*
* ```
* Cache::deleteMany(['my_data_1', 'my_data_2], 'long_term');
* ```
*
* @param array $keys Array of cache keys to be deleted
* @param string $config name of the configuration to use. Defaults to 'default'
* @return array of boolean values that are true if the value was successfully deleted,
* false if it didn't exist or couldn't be removed.
*/
public static function deleteMany($keys, $config = 'default')
{
$backend = static::pool($config);
$return = [];
foreach ($keys as $key) {
$return[$key] = $backend->delete($key);
}
return $return;
}
/**
* Delete all keys from the cache.
*
* @param bool $check if true will check expiration, otherwise delete all. This parameter
* will become a no-op value in 4.0 as it is deprecated.
* @param string $config name of the configuration to use. Defaults to 'default'
* @return bool True if the cache was successfully cleared, false otherwise
*/
public static function clear($check = false, $config = 'default')
{
$engine = static::engine($config);
return $engine->clear($check);
}
/**
* Delete all keys from the cache from all configurations.
*
* @param bool $check if true will check expiration, otherwise delete all. This parameter
* will become a no-op value in 4.0 as it is deprecated.
* @return array Status code. For each configuration, it reports the status of the operation
*/
public static function clearAll($check = false)
{
$status = [];
foreach (self::configured() as $config) {
$status[$config] = self::clear($check, $config);
}
return $status;
}
/**
* Delete all keys from the cache belonging to the same group.
*
* @param string $group name of the group to be cleared
* @param string $config name of the configuration to use. Defaults to 'default'
* @return bool True if the cache group was successfully cleared, false otherwise
*/
public static function clearGroup($group, $config = 'default')
{
$engine = static::pool($config);
return $engine->clearGroup($group);
}
/**
* Retrieve group names to config mapping.
*
* ```
* Cache::config('daily', ['duration' => '1 day', 'groups' => ['posts']]);
* Cache::config('weekly', ['duration' => '1 week', 'groups' => ['posts', 'archive']]);
* $configs = Cache::groupConfigs('posts');
* ```
*
* $configs will equal to `['posts' => ['daily', 'weekly']]`
* Calling this method will load all the configured engines.
*
* @param string|null $group group name or null to retrieve all group mappings
* @return array map of group and all configuration that has the same group
* @throws \InvalidArgumentException
*/
public static function groupConfigs($group = null)
{
foreach (array_keys(static::$_config) as $config) {
static::engine($config);
}
if ($group === null) {
return static::$_groups;
}
if (isset(self::$_groups[$group])) {
return [$group => self::$_groups[$group]];
}
throw new InvalidArgumentException(sprintf('Invalid cache group %s', $group));
}
/**
* Re-enable caching.
*
* If caching has been disabled with Cache::disable() this method will reverse that effect.
*
* @return void
*/
public static function enable()
{
static::$_enabled = true;
}
/**
* Disable caching.
*
* When disabled all cache operations will return null.
*
* @return void
*/
public static function disable()
{
static::$_enabled = false;
}
/**
* Check whether or not caching is enabled.
*
* @return bool
*/
public static function enabled()
{
return static::$_enabled;
}
/**
* Provides the ability to easily do read-through caching.
*
* When called if the $key is not set in $config, the $callable function
* will be invoked. The results will then be stored into the cache config
* at key.
*
* Examples:
*
* Using a Closure to provide data, assume `$this` is a Table object:
*
* ```
* $results = Cache::remember('all_articles', function () {
* return $this->find('all');
* });
* ```
*
* @param string $key The cache key to read/store data at.
* @param callable $callable The callable that provides data in the case when
* the cache key is empty. Can be any callable type supported by your PHP.
* @param string $config The cache configuration to use for this operation.
* Defaults to default.
* @return mixed If the key is found: the cached data, false if the data
* missing/expired, or an error. If the key is not found: boolean of the
* success of the write
*/
public static function remember($key, $callable, $config = 'default')
{
$existing = self::read($key, $config);
if ($existing !== false) {
return $existing;
}
$results = call_user_func($callable);
self::write($key, $results, $config);
return $results;
}
/**
* Write data for key into a cache engine if it doesn't exist already.
*
* ### Usage:
*
* Writing to the active cache config:
*
* ```
* Cache::add('cached_data', $data);
* ```
*
* Writing to a specific cache config:
*
* ```
* Cache::add('cached_data', $data, 'long_term');
* ```
*
* @param string $key Identifier for the data.
* @param mixed $value Data to be cached - anything except a resource.
* @param string $config Optional string configuration name to write to. Defaults to 'default'.
* @return bool True if the data was successfully cached, false on failure.
* Or if the key existed already.
*/
public static function add($key, $value, $config = 'default')
{
$pool = static::pool($config);
if (is_resource($value)) {
return false;
}
return $pool->add($key, $value);
}
}
+295
View File
@@ -0,0 +1,295 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 1.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Cache;
use Cake\Core\InstanceConfigTrait;
use InvalidArgumentException;
/**
* Storage engine for CakePHP caching
*/
abstract class CacheEngine
{
use InstanceConfigTrait;
/**
* The default cache configuration is overridden in most cache adapters. These are
* the keys that are common to all adapters. If overridden, this property is not used.
*
* - `duration` Specify how long items in this cache configuration last.
* - `groups` List of groups or 'tags' associated to every key stored in this config.
* handy for deleting a complete group from cache.
* - `prefix` Prefix appended to all entries. Good for when you need to share a keyspace
* with either another cache config or another application.
* - `probability` Probability of hitting a cache gc cleanup. Setting to 0 will disable
* cache::gc from ever being called automatically.
* - `warnOnWriteFailures` Some engines, such as ApcuEngine, may raise warnings on
* write failures.
*
* @var array
*/
protected $_defaultConfig = [
'duration' => 3600,
'groups' => [],
'prefix' => 'cake_',
'probability' => 100,
'warnOnWriteFailures' => true,
];
/**
* Contains the compiled string with all groups
* prefixes to be prepended to every key in this cache engine
*
* @var string
*/
protected $_groupPrefix;
/**
* Initialize the cache engine
*
* Called automatically by the cache frontend. Merge the runtime config with the defaults
* before use.
*
* @param array $config Associative array of parameters for the engine
* @return bool True if the engine has been successfully initialized, false if not
*/
public function init(array $config = [])
{
$this->setConfig($config);
if (!empty($this->_config['groups'])) {
sort($this->_config['groups']);
$this->_groupPrefix = str_repeat('%s_', count($this->_config['groups']));
}
if (!is_numeric($this->_config['duration'])) {
$this->_config['duration'] = strtotime($this->_config['duration']) - time();
}
return true;
}
/**
* Garbage collection
*
* Permanently remove all expired and deleted data
*
* @param int|null $expires [optional] An expires timestamp, invalidating all data before.
* @return void
*/
public function gc($expires = null)
{
}
/**
* Write value for a key into cache
*
* @param string $key Identifier for the data
* @param mixed $value Data to be cached
* @return bool True if the data was successfully cached, false on failure
*/
abstract public function write($key, $value);
/**
* Write data for many keys into cache
*
* @param array $data An array of data to be stored in the cache
* @return array of bools for each key provided, true if the data was successfully cached, false on failure
*/
public function writeMany($data)
{
$return = [];
foreach ($data as $key => $value) {
$return[$key] = $this->write($key, $value);
}
return $return;
}
/**
* Read a key from the cache
*
* @param string $key Identifier for the data
* @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it
*/
abstract public function read($key);
/**
* Read multiple keys from the cache
*
* @param array $keys An array of identifiers for the data
* @return array For each cache key (given as the array key) the cache data associated or false if the data doesn't
* exist, has expired, or if there was an error fetching it
*/
public function readMany($keys)
{
$return = [];
foreach ($keys as $key) {
$return[$key] = $this->read($key);
}
return $return;
}
/**
* Increment a number under the key and return incremented value
*
* @param string $key Identifier for the data
* @param int $offset How much to add
* @return bool|int New incremented value, false otherwise
*/
abstract public function increment($key, $offset = 1);
/**
* Decrement a number under the key and return decremented value
*
* @param string $key Identifier for the data
* @param int $offset How much to subtract
* @return bool|int New incremented value, false otherwise
*/
abstract public function decrement($key, $offset = 1);
/**
* Delete a key from the cache
*
* @param string $key Identifier for the data
* @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed
*/
abstract public function delete($key);
/**
* Delete all keys from the cache
*
* @param bool $check if true will check expiration, otherwise delete all
* @return bool True if the cache was successfully cleared, false otherwise
*/
abstract public function clear($check);
/**
* Deletes keys from the cache
*
* @param array $keys An array of identifiers for the data
* @return array For each provided cache key (given back as the array key) true if the value was successfully deleted,
* false if it didn't exist or couldn't be removed
*/
public function deleteMany($keys)
{
$return = [];
foreach ($keys as $key) {
$return[$key] = $this->delete($key);
}
return $return;
}
/**
* Add a key to the cache if it does not already exist.
*
* Defaults to a non-atomic implementation. Subclasses should
* prefer atomic implementations.
*
* @param string $key Identifier for the data.
* @param mixed $value Data to be cached.
* @return bool True if the data was successfully cached, false on failure.
*/
public function add($key, $value)
{
$cachedValue = $this->read($key);
if ($cachedValue === false) {
return $this->write($key, $value);
}
return false;
}
/**
* Clears all values belonging to a group. Is up to the implementing engine
* to decide whether actually delete the keys or just simulate it to achieve
* the same result.
*
* @param string $group name of the group to be cleared
* @return bool
*/
public function clearGroup($group)
{
return false;
}
/**
* Does whatever initialization for each group is required
* and returns the `group value` for each of them, this is
* the token representing each group in the cache key
*
* @return string[]
*/
public function groups()
{
return $this->_config['groups'];
}
/**
* Generates a safe key for use with cache engine storage engines.
*
* @param string $key the key passed over
* @return string|false string key or false
*/
public function key($key)
{
if (!$key) {
return false;
}
$prefix = '';
if ($this->_groupPrefix) {
$prefix = md5(implode('_', $this->groups()));
}
$key = preg_replace('/[\s]+/', '_', strtolower(trim(str_replace([DIRECTORY_SEPARATOR, '/', '.'], '_', (string)$key))));
return $prefix . $key;
}
/**
* Generates a safe key, taking account of the configured key prefix
*
* @param string $key the key passed over
* @return string Key
* @throws \InvalidArgumentException If key's value is empty
*/
protected function _key($key)
{
$key = $this->key($key);
if ($key === false) {
throw new InvalidArgumentException('An empty value is not valid as a cache key');
}
return $this->_config['prefix'] . $key;
}
/**
* Cache Engines may trigger warnings if they encounter failures during operation,
* if option warnOnWriteFailures is set to true.
*
* @param string $message The warning message.
* @return void
*/
protected function warning($message)
{
if ($this->getConfig('warnOnWriteFailures') !== true) {
return;
}
triggerWarning($message);
}
}
@@ -0,0 +1,67 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.7.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Cache;
/**
* Interface for cache engines that defines methods
* outside of the PSR16 interface that are used by `Cache`.
*
* Internally Cache uses this interface when calling engine
* methods.
*
* @since 3.7.0
*/
interface CacheEngineInterface
{
/**
* Write data for key into a cache engine if it doesn't exist already.
*
* @param string $key Identifier for the data.
* @param mixed $value Data to be cached - anything except a resource.
* @return bool True if the data was successfully cached, false on failure.
* Or if the key existed already.
*/
public function add($key, $value);
/**
* Increment a number under the key and return incremented value
*
* @param string $key Identifier for the data
* @param int $offset How much to add
* @return bool|int New incremented value, false otherwise
*/
public function increment($key, $offset = 1);
/**
* Decrement a number under the key and return decremented value
*
* @param string $key Identifier for the data
* @param int $offset How much to subtract
* @return bool|int New incremented value, false otherwise
*/
public function decrement($key, $offset = 1);
/**
* Clear all values belonging to the named group.
*
* Each implementation needs to decide whether actually
* delete the keys or just augment a group generation value
* to achieve the same result.
*
* @param string $group name of the group to be cleared
* @return bool
*/
public function clearGroup($group);
}
+113
View File
@@ -0,0 +1,113 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Cache;
use BadMethodCallException;
use Cake\Core\App;
use Cake\Core\ObjectRegistry;
use RuntimeException;
/**
* An object registry for cache engines.
*
* Used by Cake\Cache\Cache to load and manage cache engines.
*/
class CacheRegistry extends ObjectRegistry
{
/**
* Resolve a cache engine classname.
*
* Part of the template method for Cake\Core\ObjectRegistry::load()
*
* @param string $class Partial classname to resolve.
* @return string|false Either the correct classname or false.
*/
protected function _resolveClassName($class)
{
if (is_object($class)) {
return $class;
}
return App::className($class, 'Cache/Engine', 'Engine');
}
/**
* Throws an exception when a cache engine is missing.
*
* Part of the template method for Cake\Core\ObjectRegistry::load()
*
* @param string $class The classname that is missing.
* @param string $plugin The plugin the cache is missing in.
* @return void
* @throws \BadMethodCallException
*/
protected function _throwMissingClassError($class, $plugin)
{
throw new BadMethodCallException(sprintf('Cache engine %s is not available.', $class));
}
/**
* Create the cache engine instance.
*
* Part of the template method for Cake\Core\ObjectRegistry::load()
*
* @param string|\Cake\Cache\CacheEngine $class The classname or object to make.
* @param string $alias The alias of the object.
* @param array $config An array of settings to use for the cache engine.
* @return \Cake\Cache\CacheEngine The constructed CacheEngine class.
* @throws \RuntimeException when an object doesn't implement the correct interface.
*/
protected function _create($class, $alias, $config)
{
if (is_object($class)) {
$instance = $class;
}
unset($config['className']);
if (!isset($instance)) {
$instance = new $class($config);
}
if (!($instance instanceof CacheEngine)) {
throw new RuntimeException(
'Cache engines must use Cake\Cache\CacheEngine as a base class.'
);
}
if (!$instance->init($config)) {
throw new RuntimeException(
sprintf('Cache engine %s is not properly configured.', get_class($instance))
);
}
$config = $instance->getConfig();
if (isset($config['probability']) && time() % $config['probability'] === 0) {
$instance->gc();
}
return $instance;
}
/**
* Remove a single adapter from the registry.
*
* @param string $name The adapter name.
* @return void
*/
public function unload($name)
{
unset($this->_loaded[$name]);
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 1.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Cache\Engine;
// @deprecated 3.6.0 Add backwards compat alias.
class_alias('Cake\Cache\Engine\ApcuEngine', 'Cake\Cache\Engine\ApcEngine');
deprecationWarning('Use Cake\Cache\Engine\ApcuEngine instead of Cake\Cache\Engine\ApcEngine.');
+234
View File
@@ -0,0 +1,234 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.5.4
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Cache\Engine;
use APCuIterator;
use Cake\Cache\CacheEngine;
/**
* APCu storage engine for cache
*/
class ApcuEngine extends CacheEngine
{
/**
* Contains the compiled group names
* (prefixed with the global configuration prefix)
*
* @var string[]
*/
protected $_compiledGroupNames = [];
/**
* Initialize the Cache Engine
*
* Called automatically by the cache frontend
*
* @param array $config array of setting for the engine
* @return bool True if the engine has been successfully initialized, false if not
*/
public function init(array $config = [])
{
if (!extension_loaded('apcu')) {
return false;
}
return parent::init($config);
}
/**
* Write data for key into cache
*
* @param string $key Identifier for the data
* @param mixed $value Data to be cached
* @return bool True if the data was successfully cached, false on failure
* @link https://secure.php.net/manual/en/function.apcu-store.php
*/
public function write($key, $value)
{
$key = $this->_key($key);
$duration = $this->_config['duration'];
return apcu_store($key, $value, $duration);
}
/**
* Read a key from the cache
*
* @param string $key Identifier for the data
* @return mixed The cached data, or false if the data doesn't exist,
* has expired, or if there was an error fetching it
* @link https://secure.php.net/manual/en/function.apcu-fetch.php
*/
public function read($key)
{
$key = $this->_key($key);
return apcu_fetch($key);
}
/**
* Increments the value of an integer cached key
*
* @param string $key Identifier for the data
* @param int $offset How much to increment
* @return int|false New incremented value, false otherwise
* @link https://secure.php.net/manual/en/function.apcu-inc.php
*/
public function increment($key, $offset = 1)
{
$key = $this->_key($key);
return apcu_inc($key, $offset);
}
/**
* Decrements the value of an integer cached key
*
* @param string $key Identifier for the data
* @param int $offset How much to subtract
* @return int|false New decremented value, false otherwise
* @link https://secure.php.net/manual/en/function.apcu-dec.php
*/
public function decrement($key, $offset = 1)
{
$key = $this->_key($key);
return apcu_dec($key, $offset);
}
/**
* Delete a key from the cache
*
* @param string $key Identifier for the data
* @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed
* @link https://secure.php.net/manual/en/function.apcu-delete.php
*/
public function delete($key)
{
$key = $this->_key($key);
return apcu_delete($key);
}
/**
* Delete all keys from the cache. This will clear every cache config using APC.
*
* @param bool $check If true, nothing will be cleared, as entries are removed
* from APC as they expired. This flag is really only used by FileEngine.
* @return bool True Returns true.
* @link https://secure.php.net/manual/en/function.apcu-cache-info.php
* @link https://secure.php.net/manual/en/function.apcu-delete.php
*/
public function clear($check)
{
if ($check) {
return true;
}
if (class_exists('APCuIterator', false)) {
$iterator = new APCuIterator(
'/^' . preg_quote($this->_config['prefix'], '/') . '/',
APC_ITER_NONE
);
apcu_delete($iterator);
return true;
}
$cache = apcu_cache_info(); // Raises warning by itself already
foreach ($cache['cache_list'] as $key) {
if (strpos($key['info'], $this->_config['prefix']) === 0) {
apcu_delete($key['info']);
}
}
return true;
}
/**
* Write data for key into cache if it doesn't exist already.
* If it already exists, it fails and returns false.
*
* @param string $key Identifier for the data.
* @param mixed $value Data to be cached.
* @return bool True if the data was successfully cached, false on failure.
* @link https://secure.php.net/manual/en/function.apcu-add.php
*/
public function add($key, $value)
{
$key = $this->_key($key);
$duration = $this->_config['duration'];
return apcu_add($key, $value, $duration);
}
/**
* Returns the `group value` for each of the configured groups
* If the group initial value was not found, then it initializes
* the group accordingly.
*
* @return string[]
* @link https://secure.php.net/manual/en/function.apcu-fetch.php
* @link https://secure.php.net/manual/en/function.apcu-store.php
*/
public function groups()
{
if (empty($this->_compiledGroupNames)) {
foreach ($this->_config['groups'] as $group) {
$this->_compiledGroupNames[] = $this->_config['prefix'] . $group;
}
}
$success = false;
$groups = apcu_fetch($this->_compiledGroupNames, $success);
if ($success && count($groups) !== count($this->_config['groups'])) {
foreach ($this->_compiledGroupNames as $group) {
if (!isset($groups[$group])) {
$value = 1;
if (apcu_store($group, $value) === false) {
$this->warning(
sprintf('Failed to store key "%s" with value "%s" into APCu cache.', $group, $value)
);
}
$groups[$group] = $value;
}
}
ksort($groups);
}
$result = [];
$groups = array_values($groups);
foreach ($this->_config['groups'] as $i => $group) {
$result[] = $group . $groups[$i];
}
return $result;
}
/**
* Increments the group value to simulate deletion of all keys under a group
* old values will remain in storage until they expire.
*
* @param string $group The group to clear.
* @return bool success
* @link https://secure.php.net/manual/en/function.apcu-inc.php
*/
public function clearGroup($group)
{
$success = false;
apcu_inc($this->_config['prefix'] . $group, 1, $success);
return $success;
}
}
+183
View File
@@ -0,0 +1,183 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.7.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Cache\Engine;
use Cake\Cache\CacheEngine;
/**
* Array storage engine for cache.
*
* Not actually a persistent cache engine. All data is only
* stored in memory for the duration of a single process. While not
* useful in production settings this engine can be useful in tests
* or console tools where you don't want the overhead of interacting
* with a cache servers, but want the work saving properties a cache
* provides.
*/
class ArrayEngine extends CacheEngine
{
/**
* Cached data.
*
* Structured as [key => [exp => expiration, val => value]]
*
* @var array
*/
protected $data = [];
/**
* Write data for key into cache
*
* @param string $key Identifier for the data
* @param mixed $value Data to be cached
* @return bool True if the data was successfully cached, false on failure
*/
public function write($key, $value)
{
$key = $this->_key($key);
$expires = time() + $this->_config['duration'];
$this->data[$key] = ['exp' => $expires, 'val' => $value];
return true;
}
/**
* Read a key from the cache
*
* @param string $key Identifier for the data
* @return mixed The cached data, or false if the data doesn't exist,
* has expired, or if there was an error fetching it
*/
public function read($key)
{
$key = $this->_key($key);
if (!isset($this->data[$key])) {
return false;
}
$data = $this->data[$key];
// Check expiration
$now = time();
if ($data['exp'] <= $now) {
unset($this->data[$key]);
return false;
}
return $data['val'];
}
/**
* Increments the value of an integer cached key
*
* @param string $key Identifier for the data
* @param int $offset How much to increment
* @return int|false New incremented value, false otherwise
*/
public function increment($key, $offset = 1)
{
if (!$this->read($key)) {
$this->write($key, 0);
}
$key = $this->_key($key);
$this->data[$key]['val'] += $offset;
return $this->data[$key]['val'];
}
/**
* Decrements the value of an integer cached key
*
* @param string $key Identifier for the data
* @param int $offset How much to subtract
* @return int|false New decremented value, false otherwise
*/
public function decrement($key, $offset = 1)
{
if (!$this->read($key)) {
$this->write($key, 0);
}
$key = $this->_key($key);
$this->data[$key]['val'] -= $offset;
return $this->data[$key]['val'];
}
/**
* Delete a key from the cache
*
* @param string $key Identifier for the data
* @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed
*/
public function delete($key)
{
$key = $this->_key($key);
unset($this->data[$key]);
return true;
}
/**
* Delete all keys from the cache. This will clear every cache config using APC.
*
* @param bool $check Unused argument required by interface.
* @return bool True Returns true.
*/
public function clear($check)
{
$this->data = [];
return true;
}
/**
* Returns the `group value` for each of the configured groups
* If the group initial value was not found, then it initializes
* the group accordingly.
*
* @return string[]
*/
public function groups()
{
$result = [];
foreach ($this->_config['groups'] as $group) {
$key = $this->_config['prefix'] . $group;
if (!isset($this->data[$key])) {
$this->data[$key] = ['exp' => PHP_INT_MAX, 'val' => 1];
}
$value = $this->data[$key]['val'];
$result[] = $group . $value;
}
return $result;
}
/**
* Increments the group value to simulate deletion of all keys under a group
* old values will remain in storage until they expire.
*
* @param string $group The group to clear.
* @return bool success
*/
public function clearGroup($group)
{
$key = $this->_config['prefix'] . $group;
if (isset($this->data[$key])) {
$this->data[$key]['val'] += 1;
}
return true;
}
}
+520
View File
@@ -0,0 +1,520 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 1.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Cache\Engine;
use Cake\Cache\CacheEngine;
use Cake\Utility\Inflector;
use CallbackFilterIterator;
use Exception;
use LogicException;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplFileInfo;
use SplFileObject;
/**
* File Storage engine for cache. Filestorage is the slowest cache storage
* to read and write. However, it is good for servers that don't have other storage
* engine available, or have content which is not performance sensitive.
*
* You can configure a FileEngine cache, using Cache::config()
*/
class FileEngine extends CacheEngine
{
/**
* Instance of SplFileObject class
*
* @var \SplFileObject|null
*/
protected $_File;
/**
* The default config used unless overridden by runtime configuration
*
* - `duration` Specify how long items in this cache configuration last.
* - `groups` List of groups or 'tags' associated to every key stored in this config.
* handy for deleting a complete group from cache.
* - `isWindows` Automatically populated with whether the host is windows or not
* - `lock` Used by FileCache. Should files be locked before writing to them?
* - `mask` The mask used for created files
* - `path` Path to where cachefiles should be saved. Defaults to system's temp dir.
* - `prefix` Prepended to all entries. Good for when you need to share a keyspace
* with either another cache config or another application.
* - `probability` Probability of hitting a cache gc cleanup. Setting to 0 will disable
* cache::gc from ever being called automatically.
* - `serialize` Should cache objects be serialized first.
*
* @var array
*/
protected $_defaultConfig = [
'duration' => 3600,
'groups' => [],
'isWindows' => false,
'lock' => true,
'mask' => 0664,
'path' => null,
'prefix' => 'cake_',
'probability' => 100,
'serialize' => true,
];
/**
* True unless FileEngine::__active(); fails
*
* @var bool
*/
protected $_init = true;
/**
* Initialize File Cache Engine
*
* Called automatically by the cache frontend.
*
* @param array $config array of setting for the engine
* @return bool True if the engine has been successfully initialized, false if not
*/
public function init(array $config = [])
{
parent::init($config);
if ($this->_config['path'] === null) {
$this->_config['path'] = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'cake_cache' . DIRECTORY_SEPARATOR;
}
if (DIRECTORY_SEPARATOR === '\\') {
$this->_config['isWindows'] = true;
}
if (substr($this->_config['path'], -1) !== DIRECTORY_SEPARATOR) {
$this->_config['path'] .= DIRECTORY_SEPARATOR;
}
if ($this->_groupPrefix) {
$this->_groupPrefix = str_replace('_', DIRECTORY_SEPARATOR, $this->_groupPrefix);
}
return $this->_active();
}
/**
* Garbage collection. Permanently remove all expired and deleted data
*
* @param int|null $expires [optional] An expires timestamp, invalidating all data before.
* @return bool True if garbage collection was successful, false on failure
*/
public function gc($expires = null)
{
return $this->clear(true);
}
/**
* Write data for key into cache
*
* @param string $key Identifier for the data
* @param mixed $data Data to be cached
* @return bool True if the data was successfully cached, false on failure
*/
public function write($key, $data)
{
if ($data === '' || !$this->_init) {
return false;
}
$key = $this->_key($key);
if ($this->_setKey($key, true) === false) {
return false;
}
$lineBreak = "\n";
if ($this->_config['isWindows']) {
$lineBreak = "\r\n";
}
if (!empty($this->_config['serialize'])) {
if ($this->_config['isWindows']) {
$data = str_replace('\\', '\\\\\\\\', serialize($data));
} else {
$data = serialize($data);
}
}
$duration = $this->_config['duration'];
$expires = time() + $duration;
$contents = implode([$expires, $lineBreak, $data, $lineBreak]);
if ($this->_config['lock']) {
$this->_File->flock(LOCK_EX);
}
$this->_File->rewind();
$success = $this->_File->ftruncate(0) &&
$this->_File->fwrite($contents) &&
$this->_File->fflush();
if ($this->_config['lock']) {
$this->_File->flock(LOCK_UN);
}
$this->_File = null;
return $success;
}
/**
* Read a key from the cache
*
* @param string $key Identifier for the data
* @return mixed The cached data, or false if the data doesn't exist, has
* expired, or if there was an error fetching it
*/
public function read($key)
{
$key = $this->_key($key);
if (!$this->_init || $this->_setKey($key) === false) {
return false;
}
if ($this->_config['lock']) {
$this->_File->flock(LOCK_SH);
}
$this->_File->rewind();
$time = time();
$cachetime = (int)$this->_File->current();
if ($cachetime < $time) {
if ($this->_config['lock']) {
$this->_File->flock(LOCK_UN);
}
return false;
}
$data = '';
$this->_File->next();
while ($this->_File->valid()) {
$data .= $this->_File->current();
$this->_File->next();
}
if ($this->_config['lock']) {
$this->_File->flock(LOCK_UN);
}
$data = trim($data);
if ($data !== '' && !empty($this->_config['serialize'])) {
if ($this->_config['isWindows']) {
$data = str_replace('\\\\\\\\', '\\', $data);
}
$data = unserialize((string)$data);
}
return $data;
}
/**
* Delete a key from the cache
*
* @param string $key Identifier for the data
* @return bool True if the value was successfully deleted, false if it didn't
* exist or couldn't be removed
*/
public function delete($key)
{
$key = $this->_key($key);
if ($this->_setKey($key) === false || !$this->_init) {
return false;
}
$path = $this->_File->getRealPath();
$this->_File = null;
//@codingStandardsIgnoreStart
return @unlink($path);
//@codingStandardsIgnoreEnd
}
/**
* Delete all values from the cache
*
* @param bool $check Optional - only delete expired cache items
* @return bool True if the cache was successfully cleared, false otherwise
*/
public function clear($check)
{
if (!$this->_init) {
return false;
}
$this->_File = null;
$threshold = $now = false;
if ($check) {
$now = time();
$threshold = $now - $this->_config['duration'];
}
$this->_clearDirectory($this->_config['path'], $now, $threshold);
$directory = new RecursiveDirectoryIterator(
$this->_config['path'],
\FilesystemIterator::SKIP_DOTS
);
$contents = new RecursiveIteratorIterator(
$directory,
RecursiveIteratorIterator::SELF_FIRST
);
$cleared = [];
foreach ($contents as $path) {
if ($path->isFile()) {
continue;
}
$path = $path->getRealPath() . DIRECTORY_SEPARATOR;
if (!in_array($path, $cleared, true)) {
$this->_clearDirectory($path, $now, $threshold);
$cleared[] = $path;
}
}
return true;
}
/**
* Used to clear a directory of matching files.
*
* @param string $path The path to search.
* @param int $now The current timestamp
* @param int $threshold Any file not modified after this value will be deleted.
* @return void
*/
protected function _clearDirectory($path, $now, $threshold)
{
if (!is_dir($path)) {
return;
}
$prefixLength = strlen($this->_config['prefix']);
$dir = dir($path);
while (($entry = $dir->read()) !== false) {
if (substr($entry, 0, $prefixLength) !== $this->_config['prefix']) {
continue;
}
try {
$file = new SplFileObject($path . $entry, 'r');
} catch (Exception $e) {
continue;
}
if ($threshold) {
$mtime = $file->getMTime();
if ($mtime > $threshold) {
continue;
}
$expires = (int)$file->current();
if ($expires > $now) {
continue;
}
}
if ($file->isFile()) {
$filePath = $file->getRealPath();
$file = null;
//@codingStandardsIgnoreStart
@unlink($filePath);
//@codingStandardsIgnoreEnd
}
}
$dir->close();
}
/**
* Not implemented
*
* @param string $key The key to decrement
* @param int $offset The number to offset
* @return void
* @throws \LogicException
*/
public function decrement($key, $offset = 1)
{
throw new LogicException('Files cannot be atomically decremented.');
}
/**
* Not implemented
*
* @param string $key The key to increment
* @param int $offset The number to offset
* @return void
* @throws \LogicException
*/
public function increment($key, $offset = 1)
{
throw new LogicException('Files cannot be atomically incremented.');
}
/**
* Sets the current cache key this class is managing, and creates a writable SplFileObject
* for the cache file the key is referring to.
*
* @param string $key The key
* @param bool $createKey Whether the key should be created if it doesn't exists, or not
* @return bool true if the cache key could be set, false otherwise
*/
protected function _setKey($key, $createKey = false)
{
$groups = null;
if ($this->_groupPrefix) {
$groups = vsprintf($this->_groupPrefix, $this->groups());
}
$dir = $this->_config['path'] . $groups;
if (!is_dir($dir)) {
mkdir($dir, 0775, true);
}
$path = new SplFileInfo($dir . $key);
if (!$createKey && !$path->isFile()) {
return false;
}
if (
empty($this->_File) ||
$this->_File->getBasename() !== $key ||
$this->_File->valid() === false
) {
$exists = file_exists($path->getPathname());
try {
$this->_File = $path->openFile('c+');
} catch (Exception $e) {
trigger_error($e->getMessage(), E_USER_WARNING);
return false;
}
unset($path);
if (!$exists && !chmod($this->_File->getPathname(), (int)$this->_config['mask'])) {
trigger_error(sprintf(
'Could not apply permission mask "%s" on cache file "%s"',
$this->_File->getPathname(),
$this->_config['mask']
), E_USER_WARNING);
}
}
return true;
}
/**
* Determine if cache directory is writable
*
* @return bool
*/
protected function _active()
{
$dir = new SplFileInfo($this->_config['path']);
$path = $dir->getPathname();
$success = true;
if (!is_dir($path)) {
//@codingStandardsIgnoreStart
$success = @mkdir($path, 0775, true);
//@codingStandardsIgnoreEnd
}
$isWritableDir = ($dir->isDir() && $dir->isWritable());
if (!$success || ($this->_init && !$isWritableDir)) {
$this->_init = false;
trigger_error(sprintf(
'%s is not writable',
$this->_config['path']
), E_USER_WARNING);
}
return $success;
}
/**
* Generates a safe key for use with cache engine storage engines.
*
* @param string $key the key passed over
* @return mixed string $key or false
*/
public function key($key)
{
if (empty($key)) {
return false;
}
$key = Inflector::underscore(str_replace(
[DIRECTORY_SEPARATOR, '/', '.', '<', '>', '?', ':', '|', '*', '"'],
'_',
(string)$key
));
return $key;
}
/**
* Recursively deletes all files under any directory named as $group
*
* @param string $group The group to clear.
* @return bool success
*/
public function clearGroup($group)
{
$this->_File = null;
$prefix = (string)$this->_config['prefix'];
$directoryIterator = new RecursiveDirectoryIterator($this->_config['path']);
$contents = new RecursiveIteratorIterator(
$directoryIterator,
RecursiveIteratorIterator::CHILD_FIRST
);
$filtered = new CallbackFilterIterator(
$contents,
function (SplFileInfo $current) use ($group, $prefix) {
if (!$current->isFile()) {
return false;
}
$hasPrefix = $prefix === ''
|| strpos($current->getBasename(), $prefix) === 0;
if ($hasPrefix === false) {
return false;
}
$pos = strpos(
$current->getPathname(),
DIRECTORY_SEPARATOR . $group . DIRECTORY_SEPARATOR
);
return $pos !== false;
}
);
foreach ($filtered as $object) {
$path = $object->getPathname();
$object = null;
// @codingStandardsIgnoreLine
@unlink($path);
}
return true;
}
}
@@ -0,0 +1,534 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.5.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Cache\Engine;
use Cake\Cache\CacheEngine;
use InvalidArgumentException;
use Memcached;
/**
* Memcached storage engine for cache. Memcached has some limitations in the amount of
* control you have over expire times far in the future. See MemcachedEngine::write() for
* more information.
*
* Memcached engine supports binary protocol and igbinary
* serialization (if memcached extension is compiled with --enable-igbinary).
* Compressed keys can also be incremented/decremented.
*/
class MemcachedEngine extends CacheEngine
{
/**
* memcached wrapper.
*
* @var \Memcached
*/
protected $_Memcached;
/**
* The default config used unless overridden by runtime configuration
*
* - `compress` Whether to compress data
* - `duration` Specify how long items in this cache configuration last.
* - `groups` List of groups or 'tags' associated to every key stored in this config.
* handy for deleting a complete group from cache.
* - `username` Login to access the Memcache server
* - `password` Password to access the Memcache server
* - `persistent` The name of the persistent connection. All configurations using
* the same persistent value will share a single underlying connection.
* - `prefix` Prepended to all entries. Good for when you need to share a keyspace
* with either another cache config or another application.
* - `probability` Probability of hitting a cache gc cleanup. Setting to 0 will disable
* cache::gc from ever being called automatically.
* - `serialize` The serializer engine used to serialize data. Available engines are php,
* igbinary and json. Beside php, the memcached extension must be compiled with the
* appropriate serializer support.
* - `servers` String or array of memcached servers. If an array MemcacheEngine will use
* them as a pool.
* - `options` - Additional options for the memcached client. Should be an array of option => value.
* Use the \Memcached::OPT_* constants as keys.
*
* @var array
*/
protected $_defaultConfig = [
'compress' => false,
'duration' => 3600,
'groups' => [],
'host' => null,
'username' => null,
'password' => null,
'persistent' => false,
'port' => null,
'prefix' => 'cake_',
'probability' => 100,
'serialize' => 'php',
'servers' => ['127.0.0.1'],
'options' => [],
];
/**
* List of available serializer engines
*
* Memcached must be compiled with json and igbinary support to use these engines
*
* @var array
*/
protected $_serializers = [];
/**
* @var string[]
*/
protected $_compiledGroupNames = [];
/**
* Initialize the Cache Engine
*
* Called automatically by the cache frontend
*
* @param array $config array of setting for the engine
* @return bool True if the engine has been successfully initialized, false if not
* @throws \InvalidArgumentException When you try use authentication without
* Memcached compiled with SASL support
*/
public function init(array $config = [])
{
if (!extension_loaded('memcached')) {
return false;
}
$this->_serializers = [
'igbinary' => Memcached::SERIALIZER_IGBINARY,
'json' => Memcached::SERIALIZER_JSON,
'php' => Memcached::SERIALIZER_PHP,
];
if (defined('Memcached::HAVE_MSGPACK') && Memcached::HAVE_MSGPACK) {
$this->_serializers['msgpack'] = Memcached::SERIALIZER_MSGPACK;
}
parent::init($config);
if (!empty($config['host'])) {
if (empty($config['port'])) {
$config['servers'] = [$config['host']];
} else {
$config['servers'] = [sprintf('%s:%d', $config['host'], $config['port'])];
}
}
if (isset($config['servers'])) {
$this->setConfig('servers', $config['servers'], false);
}
if (!is_array($this->_config['servers'])) {
$this->_config['servers'] = [$this->_config['servers']];
}
if (isset($this->_Memcached)) {
return true;
}
if ($this->_config['persistent']) {
$this->_Memcached = new Memcached((string)$this->_config['persistent']);
} else {
$this->_Memcached = new Memcached();
}
$this->_setOptions();
if (count($this->_Memcached->getServerList())) {
return true;
}
$servers = [];
foreach ($this->_config['servers'] as $server) {
$servers[] = $this->parseServerString($server);
}
if (!$this->_Memcached->addServers($servers)) {
return false;
}
if (is_array($this->_config['options'])) {
foreach ($this->_config['options'] as $opt => $value) {
$this->_Memcached->setOption($opt, $value);
}
}
if (empty($this->_config['username']) && !empty($this->_config['login'])) {
throw new InvalidArgumentException(
'Please pass "username" instead of "login" for connecting to Memcached'
);
}
if ($this->_config['username'] !== null && $this->_config['password'] !== null) {
if (!method_exists($this->_Memcached, 'setSaslAuthData')) {
throw new InvalidArgumentException(
'Memcached extension is not built with SASL support'
);
}
$this->_Memcached->setOption(Memcached::OPT_BINARY_PROTOCOL, true);
$this->_Memcached->setSaslAuthData(
$this->_config['username'],
$this->_config['password']
);
}
return true;
}
/**
* Settings the memcached instance
*
* @return void
* @throws \InvalidArgumentException When the Memcached extension is not built
* with the desired serializer engine.
*/
protected function _setOptions()
{
$this->_Memcached->setOption(Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
$serializer = strtolower($this->_config['serialize']);
if (!isset($this->_serializers[$serializer])) {
throw new InvalidArgumentException(
sprintf('%s is not a valid serializer engine for Memcached', $serializer)
);
}
if (
$serializer !== 'php' &&
!constant('Memcached::HAVE_' . strtoupper($serializer))
) {
throw new InvalidArgumentException(
sprintf('Memcached extension is not compiled with %s support', $serializer)
);
}
$this->_Memcached->setOption(
Memcached::OPT_SERIALIZER,
$this->_serializers[$serializer]
);
// Check for Amazon ElastiCache instance
if (
defined('Memcached::OPT_CLIENT_MODE') &&
defined('Memcached::DYNAMIC_CLIENT_MODE')
) {
$this->_Memcached->setOption(
Memcached::OPT_CLIENT_MODE,
Memcached::DYNAMIC_CLIENT_MODE
);
}
$this->_Memcached->setOption(
Memcached::OPT_COMPRESSION,
(bool)$this->_config['compress']
);
}
/**
* Parses the server address into the host/port. Handles both IPv6 and IPv4
* addresses and Unix sockets
*
* @param string $server The server address string.
* @return array Array containing host, port
*/
public function parseServerString($server)
{
$socketTransport = 'unix://';
if (strpos($server, $socketTransport) === 0) {
return [substr($server, strlen($socketTransport)), 0];
}
if (substr($server, 0, 1) === '[') {
$position = strpos($server, ']:');
if ($position !== false) {
$position++;
}
} else {
$position = strpos($server, ':');
}
$port = 11211;
$host = $server;
if ($position !== false) {
$host = substr($server, 0, $position);
$port = substr($server, $position + 1);
}
return [$host, (int)$port];
}
/**
* Backwards compatible alias of parseServerString
*
* @param string $server The server address string.
* @return array Array containing host, port
* @deprecated 3.4.13 Will be removed in 4.0.0
*/
protected function _parseServerString($server)
{
return $this->parseServerString($server);
}
/**
* Read an option value from the memcached connection.
*
* @param string $name The option name to read.
* @return string|int|bool|null
*/
public function getOption($name)
{
return $this->_Memcached->getOption($name);
}
/**
* Write data for key into cache. When using memcached as your cache engine
* remember that the Memcached pecl extension does not support cache expiry
* times greater than 30 days in the future. Any duration greater than 30 days
* will be treated as never expiring.
*
* @param string $key Identifier for the data
* @param mixed $value Data to be cached
* @return bool True if the data was successfully cached, false on failure
* @see https://secure.php.net/manual/en/memcache.set.php
*/
public function write($key, $value)
{
$duration = $this->_config['duration'];
if ($duration > 30 * DAY) {
$duration = 0;
}
$key = $this->_key($key);
return $this->_Memcached->set($key, $value, $duration);
}
/**
* Write many cache entries to the cache at once
*
* @param array $data An array of data to be stored in the cache
* @return array of bools for each key provided, true if the data was
* successfully cached, false on failure
*/
public function writeMany($data)
{
$cacheData = [];
foreach ($data as $key => $value) {
$cacheData[$this->_key($key)] = $value;
}
$success = $this->_Memcached->setMulti($cacheData);
$return = [];
foreach (array_keys($data) as $key) {
$return[$key] = $success;
}
return $return;
}
/**
* Read a key from the cache
*
* @param string $key Identifier for the data
* @return mixed The cached data, or false if the data doesn't exist, has
* expired, or if there was an error fetching it.
*/
public function read($key)
{
$key = $this->_key($key);
return $this->_Memcached->get($key);
}
/**
* Read many keys from the cache at once
*
* @param array $keys An array of identifiers for the data
* @return array An array containing, for each of the given $keys, the cached data or
* false if cached data could not be retrieved.
*/
public function readMany($keys)
{
$cacheKeys = [];
foreach ($keys as $key) {
$cacheKeys[] = $this->_key($key);
}
$values = $this->_Memcached->getMulti($cacheKeys);
$return = [];
foreach ($keys as &$key) {
$return[$key] = array_key_exists($this->_key($key), $values) ?
$values[$this->_key($key)] : false;
}
return $return;
}
/**
* Increments the value of an integer cached key
*
* @param string $key Identifier for the data
* @param int $offset How much to increment
* @return int|false New incremented value, false otherwise
*/
public function increment($key, $offset = 1)
{
$key = $this->_key($key);
return $this->_Memcached->increment($key, $offset);
}
/**
* Decrements the value of an integer cached key
*
* @param string $key Identifier for the data
* @param int $offset How much to subtract
* @return int|false New decremented value, false otherwise
*/
public function decrement($key, $offset = 1)
{
$key = $this->_key($key);
return $this->_Memcached->decrement($key, $offset);
}
/**
* Delete a key from the cache
*
* @param string $key Identifier for the data
* @return bool True if the value was successfully deleted, false if it didn't
* exist or couldn't be removed.
*/
public function delete($key)
{
$key = $this->_key($key);
return $this->_Memcached->delete($key);
}
/**
* Delete many keys from the cache at once
*
* @param array $keys An array of identifiers for the data
* @return array of boolean values that are true if the key was successfully
* deleted, false if it didn't exist or couldn't be removed.
*/
public function deleteMany($keys)
{
$cacheKeys = [];
foreach ($keys as $key) {
$cacheKeys[] = $this->_key($key);
}
$success = $this->_Memcached->deleteMulti($cacheKeys);
$return = [];
foreach ($keys as $key) {
$return[$key] = $success;
}
return $return;
}
/**
* Delete all keys from the cache
*
* @param bool $check If true will check expiration, otherwise delete all.
* @return bool True if the cache was successfully cleared, false otherwise
*/
public function clear($check)
{
if ($check) {
return true;
}
$keys = $this->_Memcached->getAllKeys();
if ($keys === false) {
return false;
}
foreach ($keys as $key) {
if (strpos($key, $this->_config['prefix']) === 0) {
$this->_Memcached->delete($key);
}
}
return true;
}
/**
* Add a key to the cache if it does not already exist.
*
* @param string $key Identifier for the data.
* @param mixed $value Data to be cached.
* @return bool True if the data was successfully cached, false on failure.
*/
public function add($key, $value)
{
$duration = $this->_config['duration'];
if ($duration > 30 * DAY) {
$duration = 0;
}
$key = $this->_key($key);
return $this->_Memcached->add($key, $value, $duration);
}
/**
* Returns the `group value` for each of the configured groups
* If the group initial value was not found, then it initializes
* the group accordingly.
*
* @return string[]
*/
public function groups()
{
if (empty($this->_compiledGroupNames)) {
foreach ($this->_config['groups'] as $group) {
$this->_compiledGroupNames[] = $this->_config['prefix'] . $group;
}
}
$groups = $this->_Memcached->getMulti($this->_compiledGroupNames) ?: [];
if (count($groups) !== count($this->_config['groups'])) {
foreach ($this->_compiledGroupNames as $group) {
if (!isset($groups[$group])) {
$this->_Memcached->set($group, 1, 0);
$groups[$group] = 1;
}
}
ksort($groups);
}
$result = [];
$groups = array_values($groups);
foreach ($this->_config['groups'] as $i => $group) {
$result[] = $group . $groups[$i];
}
return $result;
}
/**
* Increments the group value to simulate deletion of all keys under a group
* old values will remain in storage until they expire.
*
* @param string $group name of the group to be cleared
* @return bool success
*/
public function clearGroup($group)
{
return (bool)$this->_Memcached->increment($this->_config['prefix'] . $group);
}
}
+112
View File
@@ -0,0 +1,112 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Cache\Engine;
use Cake\Cache\CacheEngine;
/**
* Null cache engine, all operations appear to work, but do nothing.
*
* This is used internally for when Cache::disable() has been called.
*/
class NullEngine extends CacheEngine
{
/**
* {@inheritDoc}
*/
public function init(array $config = [])
{
return true;
}
/**
* {@inheritDoc}
*/
public function gc($expires = null)
{
}
/**
* {@inheritDoc}
*/
public function write($key, $value)
{
return true;
}
/**
* {@inheritDoc}
*/
public function read($key)
{
return false;
}
/**
* {@inheritDoc}
*/
public function readMany($keys)
{
return [];
}
/**
* {@inheritDoc}
*/
public function increment($key, $offset = 1)
{
return true;
}
/**
* {@inheritDoc}
*/
public function decrement($key, $offset = 1)
{
return true;
}
/**
* {@inheritDoc}
*/
public function delete($key)
{
return true;
}
/**
* {@inheritDoc}
*/
public function deleteMany($keys)
{
return [];
}
/**
* {@inheritDoc}
*/
public function clear($check)
{
return false;
}
/**
* {@inheritDoc}
*/
public function clearGroup($group)
{
return false;
}
}
+321
View File
@@ -0,0 +1,321 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Cache\Engine;
use Cake\Cache\CacheEngine;
use Redis;
use RedisException;
/**
* Redis storage engine for cache.
*/
class RedisEngine extends CacheEngine
{
/**
* Redis wrapper.
*
* @var \Redis
*/
protected $_Redis;
/**
* The default config used unless overridden by runtime configuration
*
* - `database` database number to use for connection.
* - `duration` Specify how long items in this cache configuration last.
* - `groups` List of groups or 'tags' associated to every key stored in this config.
* handy for deleting a complete group from cache.
* - `password` Redis server password.
* - `persistent` Connect to the Redis server with a persistent connection
* - `port` port number to the Redis server.
* - `prefix` Prefix appended to all entries. Good for when you need to share a keyspace
* with either another cache config or another application.
* - `probability` Probability of hitting a cache gc cleanup. Setting to 0 will disable
* cache::gc from ever being called automatically.
* - `server` URL or ip to the Redis server host.
* - `timeout` timeout in seconds (float).
* - `unix_socket` Path to the unix socket file (default: false)
*
* @var array
*/
protected $_defaultConfig = [
'database' => 0,
'duration' => 3600,
'groups' => [],
'password' => false,
'persistent' => true,
'port' => 6379,
'prefix' => 'cake_',
'probability' => 100,
'host' => null,
'server' => '127.0.0.1',
'timeout' => 0,
'unix_socket' => false,
];
/**
* Initialize the Cache Engine
*
* Called automatically by the cache frontend
*
* @param array $config array of setting for the engine
* @return bool True if the engine has been successfully initialized, false if not
*/
public function init(array $config = [])
{
if (!extension_loaded('redis')) {
return false;
}
if (!empty($config['host'])) {
$config['server'] = $config['host'];
}
parent::init($config);
return $this->_connect();
}
/**
* Connects to a Redis server
*
* @return bool True if Redis server was connected
*/
protected function _connect()
{
try {
$this->_Redis = new Redis();
if (!empty($this->_config['unix_socket'])) {
$return = $this->_Redis->connect($this->_config['unix_socket']);
} elseif (empty($this->_config['persistent'])) {
$return = $this->_Redis->connect($this->_config['server'], $this->_config['port'], $this->_config['timeout']);
} else {
$persistentId = $this->_config['port'] . $this->_config['timeout'] . $this->_config['database'];
$return = $this->_Redis->pconnect($this->_config['server'], $this->_config['port'], $this->_config['timeout'], $persistentId);
}
} catch (RedisException $e) {
return false;
}
if ($return && $this->_config['password']) {
$return = $this->_Redis->auth($this->_config['password']);
}
if ($return) {
$return = $this->_Redis->select($this->_config['database']);
}
return $return;
}
/**
* Write data for key into cache.
*
* @param string $key Identifier for the data
* @param mixed $value Data to be cached
* @return bool True if the data was successfully cached, false on failure
*/
public function write($key, $value)
{
$key = $this->_key($key);
if (!is_int($value)) {
$value = serialize($value);
}
$duration = $this->_config['duration'];
if ($duration === 0) {
return $this->_Redis->set($key, $value);
}
return $this->_Redis->setEx($key, $duration, $value);
}
/**
* Read a key from the cache
*
* @param string $key Identifier for the data
* @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it
*/
public function read($key)
{
$key = $this->_key($key);
$value = $this->_Redis->get($key);
if (preg_match('/^[-]?\d+$/', $value)) {
return (int)$value;
}
if ($value !== false && is_string($value)) {
return unserialize($value);
}
return $value;
}
/**
* Increments the value of an integer cached key & update the expiry time
*
* @param string $key Identifier for the data
* @param int $offset How much to increment
* @return int|false New incremented value, false otherwise
*/
public function increment($key, $offset = 1)
{
$duration = $this->_config['duration'];
$key = $this->_key($key);
$value = (int)$this->_Redis->incrBy($key, $offset);
if ($duration > 0) {
$this->_Redis->expire($key, $duration);
}
return $value;
}
/**
* Decrements the value of an integer cached key & update the expiry time
*
* @param string $key Identifier for the data
* @param int $offset How much to subtract
* @return int|false New decremented value, false otherwise
*/
public function decrement($key, $offset = 1)
{
$duration = $this->_config['duration'];
$key = $this->_key($key);
$value = (int)$this->_Redis->decrBy($key, $offset);
if ($duration > 0) {
$this->_Redis->expire($key, $duration);
}
return $value;
}
/**
* Delete a key from the cache
*
* @param string $key Identifier for the data
* @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed
*/
public function delete($key)
{
$key = $this->_key($key);
return $this->_Redis->del($key) > 0;
}
/**
* Delete all keys from the cache
*
* @param bool $check If true will check expiration, otherwise delete all.
* @return bool True if the cache was successfully cleared, false otherwise
*/
public function clear($check)
{
if ($check) {
return true;
}
$this->_Redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
$isAllDeleted = true;
$iterator = null;
$pattern = $this->_config['prefix'] . '*';
while (true) {
$keys = $this->_Redis->scan($iterator, $pattern);
if ($keys === false) {
break;
}
foreach ($keys as $key) {
$isDeleted = ($this->_Redis->del($key) > 0);
$isAllDeleted = $isAllDeleted && $isDeleted;
}
}
return $isAllDeleted;
}
/**
* Write data for key into cache if it doesn't exist already.
* If it already exists, it fails and returns false.
*
* @param string $key Identifier for the data.
* @param mixed $value Data to be cached.
* @return bool True if the data was successfully cached, false on failure.
* @link https://github.com/phpredis/phpredis#set
*/
public function add($key, $value)
{
$duration = $this->_config['duration'];
$key = $this->_key($key);
if (!is_int($value)) {
$value = serialize($value);
}
if ($this->_Redis->set($key, $value, ['nx', 'ex' => $duration])) {
return true;
}
return false;
}
/**
* Returns the `group value` for each of the configured groups
* If the group initial value was not found, then it initializes
* the group accordingly.
*
* @return string[]
*/
public function groups()
{
$result = [];
foreach ($this->_config['groups'] as $group) {
$value = $this->_Redis->get($this->_config['prefix'] . $group);
if (!$value) {
$value = 1;
$this->_Redis->set($this->_config['prefix'] . $group, $value);
}
$result[] = $group . $value;
}
return $result;
}
/**
* Increments the group value to simulate deletion of all keys under a group
* old values will remain in storage until they expire.
*
* @param string $group name of the group to be cleared
* @return bool success
*/
public function clearGroup($group)
{
return (bool)$this->_Redis->incr($this->_config['prefix'] . $group);
}
/**
* Disconnects from the redis server
*/
public function __destruct()
{
if (empty($this->_config['persistent']) && $this->_Redis instanceof Redis) {
$this->_Redis->close();
}
}
}
@@ -0,0 +1,197 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Cache\Engine;
use Cake\Cache\CacheEngine;
/**
* Wincache storage engine for cache
*
* Supports wincache 1.1.0 and higher.
*/
class WincacheEngine extends CacheEngine
{
/**
* Contains the compiled group names
* (prefixed with the global configuration prefix)
*
* @var array
*/
protected $_compiledGroupNames = [];
/**
* Initialize the Cache Engine
*
* Called automatically by the cache frontend
*
* @param array $config array of setting for the engine
* @return bool True if the engine has been successfully initialized, false if not
*/
public function init(array $config = [])
{
if (!extension_loaded('wincache')) {
return false;
}
parent::init($config);
return true;
}
/**
* Write data for key into cache
*
* @param string $key Identifier for the data
* @param mixed $value Data to be cached
* @return bool True if the data was successfully cached, false on failure
*/
public function write($key, $value)
{
$key = $this->_key($key);
$duration = $this->_config['duration'];
return wincache_ucache_set($key, $value, $duration);
}
/**
* Read a key from the cache
*
* @param string $key Identifier for the data
* @return mixed The cached data, or false if the data doesn't exist,
* has expired, or if there was an error fetching it
*/
public function read($key)
{
$key = $this->_key($key);
return wincache_ucache_get($key);
}
/**
* Increments the value of an integer cached key
*
* @param string $key Identifier for the data
* @param int $offset How much to increment
* @return int|false New incremented value, false otherwise
*/
public function increment($key, $offset = 1)
{
$key = $this->_key($key);
return wincache_ucache_inc($key, $offset);
}
/**
* Decrements the value of an integer cached key
*
* @param string $key Identifier for the data
* @param int $offset How much to subtract
* @return int|false New decremented value, false otherwise
*/
public function decrement($key, $offset = 1)
{
$key = $this->_key($key);
return wincache_ucache_dec($key, $offset);
}
/**
* Delete a key from the cache
*
* @param string $key Identifier for the data
* @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed
*/
public function delete($key)
{
$key = $this->_key($key);
return wincache_ucache_delete($key);
}
/**
* Delete all keys from the cache. This will clear every
* item in the cache matching the cache config prefix.
*
* @param bool $check If true, nothing will be cleared, as entries will
* naturally expire in wincache..
* @return bool True Returns true.
*/
public function clear($check)
{
if ($check) {
return true;
}
$info = wincache_ucache_info();
$cacheKeys = $info['ucache_entries'];
unset($info);
foreach ($cacheKeys as $key) {
if (strpos($key['key_name'], $this->_config['prefix']) === 0) {
wincache_ucache_delete($key['key_name']);
}
}
return true;
}
/**
* Returns the `group value` for each of the configured groups
* If the group initial value was not found, then it initializes
* the group accordingly.
*
* @return string[]
*/
public function groups()
{
if (empty($this->_compiledGroupNames)) {
foreach ($this->_config['groups'] as $group) {
$this->_compiledGroupNames[] = $this->_config['prefix'] . $group;
}
}
$groups = wincache_ucache_get($this->_compiledGroupNames);
if (count($groups) !== count($this->_config['groups'])) {
foreach ($this->_compiledGroupNames as $group) {
if (!isset($groups[$group])) {
wincache_ucache_set($group, 1);
$groups[$group] = 1;
}
}
ksort($groups);
}
$result = [];
$groups = array_values($groups);
foreach ($this->_config['groups'] as $i => $group) {
$result[] = $group . $groups[$i];
}
return $result;
}
/**
* Increments the group value to simulate deletion of all keys under a group
* old values will remain in storage until they expire.
*
* @param string $group The group to clear.
* @return bool success
*/
public function clearGroup($group)
{
$success = false;
wincache_ucache_inc($this->_config['prefix'] . $group, 1, $success);
return $success;
}
}
+255
View File
@@ -0,0 +1,255 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 1.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Cache\Engine;
use Cake\Cache\CacheEngine;
/**
* Xcache storage engine for cache
*
* @link http://trac.lighttpd.net/xcache/ Xcache
* @deprecated 3.6.0 Xcache engine has been deprecated and will be removed in 4.0.0.
*/
class XcacheEngine extends CacheEngine
{
/**
* The default config used unless overridden by runtime configuration
*
* - `duration` Specify how long items in this cache configuration last.
* - `groups` List of groups or 'tags' associated to every key stored in this config.
* handy for deleting a complete group from cache.
* - `prefix` Prefix appended to all entries. Good for when you need to share a keyspace
* with either another cache config or another application.
* - `probability` Probability of hitting a cache gc cleanup. Setting to 0 will disable
* cache::gc from ever being called automatically.
* - `PHP_AUTH_USER` xcache.admin.user
* - `PHP_AUTH_PW` xcache.admin.password
*
* @var array
*/
protected $_defaultConfig = [
'duration' => 3600,
'groups' => [],
'prefix' => null,
'probability' => 100,
'PHP_AUTH_USER' => 'user',
'PHP_AUTH_PW' => 'password',
];
/**
* Initialize the Cache Engine
*
* Called automatically by the cache frontend
*
* @param array $config array of setting for the engine
* @return bool True if the engine has been successfully initialized, false if not
*/
public function init(array $config = [])
{
if (!extension_loaded('xcache')) {
return false;
}
parent::init($config);
return true;
}
/**
* Write data for key into cache
*
* @param string $key Identifier for the data
* @param mixed $value Data to be cached
* @return bool True if the data was successfully cached, false on failure
*/
public function write($key, $value)
{
$key = $this->_key($key);
if (!is_numeric($value)) {
$value = serialize($value);
}
$duration = $this->_config['duration'];
$expires = time() + $duration;
xcache_set($key . '_expires', $expires, $duration);
return xcache_set($key, $value, $duration);
}
/**
* Read a key from the cache
*
* @param string $key Identifier for the data
* @return mixed The cached data, or false if the data doesn't exist,
* has expired, or if there was an error fetching it
*/
public function read($key)
{
$key = $this->_key($key);
if (xcache_isset($key)) {
$time = time();
$cachetime = (int)xcache_get($key . '_expires');
if ($cachetime < $time || ($time + $this->_config['duration']) < $cachetime) {
return false;
}
$value = xcache_get($key);
if (is_string($value) && !is_numeric($value)) {
$value = unserialize($value);
}
return $value;
}
return false;
}
/**
* Increments the value of an integer cached key
* If the cache key is not an integer it will be treated as 0
*
* @param string $key Identifier for the data
* @param int $offset How much to increment
* @return int|false New incremented value, false otherwise
*/
public function increment($key, $offset = 1)
{
$key = $this->_key($key);
return xcache_inc($key, $offset);
}
/**
* Decrements the value of an integer cached key.
* If the cache key is not an integer it will be treated as 0
*
* @param string $key Identifier for the data
* @param int $offset How much to subtract
* @return int|false New decremented value, false otherwise
*/
public function decrement($key, $offset = 1)
{
$key = $this->_key($key);
return xcache_dec($key, $offset);
}
/**
* Delete a key from the cache
*
* @param string $key Identifier for the data
* @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed
*/
public function delete($key)
{
$key = $this->_key($key);
return xcache_unset($key);
}
/**
* Delete all keys from the cache
*
* @param bool $check If true no deletes will occur and instead CakePHP will rely
* on key TTL values.
* Unused for Xcache engine.
* @return bool True if the cache was successfully cleared, false otherwise
*/
public function clear($check)
{
$this->_auth();
$max = xcache_count(XC_TYPE_VAR);
for ($i = 0; $i < $max; $i++) {
xcache_clear_cache(XC_TYPE_VAR, $i);
}
$this->_auth(true);
return true;
}
/**
* Returns the `group value` for each of the configured groups
* If the group initial value was not found, then it initializes
* the group accordingly.
*
* @return string[]
*/
public function groups()
{
$result = [];
foreach ($this->_config['groups'] as $group) {
$value = xcache_get($this->_config['prefix'] . $group);
if (!$value) {
$value = 1;
xcache_set($this->_config['prefix'] . $group, $value, 0);
}
$result[] = $group . $value;
}
return $result;
}
/**
* Increments the group value to simulate deletion of all keys under a group
* old values will remain in storage until they expire.
*
* @param string $group The group to clear.
* @return bool success
*/
public function clearGroup($group)
{
return (bool)xcache_inc($this->_config['prefix'] . $group, 1);
}
/**
* Populates and reverses $_SERVER authentication values
* Makes necessary changes (and reverting them back) in $_SERVER
*
* This has to be done because xcache_clear_cache() needs to pass Basic Http Auth
* (see xcache.admin configuration config)
*
* @param bool $reverse Revert changes
* @return void
*/
protected function _auth($reverse = false)
{
static $backup = [];
$keys = ['PHP_AUTH_USER' => 'user', 'PHP_AUTH_PW' => 'password'];
foreach ($keys as $key => $value) {
if ($reverse) {
if (isset($backup[$key])) {
$_SERVER[$key] = $backup[$key];
unset($backup[$key]);
} else {
unset($_SERVER[$key]);
}
} else {
$value = env($key);
if (!empty($value)) {
$backup[$key] = $value;
}
if (!empty($this->_config[$value])) {
$_SERVER[$key] = $this->_config[$value];
} elseif (!empty($this->_config[$key])) {
$_SERVER[$key] = $this->_config[$key];
} else {
$_SERVER[$key] = $value;
}
}
}
}
}
@@ -0,0 +1,25 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.7.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Cache;
use Cake\Core\Exception\Exception;
use Psr\SimpleCache\InvalidArgumentException as InvalidArgumentInterface;
/**
* Exception raised when cache keys are invalid.
*/
class InvalidArgumentException extends Exception implements InvalidArgumentInterface
{
}
+57
View File
@@ -0,0 +1,57 @@
# CakePHP Caching Library
The Cache library provides a `Cache` service locator for interfacing with multiple caching backends using
a simple to use interface.
The caching backends supported are:
* Files
* APC
* Memcached
* Redis
* Wincache
* Xcache
## Usage
Caching engines need to be configured with the `Cache::config()` method.
```php
use Cake\Cache\Cache;
// Using a short name
Cache::config('default', [
'className' => 'File',
'duration' => '+1 hours',
'path' => sys_get_tmp_dir(),
'prefix' => 'my_app_'
]);
// Using a fully namespaced name.
Cache::config('long', [
'className' => 'Cake\Cache\Engine\ApcuEngine',
'duration' => '+1 week',
'prefix' => 'my_app_'
]);
// Using a constructed object.
$object = new FileEngine($config);
Cache::config('other', $object);
```
You can now read a write from the cache:
```php
$data = Cache::remember('my_cache_key', function () {
return Service::expensiveCall();
});
```
The code above will try to look for data stored in cache under the `my_cache_key`, if not found
the callback will be executed and the returned data will be cached for future calls.
## Documentation
Please make sure you check the [official documentation](https://book.cakephp.org/3/en/core-libraries/caching.html)
+282
View File
@@ -0,0 +1,282 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.7.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Cache;
use Cake\Cache\CacheEngineInterface;
use Psr\SimpleCache\CacheInterface;
/**
* Wrapper for Cake engines that allow them to support
* the PSR16 Simple Cache Interface
*
* @since 3.7.0
* @link https://www.php-fig.org/psr/psr-16/
*/
class SimpleCacheEngine implements CacheInterface, CacheEngineInterface
{
/**
* The wrapped cache engine object.
*
* @var \Cake\Cache\CacheEngine
*/
protected $innerEngine;
/**
* Constructor
*
* @param \Cake\Cache\CacheEngine $innerEngine The decorated engine.
*/
public function __construct(CacheEngine $innerEngine)
{
$this->innerEngine = $innerEngine;
}
/**
* Ensure the validity of the given cache key.
*
* @param string $key Key to check.
* @return void
* @throws \Cake\Cache\InvalidArgumentException When the key is not valid.
*/
protected function ensureValidKey($key)
{
if (!is_string($key) || strlen($key) === 0) {
throw new InvalidArgumentException('A cache key must be a non-empty string.');
}
}
/**
* Ensure the validity of the given cache keys.
*
* @param mixed $keys The keys to check.
* @return void
* @throws \Cake\Cache\InvalidArgumentException When the keys are not valid.
*/
protected function ensureValidKeys($keys)
{
if (!is_array($keys) && !($keys instanceof \Traversable)) {
throw new InvalidArgumentException('A cache key set must be either an array or a Traversable.');
}
foreach ($keys as $key) {
$this->ensureValidKey($key);
}
}
/**
* Fetches the value for a given key from the cache.
*
* @param string $key The unique key of this item in the cache.
* @param mixed $default Default value to return if the key does not exist.
* @return mixed The value of the item from the cache, or $default in case of cache miss.
* @throws \Cake\Cache\InvalidArgumentException If the $key string is not a legal value.
*/
public function get($key, $default = null)
{
$this->ensureValidKey($key);
$result = $this->innerEngine->read($key);
if ($result === false) {
return $default;
}
return $result;
}
/**
* Persists data in the cache, uniquely referenced by the given key with an optional expiration TTL time.
*
* @param string $key The key of the item to store.
* @param mixed $value The value of the item to store, must be serializable.
* @param \DateInterval|int|null $ttl Optional. The TTL value of this item. If no value is sent and
* the driver supports TTL then the library may set a default value
* for it or let the driver take care of that.
* @return bool True on success and false on failure.
* @throws \Cake\Cache\InvalidArgumentException
* MUST be thrown if the $key string is not a legal value.
*/
public function set($key, $value, $ttl = null)
{
$this->ensureValidKey($key);
if ($ttl !== null) {
$restore = $this->innerEngine->getConfig('duration');
$this->innerEngine->setConfig('duration', $ttl);
}
try {
$result = $this->innerEngine->write($key, $value);
return (bool)$result;
} finally {
if (isset($restore)) {
$this->innerEngine->setConfig('duration', $restore);
}
}
}
/**
* Delete an item from the cache by its unique key.
*
* @param string $key The unique cache key of the item to delete.
* @return bool True if the item was successfully removed. False if there was an error.
* @throws \Cake\Cache\InvalidArgumentException If the $key string is not a legal value.
*/
public function delete($key)
{
$this->ensureValidKey($key);
return $this->innerEngine->delete($key);
}
/**
* Wipes clean the entire cache's keys.
*
* @return bool True on success and false on failure.
*/
public function clear()
{
return $this->innerEngine->clear(false);
}
/**
* Obtains multiple cache items by their unique keys.
*
* @param iterable $keys A list of keys that can obtained in a single operation.
* @param mixed $default Default value to return for keys that do not exist.
* @return iterable A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value.
* @throws \Cake\Cache\InvalidArgumentException If $keys is neither an array nor a Traversable,
* or if any of the $keys are not a legal value.
*/
public function getMultiple($keys, $default = null)
{
$this->ensureValidKeys($keys);
$results = $this->innerEngine->readMany($keys);
foreach ($results as $key => $value) {
if ($value === false) {
$results[$key] = $default;
}
}
return $results;
}
/**
* Persists a set of key => value pairs in the cache, with an optional TTL.
*
* @param iterable $values A list of key => value pairs for a multiple-set operation.
* @param \DateInterval|int|null $ttl Optional. The TTL value of this item. If no value is sent and
* the driver supports TTL then the library may set a default value
* for it or let the driver take care of that.
* @return bool True on success and false on failure.
* @throws \Cake\Cache\InvalidArgumentException If $values is neither an array nor a Traversable,
* or if any of the $values are not a legal value.
*/
public function setMultiple($values, $ttl = null)
{
$this->ensureValidKeys(array_keys($values));
if ($ttl !== null) {
$restore = $this->innerEngine->getConfig('duration');
$this->innerEngine->setConfig('duration', $ttl);
}
try {
$result = $this->innerEngine->writeMany($values);
foreach ($result as $key => $success) {
if ($success === false) {
return false;
}
}
return true;
} finally {
if (isset($restore)) {
$this->innerEngine->setConfig('duration', $restore);
}
}
return false;
}
/**
* Deletes multiple cache items in a single operation.
*
* @param iterable $keys A list of string-based keys to be deleted.
* @return bool True if the items were successfully removed. False if there was an error.
* @throws \Cake\Cache\InvalidArgumentException If $keys is neither an array nor a Traversable,
* or if any of the $keys are not a legal value.
*/
public function deleteMultiple($keys)
{
$this->ensureValidKeys($keys);
$result = $this->innerEngine->deleteMany($keys);
foreach ($result as $key => $success) {
if ($success === false) {
return false;
}
}
return true;
}
/**
* Determines whether an item is present in the cache.
*
* NOTE: It is recommended that has() is only to be used for cache warming type purposes
* and not to be used within your live applications operations for get/set, as this method
* is subject to a race condition where your has() will return true and immediately after,
* another script can remove it making the state of your app out of date.
*
* @param string $key The cache item key.
* @return bool
* @throws \Cake\Cache\InvalidArgumentException If the $key string is not a legal value.
*/
public function has($key)
{
return $this->get($key) !== null;
}
/**
* {@inheritDoc}
*/
public function add($key, $value)
{
return $this->innerEngine->add($key, $value);
}
/**
* {@inheritDoc}
*/
public function increment($key, $offset = 1)
{
return $this->innerEngine->increment($key, $offset);
}
/**
* {@inheritDoc}
*/
public function decrement($key, $offset = 1)
{
return $this->innerEngine->decrement($key, $offset);
}
/**
* {@inheritDoc}
*/
public function clearGroup($group)
{
return $this->innerEngine->clearGroup($group);
}
}
+34
View File
@@ -0,0 +1,34 @@
{
"name": "cakephp/cache",
"description": "Easy to use Caching library with support for multiple caching backends",
"type": "library",
"keywords": [
"cakephp",
"caching",
"cache"
],
"homepage": "https://cakephp.org",
"license": "MIT",
"authors": [
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/cache/graphs/contributors"
}
],
"support": {
"issues": "https://github.com/cakephp/cakephp/issues",
"forum": "https://stackoverflow.com/tags/cakephp",
"irc": "irc://irc.freenode.org/cakephp",
"source": "https://github.com/cakephp/cache"
},
"require": {
"php": ">=5.6.0",
"psr/simple-cache": "^1.0.0",
"cakephp/core": "^3.6.0"
},
"autoload": {
"psr-4": {
"Cake\\Cache\\": "."
}
}
}
+111
View File
@@ -0,0 +1,111 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Collection;
use ArrayIterator;
use InvalidArgumentException;
use IteratorIterator;
use Serializable;
use Traversable;
/**
* A collection is an immutable list of elements with a handful of functions to
* iterate, group, transform and extract information from it.
*/
class Collection extends IteratorIterator implements CollectionInterface, Serializable
{
use CollectionTrait;
/**
* Constructor. You can provide an array or any traversable object
*
* @param array|\Traversable $items Items.
* @throws \InvalidArgumentException If passed incorrect type for items.
*/
public function __construct($items)
{
if (is_array($items)) {
$items = new ArrayIterator($items);
}
if (!($items instanceof Traversable)) {
$msg = 'Only an array or \Traversable is allowed for Collection';
throw new InvalidArgumentException($msg);
}
parent::__construct($items);
}
/**
* Returns a string representation of this object that can be used
* to reconstruct it
*
* @return string
*/
public function serialize()
{
return serialize($this->buffered());
}
/**
* Unserializes the passed string and rebuilds the Collection instance
*
* @param string $collection The serialized collection
* @return void
*/
public function unserialize($collection)
{
$this->__construct(unserialize($collection));
}
/**
* {@inheritDoc}
*
* @return int
*/
public function count()
{
$traversable = $this->optimizeUnwrap();
if (is_array($traversable)) {
return count($traversable);
}
return iterator_count($traversable);
}
/**
* {@inheritDoc}
*
* @return int
*/
public function countKeys()
{
return count($this->toArray());
}
/**
* Returns an array that can be used to describe the internal state of this
* object.
*
* @return array
*/
public function __debugInfo()
{
return [
'count' => $this->count(),
];
}
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+148
View File
@@ -0,0 +1,148 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Collection;
use Traversable;
/**
* Provides utility protected methods for extracting a property or column
* from an array or object.
*/
trait ExtractTrait
{
/**
* Returns a callable that can be used to extract a property or column from
* an array or object based on a dot separated path.
*
* @param string|callable $callback A dot separated path of column to follow
* so that the final one can be returned or a callable that will take care
* of doing that.
* @return callable
*/
protected function _propertyExtractor($callback)
{
if (!is_string($callback)) {
return $callback;
}
$path = explode('.', $callback);
if (strpos($callback, '{*}') !== false) {
return function ($element) use ($path) {
return $this->_extract($element, $path);
};
}
return function ($element) use ($path) {
return $this->_simpleExtract($element, $path);
};
}
/**
* Returns a column from $data that can be extracted
* by iterating over the column names contained in $path.
* It will return arrays for elements in represented with `{*}`
*
* @param array|\ArrayAccess $data Data.
* @param string[] $path Path to extract from.
* @return mixed
*/
protected function _extract($data, $path)
{
$value = null;
$collectionTransform = false;
foreach ($path as $i => $column) {
if ($column === '{*}') {
$collectionTransform = true;
continue;
}
if (
$collectionTransform &&
!($data instanceof Traversable || is_array($data))
) {
return null;
}
if ($collectionTransform) {
$rest = implode('.', array_slice($path, $i));
return (new Collection($data))->extract($rest);
}
if (!isset($data[$column])) {
return null;
}
$value = $data[$column];
$data = $value;
}
return $value;
}
/**
* Returns a column from $data that can be extracted
* by iterating over the column names contained in $path
*
* @param array|\ArrayAccess $data Data.
* @param string[] $path Path to extract from.
* @return mixed
*/
protected function _simpleExtract($data, $path)
{
$value = null;
foreach ($path as $column) {
if (!isset($data[$column])) {
return null;
}
$value = $data[$column];
$data = $value;
}
return $value;
}
/**
* Returns a callable that receives a value and will return whether or not
* it matches certain condition.
*
* @param array $conditions A key-value list of conditions to match where the
* key is the property path to get from the current item and the value is the
* value to be compared the item with.
* @return \Closure
*/
protected function _createMatcherFilter(array $conditions)
{
$matchers = [];
foreach ($conditions as $property => $value) {
$extractor = $this->_propertyExtractor($property);
$matchers[] = function ($v) use ($extractor, $value) {
return $extractor($v) == $value;
};
}
return function ($value) use ($matchers) {
foreach ($matchers as $match) {
if (!$match($value)) {
return false;
}
}
return true;
};
}
}
@@ -0,0 +1,211 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Collection\Iterator;
use Cake\Collection\Collection;
use Countable;
use Serializable;
use SplDoublyLinkedList;
/**
* Creates an iterator from another iterator that will keep the results of the inner
* iterator in memory, so that results don't have to be re-calculated.
*/
class BufferedIterator extends Collection implements Countable, Serializable
{
/**
* The in-memory cache containing results from previous iterators
*
* @var \SplDoublyLinkedList
*/
protected $_buffer;
/**
* Points to the next record number that should be fetched
*
* @var int
*/
protected $_index = 0;
/**
* Last record fetched from the inner iterator
*
* @var mixed
*/
protected $_current;
/**
* Last key obtained from the inner iterator
*
* @var mixed
*/
protected $_key;
/**
* Whether or not the internal iterator's rewind method was already
* called
*
* @var bool
*/
protected $_started = false;
/**
* Whether or not the internal iterator has reached its end.
*
* @var bool
*/
protected $_finished = false;
/**
* Maintains an in-memory cache of the results yielded by the internal
* iterator.
*
* @param array|\Traversable $items The items to be filtered.
*/
public function __construct($items)
{
$this->_buffer = new SplDoublyLinkedList();
parent::__construct($items);
}
/**
* Returns the current key in the iterator
*
* @return mixed
*/
public function key()
{
return $this->_key;
}
/**
* Returns the current record in the iterator
*
* @return mixed
*/
public function current()
{
return $this->_current;
}
/**
* Rewinds the collection
*
* @return void
*/
public function rewind()
{
if ($this->_index === 0 && !$this->_started) {
$this->_started = true;
parent::rewind();
return;
}
$this->_index = 0;
}
/**
* Returns whether or not the iterator has more elements
*
* @return bool
*/
public function valid()
{
if ($this->_buffer->offsetExists($this->_index)) {
$current = $this->_buffer->offsetGet($this->_index);
$this->_current = $current['value'];
$this->_key = $current['key'];
return true;
}
$valid = parent::valid();
if ($valid) {
$this->_current = parent::current();
$this->_key = parent::key();
$this->_buffer->push([
'key' => $this->_key,
'value' => $this->_current,
]);
}
$this->_finished = !$valid;
return $valid;
}
/**
* Advances the iterator pointer to the next element
*
* @return void
*/
public function next()
{
$this->_index++;
if (!$this->_finished) {
parent::next();
}
}
/**
* Returns the number or items in this collection
*
* @return int
*/
public function count()
{
if (!$this->_started) {
$this->rewind();
}
while ($this->valid()) {
$this->next();
}
return $this->_buffer->count();
}
/**
* Returns a string representation of this object that can be used
* to reconstruct it
*
* @return string
*/
public function serialize()
{
if (!$this->_finished) {
$this->count();
}
return serialize($this->_buffer);
}
/**
* Unserializes the passed string and rebuilds the BufferedIterator instance
*
* @param string $buffer The serialized buffer iterator
* @return void
*/
public function unserialize($buffer)
{
$this->__construct([]);
$this->_buffer = unserialize($buffer);
$this->_started = true;
$this->_finished = true;
}
}
@@ -0,0 +1,107 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Collection\Iterator;
use ArrayIterator;
use Cake\Collection\Collection;
use Cake\Collection\CollectionInterface;
/**
* Creates an iterator from another iterator that extract the requested column
* or property based on a path
*/
class ExtractIterator extends Collection
{
/**
* A callable responsible for extracting a single value for each
* item in the collection.
*
* @var callable
*/
protected $_extractor;
/**
* Creates the iterator that will return the requested property for each value
* in the collection expressed in $path
*
* ### Example:
*
* Extract the user name for all comments in the array:
*
* ```
* $items = [
* ['comment' => ['body' => 'cool', 'user' => ['name' => 'Mark']],
* ['comment' => ['body' => 'very cool', 'user' => ['name' => 'Renan']]
* ];
* $extractor = new ExtractIterator($items, 'comment.user.name'');
* ```
*
* @param array|\Traversable $items The list of values to iterate
* @param string $path a dot separated string symbolizing the path to follow
* inside the hierarchy of each value so that the column can be extracted.
*/
public function __construct($items, $path)
{
$this->_extractor = $this->_propertyExtractor($path);
parent::__construct($items);
}
/**
* Returns the column value defined in $path or null if the path could not be
* followed
*
* @return mixed
*/
public function current()
{
$extractor = $this->_extractor;
return $extractor(parent::current());
}
/**
* {@inheritDoc}
*
* We perform here some strictness analysis so that the
* iterator logic is bypassed entirely.
*
* @return \Iterator
*/
public function unwrap()
{
$iterator = $this->getInnerIterator();
if ($iterator instanceof CollectionInterface) {
$iterator = $iterator->unwrap();
}
if (get_class($iterator) !== ArrayIterator::class) {
return $this;
}
// ArrayIterator can be traversed strictly.
// Let's do that for performance gains
$callback = $this->_extractor;
$res = [];
/** @var \ArrayObject $iterator */
foreach ($iterator->getArrayCopy() as $k => $v) {
$res[$k] = $callback($v);
}
return new ArrayIterator($res);
}
}
@@ -0,0 +1,95 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Collection\Iterator;
use ArrayIterator;
use Cake\Collection\Collection;
use Cake\Collection\CollectionInterface;
use CallbackFilterIterator;
use Iterator;
/**
* Creates a filtered iterator from another iterator. The filtering is done by
* passing a callback function to each of the elements and taking them out if
* it does not return true.
*/
class FilterIterator extends Collection
{
/**
* The callback used to filter the elements in this collection
*
* @var callable
*/
protected $_callback;
/**
* Creates a filtered iterator using the callback to determine which items are
* accepted or rejected.
*
* Each time the callback is executed it will receive the value of the element
* in the current iteration, the key of the element and the passed $items iterator
* as arguments, in that order.
*
* @param \Traversable|array $items The items to be filtered.
* @param callable $callback Callback.
*/
public function __construct($items, callable $callback)
{
if (!$items instanceof Iterator) {
$items = new Collection($items);
}
$this->_callback = $callback;
$wrapper = new CallbackFilterIterator($items, $callback);
parent::__construct($wrapper);
}
/**
* {@inheritDoc}
*
* We perform here some strictness analysis so that the
* iterator logic is bypassed entirely.
*
* @return \Iterator
*/
public function unwrap()
{
/** @var \IteratorIterator $filter */
$filter = $this->getInnerIterator();
$iterator = $filter->getInnerIterator();
if ($iterator instanceof CollectionInterface) {
$iterator = $iterator->unwrap();
}
if (get_class($iterator) !== ArrayIterator::class) {
return $filter;
}
// ArrayIterator can be traversed strictly.
// Let's do that for performance gains
$callback = $this->_callback;
$res = [];
foreach ($iterator as $k => $v) {
if ($callback($v, $k, $iterator)) {
$res[$k] = $v;
}
}
return new ArrayIterator($res);
}
}
@@ -0,0 +1,134 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Collection\Iterator;
use Cake\Collection\Collection;
/**
* This iterator will insert values into a property of each of the records returned.
* The values to be inserted come out of another traversal object. This is useful
* when you have two separate collections and want to merge them together by placing
* each of the values from one collection into a property inside the other collection.
*/
class InsertIterator extends Collection
{
/**
* The collection from which to extract the values to be inserted
*
* @var \Cake\Collection\Collection
*/
protected $_values;
/**
* Holds whether the values collection is still valid. (has more records)
*
* @var bool
*/
protected $_validValues = true;
/**
* An array containing each of the properties to be traversed to reach the
* point where the values should be inserted.
*
* @var array
*/
protected $_path;
/**
* The property name to which values will be assigned
*
* @var string
*/
protected $_target;
/**
* Constructs a new collection that will dynamically add properties to it out of
* the values found in $values.
*
* @param array|\Traversable $into The target collection to which the values will
* be inserted at the specified path.
* @param string $path A dot separated list of properties that need to be traversed
* to insert the value into the target collection.
* @param array|\Traversable $values The source collection from which the values will
* be inserted at the specified path.
*/
public function __construct($into, $path, $values)
{
parent::__construct($into);
if (!($values instanceof Collection)) {
$values = new Collection($values);
}
$path = explode('.', $path);
$target = array_pop($path);
$this->_path = $path;
$this->_target = $target;
$this->_values = $values;
}
/**
* Advances the cursor to the next record
*
* @return void
*/
public function next()
{
parent::next();
if ($this->_validValues) {
$this->_values->next();
}
$this->_validValues = $this->_values->valid();
}
/**
* Returns the current element in the target collection after inserting
* the value from the source collection into the specified path.
*
* @return mixed
*/
public function current()
{
$row = parent::current();
if (!$this->_validValues) {
return $row;
}
$pointer =& $row;
foreach ($this->_path as $step) {
if (!isset($pointer[$step])) {
return $row;
}
$pointer =& $pointer[$step];
}
$pointer[$this->_target] = $this->_values->current();
return $row;
}
/**
* Resets the collection pointer.
*
* @return void
*/
public function rewind()
{
parent::rewind();
$this->_values->rewind();
$this->_validValues = $this->_values->valid();
}
}
@@ -0,0 +1,193 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Collection\Iterator;
use ArrayIterator;
use IteratorAggregate;
use LogicException;
use Traversable;
/**
* Implements a simplistic version of the popular Map-Reduce algorithm. Acts
* like an iterator for the original passed data after each result has been
* processed, thus offering a transparent wrapper for results coming from any
* source.
*/
class MapReduce implements IteratorAggregate
{
/**
* Holds the shuffled results that were emitted from the map
* phase
*
* @var array
*/
protected $_intermediate = [];
/**
* Holds the results as emitted during the reduce phase
*
* @var array
*/
protected $_result = [];
/**
* Whether the Map-Reduce routine has been executed already on the data
*
* @var bool
*/
protected $_executed = false;
/**
* Holds the original data that needs to be processed
*
* @var \Traversable|null
*/
protected $_data;
/**
* A callable that will be executed for each record in the original data
*
* @var callable
*/
protected $_mapper;
/**
* A callable that will be executed for each intermediate record emitted during
* the Map phase
*
* @var callable|null
*/
protected $_reducer;
/**
* Count of elements emitted during the Reduce phase
*
* @var int
*/
protected $_counter = 0;
/**
* Constructor
*
* ### Example:
*
* Separate all unique odd and even numbers in an array
*
* ```
* $data = new \ArrayObject([1, 2, 3, 4, 5, 3]);
* $mapper = function ($value, $key, $mr) {
* $type = ($value % 2 === 0) ? 'even' : 'odd';
* $mr->emitIntermediate($value, $type);
* };
*
* $reducer = function ($numbers, $type, $mr) {
* $mr->emit(array_unique($numbers), $type);
* };
* $results = new MapReduce($data, $mapper, $reducer);
* ```
*
* Previous example will generate the following result:
*
* ```
* ['odd' => [1, 3, 5], 'even' => [2, 4]]
* ```
*
* @param \Traversable $data the original data to be processed
* @param callable $mapper the mapper callback. This function will receive 3 arguments.
* The first one is the current value, second the current results key and third is
* this class instance so you can call the result emitters.
* @param callable|null $reducer the reducer callback. This function will receive 3 arguments.
* The first one is the list of values inside a bucket, second one is the name
* of the bucket that was created during the mapping phase and third one is an
* instance of this class.
*/
public function __construct(Traversable $data, callable $mapper, callable $reducer = null)
{
$this->_data = $data;
$this->_mapper = $mapper;
$this->_reducer = $reducer;
}
/**
* Returns an iterator with the end result of running the Map and Reduce
* phases on the original data
*
* @return \ArrayIterator
*/
public function getIterator()
{
if (!$this->_executed) {
$this->_execute();
}
return new ArrayIterator($this->_result);
}
/**
* Appends a new record to the bucket labelled with $key, usually as a result
* of mapping a single record from the original data.
*
* @param mixed $val The record itself to store in the bucket
* @param string $bucket the name of the bucket where to put the record
* @return void
*/
public function emitIntermediate($val, $bucket)
{
$this->_intermediate[$bucket][] = $val;
}
/**
* Appends a new record to the final list of results and optionally assign a key
* for this record.
*
* @param mixed $val The value to be appended to the final list of results
* @param string|null $key and optional key to assign to the value
* @return void
*/
public function emit($val, $key = null)
{
$this->_result[$key === null ? $this->_counter : $key] = $val;
$this->_counter++;
}
/**
* Runs the actual Map-Reduce algorithm. This is iterate the original data
* and call the mapper function for each , then for each intermediate
* bucket created during the Map phase call the reduce function.
*
* @return void
* @throws \LogicException if emitIntermediate was called but no reducer function
* was provided
*/
protected function _execute()
{
$mapper = $this->_mapper;
foreach ($this->_data as $key => $val) {
$mapper($val, $key, $this);
}
$this->_data = null;
if (!empty($this->_intermediate) && empty($this->_reducer)) {
throw new LogicException('No reducer function was provided');
}
/** @var callable $reducer */
$reducer = $this->_reducer;
foreach ($this->_intermediate as $key => $list) {
$reducer($list, $key, $this);
}
$this->_intermediate = [];
$this->_executed = true;
}
}
@@ -0,0 +1,76 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Collection\Iterator;
use Cake\Collection\Collection;
use RecursiveIterator;
use Traversable;
/**
* A type of collection that is aware of nested items and exposes methods to
* check or retrieve them
*/
class NestIterator extends Collection implements RecursiveIterator
{
/**
* The name of the property that contains the nested items for each element
*
* @var string|callable
*/
protected $_nestKey;
/**
* Constructor
*
* @param array|\Traversable $items Collection items.
* @param string|callable $nestKey the property that contains the nested items
* If a callable is passed, it should return the childrens for the passed item
*/
public function __construct($items, $nestKey)
{
parent::__construct($items);
$this->_nestKey = $nestKey;
}
/**
* Returns a traversable containing the children for the current item
*
* @return \RecursiveIterator
*/
public function getChildren()
{
$property = $this->_propertyExtractor($this->_nestKey);
return new static($property($this->current()), $this->_nestKey);
}
/**
* Returns true if there is an array or a traversable object stored under the
* configured nestKey for the current item
*
* @return bool
*/
public function hasChildren()
{
$property = $this->_propertyExtractor($this->_nestKey);
$children = $property($this->current());
if (is_array($children)) {
return !empty($children);
}
return $children instanceof Traversable;
}
}
@@ -0,0 +1,46 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Collection\Iterator;
use Cake\Collection\Collection;
use RecursiveIterator;
/**
* An iterator that can be used as an argument for other iterators that require
* a RecursiveIterator but do not want children. This iterator will
* always behave as having no nested items.
*/
class NoChildrenIterator extends Collection implements RecursiveIterator
{
/**
* Returns false as there are no children iterators in this collection
*
* @return bool
*/
public function hasChildren()
{
return false;
}
/**
* Returns null as there are no children for this iteration level
*
* @return null
*/
public function getChildren()
{
return null;
}
}
@@ -0,0 +1,104 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Collection\Iterator;
use ArrayIterator;
use Cake\Collection\Collection;
use Cake\Collection\CollectionInterface;
/**
* Creates an iterator from another iterator that will modify each of the values
* by converting them using a callback function.
*/
class ReplaceIterator extends Collection
{
/**
* The callback function to be used to transform values
*
* @var callable
*/
protected $_callback;
/**
* A reference to the internal iterator this object is wrapping.
*
* @var \Iterator
*/
protected $_innerIterator;
/**
* Creates an iterator from another iterator that will modify each of the values
* by converting them using a callback function.
*
* Each time the callback is executed it will receive the value of the element
* in the current iteration, the key of the element and the passed $items iterator
* as arguments, in that order.
*
* @param array|\Traversable $items The items to be filtered.
* @param callable $callback Callback.
*/
public function __construct($items, callable $callback)
{
$this->_callback = $callback;
parent::__construct($items);
$this->_innerIterator = $this->getInnerIterator();
}
/**
* Returns the value returned by the callback after passing the current value in
* the iteration
*
* @return mixed
*/
public function current()
{
$callback = $this->_callback;
return $callback(parent::current(), $this->key(), $this->_innerIterator);
}
/**
* {@inheritDoc}
*
* We perform here some strictness analysis so that the
* iterator logic is bypassed entirely.
*
* @return \Iterator
*/
public function unwrap()
{
$iterator = $this->_innerIterator;
if ($iterator instanceof CollectionInterface) {
$iterator = $iterator->unwrap();
}
if (get_class($iterator) !== ArrayIterator::class) {
return $this;
}
// ArrayIterator can be traversed strictly.
// Let's do that for performance gains
$callback = $this->_callback;
$res = [];
foreach ($iterator as $k => $v) {
$res[$k] = $callback($v, $k, $iterator);
}
return new ArrayIterator($res);
}
}
@@ -0,0 +1,92 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Collection\Iterator;
use Cake\Collection\Collection;
use DateTimeInterface;
/**
* An iterator that will return the passed items in order. The order is given by
* the value returned in a callback function that maps each of the elements.
*
* ### Example:
*
* ```
* $items = [$user1, $user2, $user3];
* $sorted = new SortIterator($items, function ($user) {
* return $user->age;
* });
*
* // output all user name order by their age in descending order
* foreach ($sorted as $user) {
* echo $user->name;
* }
* ```
*
* This iterator does not preserve the keys passed in the original elements.
*/
class SortIterator extends Collection
{
/**
* Wraps this iterator around the passed items so when iterated they are returned
* in order.
*
* The callback will receive as first argument each of the elements in $items,
* the value returned in the callback will be used as the value for sorting such
* element. Please note that the callback function could be called more than once
* per element.
*
* @param array|\Traversable $items The values to sort
* @param callable|string $callback A function used to return the actual value to
* be compared. It can also be a string representing the path to use to fetch a
* column or property in each element
* @param int $dir either SORT_DESC or SORT_ASC
* @param int $type the type of comparison to perform, either SORT_STRING
* SORT_NUMERIC or SORT_NATURAL
*/
public function __construct($items, $callback, $dir = \SORT_DESC, $type = \SORT_NUMERIC)
{
if (!is_array($items)) {
$items = iterator_to_array((new Collection($items))->unwrap(), false);
}
$callback = $this->_propertyExtractor($callback);
$results = [];
foreach ($items as $key => $val) {
$val = $callback($val);
if ($val instanceof DateTimeInterface && $type === \SORT_NUMERIC) {
$val = $val->format('U');
}
$results[$key] = $val;
}
$dir === SORT_DESC ? arsort($results, $type) : asort($results, $type);
foreach (array_keys($results) as $key) {
$results[$key] = $items[$key];
}
parent::__construct($results);
}
/**
* {@inheritDoc}
*
* @return \Iterator
*/
public function unwrap()
{
return $this->getInnerIterator();
}
}
@@ -0,0 +1,118 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Collection\Iterator;
use ArrayIterator;
use Cake\Collection\Collection;
use Cake\Collection\CollectionInterface;
/**
* Creates an iterator from another iterator that will verify a condition on each
* step. If the condition evaluates to false, the iterator will not yield more
* results.
*
* @internal
* @see \Cake\Collection\Collection::stopWhen()
*/
class StoppableIterator extends Collection
{
/**
* The condition to evaluate for each item of the collection
*
* @var callable
*/
protected $_condition;
/**
* A reference to the internal iterator this object is wrapping.
*
* @var \Iterator
*/
protected $_innerIterator;
/**
* Creates an iterator that can be stopped based on a condition provided by a callback.
*
* Each time the condition callback is executed it will receive the value of the element
* in the current iteration, the key of the element and the passed $items iterator
* as arguments, in that order.
*
* @param array|\Traversable $items The list of values to iterate
* @param callable $condition A function that will be called for each item in
* the collection, if the result evaluates to false, no more items will be
* yielded from this iterator.
*/
public function __construct($items, callable $condition)
{
$this->_condition = $condition;
parent::__construct($items);
$this->_innerIterator = $this->getInnerIterator();
}
/**
* Evaluates the condition and returns its result, this controls
* whether or not more results will be yielded.
*
* @return bool
*/
public function valid()
{
if (!parent::valid()) {
return false;
}
$current = $this->current();
$key = $this->key();
$condition = $this->_condition;
return !$condition($current, $key, $this->_innerIterator);
}
/**
* {@inheritDoc}
*
* We perform here some strictness analysis so that the
* iterator logic is bypassed entirely.
*
* @return \Iterator
*/
public function unwrap()
{
$iterator = $this->_innerIterator;
if ($iterator instanceof CollectionInterface) {
$iterator = $iterator->unwrap();
}
if (get_class($iterator) !== ArrayIterator::class) {
return $this;
}
// ArrayIterator can be traversed strictly.
// Let's do that for performance gains
$callback = $this->_condition;
$res = [];
foreach ($iterator as $k => $v) {
if ($callback($v, $k, $iterator)) {
break;
}
$res[$k] = $v;
}
return new ArrayIterator($res);
}
}
@@ -0,0 +1,104 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Collection\Iterator;
use Cake\Collection\CollectionTrait;
use RecursiveIterator;
use RecursiveIteratorIterator;
/**
* A Recursive iterator used to flatten nested structures and also exposes
* all Collection methods
*/
class TreeIterator extends RecursiveIteratorIterator
{
use CollectionTrait;
/**
* The iteration mode
*
* @var int
*/
protected $_mode;
/**
* Constructor
*
* @param \RecursiveIterator $items The iterator to flatten.
* @param int $mode Iterator mode.
* @param int $flags Iterator flags.
*/
public function __construct(RecursiveIterator $items, $mode = RecursiveIteratorIterator::SELF_FIRST, $flags = 0)
{
parent::__construct($items, $mode, $flags);
$this->_mode = $mode;
}
/**
* Returns another iterator which will return the values ready to be displayed
* to a user. It does so by extracting one property from each of the elements
* and prefixing it with a spacer so that the relative position in the tree
* can be visualized.
*
* Both $valuePath and $keyPath can be a string with a property name to extract
* or a dot separated path of properties that should be followed to get the last
* one in the path.
*
* Alternatively, $valuePath and $keyPath can be callable functions. They will get
* the current element as first parameter, the current iteration key as second
* parameter, and the iterator instance as third argument.
*
* ### Example
*
* ```
* $printer = (new Collection($treeStructure))->listNested()->printer('name');
* ```
*
* Using a closure:
*
* ```
* $printer = (new Collection($treeStructure))
* ->listNested()
* ->printer(function ($item, $key, $iterator) {
* return $item->name;
* });
* ```
*
* @param string|callable $valuePath The property to extract or a callable to return
* the display value
* @param string|callable|null $keyPath The property to use as iteration key or a
* callable returning the key value.
* @param string $spacer The string to use for prefixing the values according to
* their depth in the tree
* @return \Cake\Collection\Iterator\TreePrinter
*/
public function printer($valuePath, $keyPath = null, $spacer = '__')
{
if (!$keyPath) {
$counter = 0;
$keyPath = function () use (&$counter) {
return $counter++;
};
}
return new TreePrinter(
$this->getInnerIterator(),
$valuePath,
$keyPath,
$spacer,
$this->_mode
);
}
}
@@ -0,0 +1,126 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Collection\Iterator;
use Cake\Collection\CollectionTrait;
use RecursiveIteratorIterator;
/**
* Iterator for flattening elements in a tree structure while adding some
* visual markers for their relative position in the tree
*/
class TreePrinter extends RecursiveIteratorIterator
{
use CollectionTrait;
/**
* A callable to generate the iteration key
*
* @var callable
*/
protected $_key;
/**
* A callable to extract the display value
*
* @var callable
*/
protected $_value;
/**
* Cached value for the current iteration element
*
* @var mixed
*/
protected $_current;
/**
* The string to use for prefixing the values according to their depth in the tree.
*
* @var string
*/
protected $_spacer;
/**
* Constructor
*
* @param \RecursiveIterator $items The iterator to flatten.
* @param string|callable $valuePath The property to extract or a callable to return
* the display value.
* @param string|callable $keyPath The property to use as iteration key or a
* callable returning the key value.
* @param string $spacer The string to use for prefixing the values according to
* their depth in the tree.
* @param int $mode Iterator mode.
*/
public function __construct($items, $valuePath, $keyPath, $spacer, $mode = RecursiveIteratorIterator::SELF_FIRST)
{
parent::__construct($items, $mode);
$this->_value = $this->_propertyExtractor($valuePath);
$this->_key = $this->_propertyExtractor($keyPath);
$this->_spacer = $spacer;
}
/**
* Returns the current iteration key
*
* @return mixed
*/
public function key()
{
$extractor = $this->_key;
return $extractor($this->_fetchCurrent(), parent::key(), $this);
}
/**
* Returns the current iteration value
*
* @return string
*/
public function current()
{
$extractor = $this->_value;
$current = $this->_fetchCurrent();
$spacer = str_repeat($this->_spacer, $this->getDepth());
return $spacer . $extractor($current, parent::key(), $this);
}
/**
* Advances the cursor one position
*
* @return void
*/
public function next()
{
parent::next();
$this->_current = null;
}
/**
* Returns the current iteration element and caches its value
*
* @return mixed
*/
protected function _fetchCurrent()
{
if ($this->_current !== null) {
return $this->_current;
}
return $this->_current = parent::current();
}
}
@@ -0,0 +1,85 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Collection\Iterator;
use IteratorIterator;
use RecursiveIterator;
/**
* An iterator that can be used to generate nested iterators out of a collection
* of items by applying an function to each of the elements in this iterator.
*
* @internal
* @see \Cake\Collection\Collection::unfold()
*/
class UnfoldIterator extends IteratorIterator implements RecursiveIterator
{
/**
* A function that is passed each element in this iterator and
* must return an array or Traversable object.
*
* @var callable
*/
protected $_unfolder;
/**
* A reference to the internal iterator this object is wrapping.
*
* @var \Iterator
*/
protected $_innerIterator;
/**
* Creates the iterator that will generate child iterators from each of the
* elements it was constructed with.
*
* @param array|\Traversable $items The list of values to iterate
* @param callable $unfolder A callable function that will receive the
* current item and key. It must return an array or Traversable object
* out of which the nested iterators will be yielded.
*/
public function __construct($items, callable $unfolder)
{
$this->_unfolder = $unfolder;
parent::__construct($items);
$this->_innerIterator = $this->getInnerIterator();
}
/**
* Returns true as each of the elements in the array represent a
* list of items
*
* @return bool
*/
public function hasChildren()
{
return true;
}
/**
* Returns an iterator containing the items generated by transforming
* the current value with the callable function.
*
* @return \RecursiveIterator
*/
public function getChildren()
{
$current = $this->current();
$key = $this->key();
$unfolder = $this->_unfolder;
return new NoChildrenIterator($unfolder($current, $key, $this->_innerIterator));
}
}
@@ -0,0 +1,125 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.5
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Collection\Iterator;
use Cake\Collection\Collection;
use Cake\Collection\CollectionInterface;
use Cake\Collection\CollectionTrait;
use MultipleIterator;
use Serializable;
/**
* Creates an iterator that returns elements grouped in pairs
*
* ### Example
*
* ```
* $iterator = new ZipIterator([[1, 2], [3, 4]]);
* $iterator->toList(); // Returns [[1, 3], [2, 4]]
* ```
*
* You can also chose a custom function to zip the elements together, such
* as doing a sum by index:
*
* ### Example
*
* ```
* $iterator = new ZipIterator([[1, 2], [3, 4]], function ($a, $b) {
* return $a + $b;
* });
* $iterator->toList(); // Returns [4, 6]
* ```
*/
class ZipIterator extends MultipleIterator implements CollectionInterface, Serializable
{
use CollectionTrait;
/**
* The function to use for zipping items together
*
* @var callable|null
*/
protected $_callback;
/**
* Contains the original iterator objects that were attached
*
* @var array
*/
protected $_iterators = [];
/**
* Creates the iterator to merge together the values by for all the passed
* iterators by their corresponding index.
*
* @param array $sets The list of array or iterators to be zipped.
* @param callable|null $callable The function to use for zipping the elements of each iterator.
*/
public function __construct(array $sets, $callable = null)
{
$sets = array_map(function ($items) {
return (new Collection($items))->unwrap();
}, $sets);
$this->_callback = $callable;
parent::__construct(MultipleIterator::MIT_NEED_ALL | MultipleIterator::MIT_KEYS_NUMERIC);
foreach ($sets as $set) {
$this->_iterators[] = $set;
$this->attachIterator($set);
}
}
/**
* Returns the value resulting out of zipping all the elements for all the
* iterators with the same positional index.
*
* @return array|false
*/
public function current()
{
if ($this->_callback === null) {
return parent::current();
}
return call_user_func_array($this->_callback, parent::current());
}
/**
* Returns a string representation of this object that can be used
* to reconstruct it
*
* @return string
*/
public function serialize()
{
return serialize($this->_iterators);
}
/**
* Unserializes the passed string and rebuilds the ZipIterator instance
*
* @param string $iterators The serialized iterators
* @return void
*/
public function unserialize($iterators)
{
parent::__construct(MultipleIterator::MIT_NEED_ALL | MultipleIterator::MIT_KEYS_NUMERIC);
$this->_iterators = unserialize($iterators);
foreach ($this->_iterators as $it) {
$this->attachIterator($it);
}
}
}
+22
View File
@@ -0,0 +1,22 @@
The MIT License (MIT)
CakePHP(tm) : The Rapid Development PHP Framework (https://cakephp.org)
Copyright (c) 2005-2016, Cake Software Foundation, Inc. (https://cakefoundation.org)
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.
+31
View File
@@ -0,0 +1,31 @@
[![Total Downloads](https://img.shields.io/packagist/dt/cakephp/collection.svg?style=flat-square)](https://packagist.org/packages/cakephp/collection)
[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE.txt)
# CakePHP Collection Library
The collection classes provide a set of tools to manipulate arrays or Traversable objects.
If you have ever used underscore.js, you have an idea of what you can expect from the collection classes.
## Usage
Collections can be created using an array or Traversable object. A simple use of a Collection would be:
```php
use Cake\Collection\Collection;
$items = ['a' => 1, 'b' => 2, 'c' => 3];
$collection = new Collection($items);
// Create a new collection containing elements
// with a value greater than one.
$overOne = $collection->filter(function ($value, $key, $iterator) {
return $value > 1;
});
```
The `Collection\CollectionTrait` allows you to integrate collection-like features into any Traversable object
you have in your application as well.
## Documentation
Please make sure you check the [official documentation](https://book.cakephp.org/3/en/core-libraries/collections.html)
+36
View File
@@ -0,0 +1,36 @@
{
"name": "cakephp/collection",
"description": "Work easily with arrays and iterators by having a battery of utility traversal methods",
"type": "library",
"keywords": [
"cakephp",
"collections",
"iterators",
"arrays"
],
"homepage": "https://cakephp.org",
"license": "MIT",
"authors": [
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/collection/graphs/contributors"
}
],
"support": {
"issues": "https://github.com/cakephp/cakephp/issues",
"forum": "https://stackoverflow.com/tags/cakephp",
"irc": "irc://irc.freenode.org/cakephp",
"source": "https://github.com/cakephp/collection"
},
"require": {
"php": ">=5.6.0"
},
"autoload": {
"psr-4": {
"Cake\\Collection\\": "."
},
"files": [
"functions.php"
]
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
use Cake\Collection\Collection;
if (!function_exists('collection')) {
/**
* Returns a new Cake\Collection\Collection object wrapping the passed argument.
*
* @param \Traversable|array $items The items from which the collection will be built.
* @return \Cake\Collection\Collection
*/
function collection($items)
{
return new Collection($items);
}
}
+209
View File
@@ -0,0 +1,209 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.6.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Command;
use Cake\Console\Arguments;
use Cake\Console\Command;
use Cake\Console\CommandCollection;
use Cake\Console\CommandCollectionAwareInterface;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;
use Cake\Console\ConsoleOutput;
use Cake\Utility\Inflector;
use SimpleXMLElement;
/**
* Print out command list
*/
class HelpCommand extends Command implements CommandCollectionAwareInterface
{
/**
* The command collection to get help on.
*
* @var \Cake\Console\CommandCollection
*/
protected $commands;
/**
* {@inheritDoc}
*/
public function setCommandCollection(CommandCollection $commands)
{
$this->commands = $commands;
}
/**
* Main function Prints out the list of commands.
*
* @param \Cake\Console\Arguments $args The command arguments.
* @param \Cake\Console\ConsoleIo $io The console io
* @return int
*/
public function execute(Arguments $args, ConsoleIo $io)
{
if (!$args->getOption('xml')) {
$io->out('<info>Current Paths:</info>', 2);
$io->out('* app: ' . APP_DIR . DIRECTORY_SEPARATOR);
$io->out('* root: ' . ROOT . DIRECTORY_SEPARATOR);
$io->out('* core: ' . CORE_PATH);
$io->out('');
$io->out('<info>Available Commands:</info>', 2);
}
$commands = $this->commands->getIterator();
$commands->ksort();
if ($args->getOption('xml')) {
$this->asXml($io, $commands);
return static::CODE_SUCCESS;
}
$this->asText($io, $commands);
return static::CODE_SUCCESS;
}
/**
* Output text.
*
* @param \Cake\Console\ConsoleIo $io The console io
* @param \ArrayIterator $commands The command collection to output.
* @return void
*/
protected function asText($io, $commands)
{
$invert = [];
foreach ($commands as $name => $class) {
if (is_object($class)) {
$class = get_class($class);
}
if (!isset($invert[$class])) {
$invert[$class] = [];
}
$invert[$class][] = $name;
}
$prefixed = [];
foreach ($invert as $class => $names) {
$prefixedName = $this->findPrefixedName($names);
if (!$prefixedName) {
$prefix = preg_match('#^Cake\\\\(Command|Shell)\\\\#', $class) ? '[core]' : '[app]';
$prefixed[$prefix][] = $this->getShortestName($names);
continue;
}
list ($prefix, $name) = explode('.', $prefixedName, 2);
$prefix = Inflector::camelize($prefix);
$shortestName = $this->getShortestName($names);
if (strpos($shortestName, '.') !== false) {
list (, $shortestName) = explode('.', $shortestName, 2);
}
$prefixed[$prefix][] = $shortestName;
}
ksort($prefixed);
foreach ($prefixed as $prefix => $names) {
$io->out($prefix . ':');
sort($names);
foreach ($names as $name) {
$io->out(' - ' . $name);
}
}
$io->out('');
$io->out('To run a command, type <info>`cake command_name [args|options]`</info>');
$io->out('To get help on a specific command, type <info>`cake command_name --help`</info>', 2);
}
/**
* @param string[] $names Names
*
* @return string|null
*/
protected function findPrefixedName(array $names)
{
foreach ($names as $name) {
if (strpos($name, '.') !== false) {
return $name;
}
}
return null;
}
/**
* @param string[] $names Names
* @return string
*/
protected function getShortestName(array $names)
{
if (count($names) <= 1) {
return array_shift($names);
}
usort($names, function ($a, $b) {
return strlen($a) - strlen($b);
});
return array_shift($names);
}
/**
* Output as XML
*
* @param \Cake\Console\ConsoleIo $io The console io
* @param \ArrayIterator $commands The command collection to output
* @return void
*/
protected function asXml($io, $commands)
{
$shells = new SimpleXMLElement('<shells></shells>');
foreach ($commands as $name => $class) {
if (is_object($class)) {
$class = get_class($class);
}
$shell = $shells->addChild('shell');
$shell->addAttribute('name', $name);
$shell->addAttribute('call_as', $name);
$shell->addAttribute('provider', $class);
$shell->addAttribute('help', $name . ' -h');
}
$io->setOutputAs(ConsoleOutput::RAW);
$io->out($shells->saveXML());
}
/**
* Gets the option parser instance and configures it.
*
* @param \Cake\Console\ConsoleOptionParser $parser The parser to build
* @return \Cake\Console\ConsoleOptionParser
*/
protected function buildOptionParser(ConsoleOptionParser $parser)
{
$parser->setDescription(
'Get the list of available commands for this application.'
)->addOption('xml', [
'help' => 'Get the listing as XML.',
'boolean' => true,
]);
return $parser;
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.6.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Command;
use Cake\Console\Arguments;
use Cake\Console\Command;
use Cake\Console\ConsoleIo;
use Cake\Core\Configure;
/**
* Print out the version of CakePHP in use.
*/
class VersionCommand extends Command
{
/**
* Print out the version of CakePHP in use.
*
* @param \Cake\Console\Arguments $args The command arguments.
* @param \Cake\Console\ConsoleIo $io The console io
* @return int
*/
public function execute(Arguments $args, ConsoleIo $io)
{
$io->out(Configure::version());
return static::CODE_SUCCESS;
}
}
+162
View File
@@ -0,0 +1,162 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.6.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
/**
* Provides an interface for interacting with
* a command's options and arguments.
*/
class Arguments
{
/**
* Positional argument name map
*
* @var string[]
*/
protected $argNames;
/**
* Positional arguments.
*
* @var string[]
*/
protected $args;
/**
* Named options
*
* @var array
*/
protected $options;
/**
* Constructor
*
* @param string[] $args Positional arguments
* @param array $options Named arguments
* @param string[] $argNames List of argument names. Order is expected to be
* the same as $args.
*/
public function __construct(array $args, array $options, array $argNames)
{
$this->args = $args;
$this->options = $options;
$this->argNames = $argNames;
}
/**
* Get all positional arguments.
*
* @return string[]
*/
public function getArguments()
{
return $this->args;
}
/**
* Get positional arguments by index.
*
* @param int $index The argument index to access.
* @return string|null The argument value or null
*/
public function getArgumentAt($index)
{
if ($this->hasArgumentAt($index)) {
return $this->args[$index];
}
return null;
}
/**
* Check if a positional argument exists
*
* @param int $index The argument index to check.
* @return bool
*/
public function hasArgumentAt($index)
{
return isset($this->args[$index]);
}
/**
* Check if a positional argument exists by name
*
* @param string $name The argument name to check.
* @return bool
*/
public function hasArgument($name)
{
$offset = array_search($name, $this->argNames, true);
if ($offset === false) {
return false;
}
return isset($this->args[$offset]);
}
/**
* Check if a positional argument exists by name
*
* @param string $name The argument name to check.
* @return string|null
*/
public function getArgument($name)
{
$offset = array_search($name, $this->argNames, true);
if ($offset === false || !isset($this->args[$offset])) {
return null;
}
return $this->args[$offset];
}
/**
* Get an array of all the options
*
* @return array
*/
public function getOptions()
{
return $this->options;
}
/**
* Get an option's value or null
*
* @param string $name The name of the option to check.
* @return string|int|bool|null The option value or null.
*/
public function getOption($name)
{
if (isset($this->options[$name])) {
return $this->options[$name];
}
return null;
}
/**
* Check if an option is defined and not null.
*
* @param string $name The name of the option to check.
* @return bool
*/
public function hasOption($name)
{
return isset($this->options[$name]);
}
}
+283
View File
@@ -0,0 +1,283 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.6.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
use Cake\Console\Exception\ConsoleException;
use Cake\Console\Exception\StopException;
use Cake\Datasource\ModelAwareTrait;
use Cake\Log\LogTrait;
use Cake\ORM\Locator\LocatorAwareTrait;
use InvalidArgumentException;
use RuntimeException;
/**
* Base class for console commands.
*/
class Command
{
use LocatorAwareTrait;
use LogTrait;
use ModelAwareTrait;
/**
* Default error code
*
* @var int
*/
const CODE_ERROR = 1;
/**
* Default success code
*
* @var int
*/
const CODE_SUCCESS = 0;
/**
* The name of this command.
*
* @var string
*/
protected $name = 'cake unknown';
/**
* Constructor
*
* By default CakePHP will construct command objects when
* building the CommandCollection for your application.
*/
public function __construct()
{
$this->modelFactory('Table', function ($alias) {
return $this->getTableLocator()->get($alias);
});
if (isset($this->modelClass)) {
$this->loadModel();
}
}
/**
* Set the name this command uses in the collection.
*
* Generally invoked by the CommandCollection when the command is added.
* Required to have at least one space in the name so that the root
* command can be calculated.
*
* @param string $name The name the command uses in the collection.
* @return $this
* @throws \InvalidArgumentException
*/
public function setName($name)
{
if (strpos($name, ' ') < 1) {
throw new InvalidArgumentException(
"The name '{$name}' is missing a space. Names should look like `cake routes`"
);
}
$this->name = $name;
return $this;
}
/**
* Get the command name.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Get the option parser.
*
* You can override buildOptionParser() to define your options & arguments.
*
* @return \Cake\Console\ConsoleOptionParser
* @throws \RuntimeException When the parser is invalid
*/
public function getOptionParser()
{
list($root, $name) = explode(' ', $this->name, 2);
$parser = new ConsoleOptionParser($name);
$parser->setRootName($root);
$parser = $this->buildOptionParser($parser);
if (!($parser instanceof ConsoleOptionParser)) {
throw new RuntimeException(sprintf(
"Invalid option parser returned from buildOptionParser(). Expected %s, got %s",
ConsoleOptionParser::class,
getTypeName($parser)
));
}
return $parser;
}
/**
* Hook method for defining this command's option parser.
*
* @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined
* @return \Cake\Console\ConsoleOptionParser The built parser.
*/
protected function buildOptionParser(ConsoleOptionParser $parser)
{
return $parser;
}
/**
* Hook method invoked by CakePHP when a command is about to be executed.
*
* Override this method and implement expensive/important setup steps that
* should not run on every command run. This method will be called *before*
* the options and arguments are validated and processed.
*
* @return void
*/
public function initialize()
{
}
/**
* Run the command.
*
* @param array $argv Arguments from the CLI environment.
* @param \Cake\Console\ConsoleIo $io The console io
* @return int|null Exit code or null for success.
*/
public function run(array $argv, ConsoleIo $io)
{
$this->initialize();
$parser = $this->getOptionParser();
try {
list($options, $arguments) = $parser->parse($argv);
$args = new Arguments(
$arguments,
$options,
$parser->argumentNames()
);
} catch (ConsoleException $e) {
$io->err('Error: ' . $e->getMessage());
return static::CODE_ERROR;
}
$this->setOutputLevel($args, $io);
if ($args->getOption('help')) {
$this->displayHelp($parser, $args, $io);
return static::CODE_SUCCESS;
}
return $this->execute($args, $io);
}
/**
* Output help content
*
* @param \Cake\Console\ConsoleOptionParser $parser The option parser.
* @param \Cake\Console\Arguments $args The command arguments.
* @param \Cake\Console\ConsoleIo $io The console io
* @return void
*/
protected function displayHelp(ConsoleOptionParser $parser, Arguments $args, ConsoleIo $io)
{
$format = 'text';
if ($args->getArgumentAt(0) === 'xml') {
$format = 'xml';
$io->setOutputAs(ConsoleOutput::RAW);
}
$io->out($parser->help(null, $format));
}
/**
* Set the output level based on the Arguments.
*
* @param \Cake\Console\Arguments $args The command arguments.
* @param \Cake\Console\ConsoleIo $io The console io
* @return void
*/
protected function setOutputLevel(Arguments $args, ConsoleIo $io)
{
$io->setLoggers(ConsoleIo::NORMAL);
if ($args->getOption('quiet')) {
$io->level(ConsoleIo::QUIET);
$io->setLoggers(ConsoleIo::QUIET);
}
if ($args->getOption('verbose')) {
$io->level(ConsoleIo::VERBOSE);
$io->setLoggers(ConsoleIo::VERBOSE);
}
}
/**
* Implement this method with your command's logic.
*
* @param \Cake\Console\Arguments $args The command arguments.
* @param \Cake\Console\ConsoleIo $io The console io
* @return int|null The exit code or null for success
*/
public function execute(Arguments $args, ConsoleIo $io)
{
return null;
}
/**
* Halt the the current process with a StopException.
*
* @param int $code The exit code to use.
* @throws \Cake\Console\Exception\StopException
* @return void
*/
public function abort($code = self::CODE_ERROR)
{
throw new StopException('Command aborted', $code);
}
/**
* Execute another command with the provided set of arguments.
*
* @param string|\Cake\Console\Command $command The command class name or command instance.
* @param array $args The arguments to invoke the command with.
* @param \Cake\Console\ConsoleIo $io The ConsoleIo instance to use for the executed command.
* @return int|null The exit code or null for success of the command.
*/
public function executeCommand($command, array $args = [], ConsoleIo $io = null)
{
if (is_string($command)) {
if (!class_exists($command)) {
throw new InvalidArgumentException("Command class '{$command}' does not exist.");
}
$command = new $command();
}
if (!$command instanceof Command) {
$commandType = getTypeName($command);
throw new InvalidArgumentException(
"Command '{$commandType}' is not a subclass of Cake\Console\Command."
);
}
$io = $io ?: new ConsoleIo();
try {
return $command->run($args, $io);
} catch (StopException $e) {
return $e->getCode();
}
}
}
+228
View File
@@ -0,0 +1,228 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @since 3.5.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
use ArrayIterator;
use Countable;
use InvalidArgumentException;
use IteratorAggregate;
/**
* Collection for Commands.
*
* Used by Applications to whitelist their console commands.
* CakePHP will use the mapped commands to construct and dispatch
* shell commands.
*/
class CommandCollection implements IteratorAggregate, Countable
{
/**
* Command list
*
* @var array
*/
protected $commands = [];
/**
* Constructor
*
* @param array $commands The map of commands to add to the collection.
*/
public function __construct(array $commands = [])
{
foreach ($commands as $name => $command) {
$this->add($name, $command);
}
}
/**
* Add a command to the collection
*
* @param string $name The name of the command you want to map.
* @param string|\Cake\Console\Shell|\Cake\Console\Command $command The command to map.
* @return $this
* @throws \InvalidArgumentException
*/
public function add($name, $command)
{
// Once we have a new Command class this should check
// against that interface.
if (!is_subclass_of($command, Shell::class) && !is_subclass_of($command, Command::class)) {
$class = is_string($command) ? $command : get_class($command);
throw new InvalidArgumentException(
"Cannot use '$class' for command '$name'. It is not a subclass of Cake\Console\Shell or Cake\Console\Command."
);
}
if (!preg_match('/^[^\s]+(?:(?: [^\s]+){1,2})?$/ui', $name)) {
throw new InvalidArgumentException(
"The command name `{$name}` is invalid. Names can only be a maximum of three words."
);
}
$this->commands[$name] = $command;
return $this;
}
/**
* Add multiple commands at once.
*
* @param array $commands A map of command names => command classes/instances.
* @return $this
* @see \Cake\Console\CommandCollection::add()
*/
public function addMany(array $commands)
{
foreach ($commands as $name => $class) {
$this->add($name, $class);
}
return $this;
}
/**
* Remove a command from the collection if it exists.
*
* @param string $name The named shell.
* @return $this
*/
public function remove($name)
{
unset($this->commands[$name]);
return $this;
}
/**
* Check whether the named shell exists in the collection.
*
* @param string $name The named shell.
* @return bool
*/
public function has($name)
{
return isset($this->commands[$name]);
}
/**
* Get the target for a command.
*
* @param string $name The named shell.
* @return string|\Cake\Console\Shell Either the shell class or an instance.
* @throws \InvalidArgumentException when unknown commands are fetched.
*/
public function get($name)
{
if (!$this->has($name)) {
throw new InvalidArgumentException("The $name is not a known command name.");
}
return $this->commands[$name];
}
/**
* Implementation of IteratorAggregate.
*
* @return \ArrayIterator
*/
public function getIterator()
{
return new ArrayIterator($this->commands);
}
/**
* Implementation of Countable.
*
* Get the number of commands in the collection.
*
* @return int
*/
public function count()
{
return count($this->commands);
}
/**
* Auto-discover shell & commands from the named plugin.
*
* Discovered commands will have their names de-duplicated with
* existing commands in the collection. If a command is already
* defined in the collection and discovered in a plugin, only
* the long name (`plugin.command`) will be returned.
*
* @param string $plugin The plugin to scan.
* @return string[] Discovered plugin commands.
*/
public function discoverPlugin($plugin)
{
$scanner = new CommandScanner();
$shells = $scanner->scanPlugin($plugin);
return $this->resolveNames($shells);
}
/**
* Resolve names based on existing commands
*
* @param array $input The results of a CommandScanner operation.
* @return string[] A flat map of command names => class names.
*/
protected function resolveNames(array $input)
{
$out = [];
foreach ($input as $info) {
$name = $info['name'];
$addLong = $name !== $info['fullName'];
// If the short name has been used, use the full name.
// This allows app shells to have name preference.
// and app shells to overwrite core shells.
if ($this->has($name) && $addLong) {
$name = $info['fullName'];
}
$out[$name] = $info['class'];
if ($addLong) {
$out[$info['fullName']] = $info['class'];
}
}
return $out;
}
/**
* Automatically discover shell commands in CakePHP, the application and all plugins.
*
* Commands will be located using filesystem conventions. Commands are
* discovered in the following order:
*
* - CakePHP provided commands
* - Application commands
*
* Commands defined in the application will ovewrite commands with
* the same name provided by CakePHP.
*
* @return string[] An array of command names and their classes.
*/
public function autoDiscover()
{
$scanner = new CommandScanner();
$core = $this->resolveNames($scanner->scanCore());
$app = $this->resolveNames($scanner->scanApp());
return array_merge($core, $app);
}
}
@@ -0,0 +1,31 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @since 3.5.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
/**
* An interface for shells that take a CommandCollection
* during initialization.
*
*/
interface CommandCollectionAwareInterface
{
/**
* Set the command collection being used.
*
* @param \Cake\Console\CommandCollection $commands The commands to use.
* @return void
*/
public function setCommandCollection(CommandCollection $commands);
}
+39
View File
@@ -0,0 +1,39 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
use InvalidArgumentException;
/**
* This is a factory for creating Command and Shell instances.
*
* This factory can be replaced or extended if you need to customize building
* your command and shell objects.
*/
class CommandFactory implements CommandFactoryInterface
{
/**
* {@inheritDoc}
*/
public function create($className)
{
$command = new $className();
if (!($command instanceof Command) && !($command instanceof Shell)) {
$valid = implode('` or `', [Shell::class, Command::class]);
$message = sprintf('Class `%s` must be an instance of `%s`.', $className, $valid);
throw new InvalidArgumentException($message);
}
return $command;
}
}
@@ -0,0 +1,27 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
/**
* An interface for abstracting creation of command and shell instances.
*/
interface CommandFactoryInterface
{
/**
* The factory method for creating Command and Shell instances.
*
* @param string $className Command/Shell class name.
* @return \Cake\Console\Shell|\Cake\Console\Command
*/
public function create($className);
}
+426
View File
@@ -0,0 +1,426 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @since 3.5.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
use Cake\Command\HelpCommand;
use Cake\Command\VersionCommand;
use Cake\Console\Exception\StopException;
use Cake\Core\ConsoleApplicationInterface;
use Cake\Core\HttpApplicationInterface;
use Cake\Core\PluginApplicationInterface;
use Cake\Event\EventDispatcherInterface;
use Cake\Event\EventDispatcherTrait;
use Cake\Event\EventManager;
use Cake\Routing\Router;
use Cake\Utility\Inflector;
use InvalidArgumentException;
use RuntimeException;
/**
* Run CLI commands for the provided application.
*/
class CommandRunner implements EventDispatcherInterface
{
/**
* Alias methods away so we can implement proxying methods.
*/
use EventDispatcherTrait {
eventManager as private _eventManager;
getEventManager as private _getEventManager;
setEventManager as private _setEventManager;
}
/**
* The application console commands are being run for.
*
* @var \Cake\Core\ConsoleApplicationInterface
*/
protected $app;
/**
* The application console commands are being run for.
*
* @var \Cake\Console\CommandFactoryInterface
*/
protected $factory;
/**
* The root command name. Defaults to `cake`.
*
* @var string
*/
protected $root;
/**
* Alias mappings.
*
* @var array
*/
protected $aliases = [];
/**
* Constructor
*
* @param \Cake\Core\ConsoleApplicationInterface $app The application to run CLI commands for.
* @param string $root The root command name to be removed from argv.
* @param \Cake\Console\CommandFactoryInterface|null $factory Command factory instance.
*/
public function __construct(ConsoleApplicationInterface $app, $root = 'cake', CommandFactoryInterface $factory = null)
{
$this->app = $app;
$this->root = $root;
$this->factory = $factory ?: new CommandFactory();
$this->aliases = [
'--version' => 'version',
'--help' => 'help',
'-h' => 'help',
];
}
/**
* Replace the entire alias map for a runner.
*
* Aliases allow you to define alternate names for commands
* in the collection. This can be useful to add top level switches
* like `--version` or `-h`
*
* ### Usage
*
* ```
* $runner->setAliases(['--version' => 'version']);
* ```
*
* @param string[] $aliases The map of aliases to replace.
* @return $this
*/
public function setAliases(array $aliases)
{
$this->aliases = $aliases;
return $this;
}
/**
* Run the command contained in $argv.
*
* Use the application to do the following:
*
* - Bootstrap the application
* - Create the CommandCollection using the console() hook on the application.
* - Trigger the `Console.buildCommands` event of auto-wiring plugins.
* - Run the requested command.
*
* @param array $argv The arguments from the CLI environment.
* @param \Cake\Console\ConsoleIo $io The ConsoleIo instance. Used primarily for testing.
* @return int The exit code of the command.
* @throws \RuntimeException
*/
public function run(array $argv, ConsoleIo $io = null)
{
$this->bootstrap();
$commands = new CommandCollection([
'version' => VersionCommand::class,
'help' => HelpCommand::class,
]);
$commands = $this->app->console($commands);
$this->checkCollection($commands, 'console');
if ($this->app instanceof PluginApplicationInterface) {
$commands = $this->app->pluginConsole($commands);
}
$this->checkCollection($commands, 'pluginConsole');
$this->dispatchEvent('Console.buildCommands', ['commands' => $commands]);
$this->loadRoutes();
if (empty($argv)) {
throw new RuntimeException("Cannot run any commands. No arguments received.");
}
// Remove the root executable segment
array_shift($argv);
$io = $io ?: new ConsoleIo();
list($name, $argv) = $this->longestCommandName($commands, $argv);
$name = $this->resolveName($commands, $io, $name);
$result = Command::CODE_ERROR;
$shell = $this->getShell($io, $commands, $name);
if ($shell instanceof Shell) {
$result = $this->runShell($shell, $argv);
}
if ($shell instanceof Command) {
$result = $this->runCommand($shell, $argv, $io);
}
if ($result === null || $result === true) {
return Command::CODE_SUCCESS;
}
if (is_int($result) && $result >= 0 && $result <= 255) {
return $result;
}
return Command::CODE_ERROR;
}
/**
* Application bootstrap wrapper.
*
* Calls `bootstrap()` and `events()` if application implements `EventApplicationInterface`.
* After the application is bootstrapped and events are attached, plugins are bootstrapped
* and have their events attached.
*
* @return void
*/
protected function bootstrap()
{
$this->app->bootstrap();
if ($this->app instanceof PluginApplicationInterface) {
$this->app->pluginBootstrap();
}
}
/**
* Check the created CommandCollection
*
* @param mixed $commands The CommandCollection to check, could be anything though.
* @param string $method The method that was used.
* @return void
* @throws \RuntimeException
* @deprecated 3.6.0 This method should be replaced with return types in 4.x
*/
protected function checkCollection($commands, $method)
{
if (!($commands instanceof CommandCollection)) {
$type = getTypeName($commands);
throw new RuntimeException(
"The application's `{$method}` method did not return a CommandCollection." .
" Got '{$type}' instead."
);
}
}
/**
* Get the application's event manager or the global one.
*
* @return \Cake\Event\EventManagerInterface
*/
public function getEventManager()
{
if ($this->app instanceof PluginApplicationInterface) {
return $this->app->getEventManager();
}
return EventManager::instance();
}
/**
* Get/set the application's event manager.
*
* If the application does not support events and this method is used as
* a setter, an exception will be raised.
*
* @param \Cake\Event\EventManager|null $events The event manager to set.
* @return \Cake\Event\EventManager|$this
* @deprecated 3.6.0 Will be removed in 4.0
*/
public function eventManager(EventManager $events = null)
{
deprecationWarning('eventManager() is deprecated. Use getEventManager()/setEventManager() instead.');
if ($events === null) {
return $this->getEventManager();
}
return $this->setEventManager($events);
}
/**
* Get/set the application's event manager.
*
* If the application does not support events and this method is used as
* a setter, an exception will be raised.
*
* @param \Cake\Event\EventManager $events The event manager to set.
* @return $this
*/
public function setEventManager(EventManager $events)
{
if ($this->app instanceof PluginApplicationInterface) {
$this->app->setEventManager($events);
return $this;
}
throw new InvalidArgumentException('Cannot set the event manager, the application does not support events.');
}
/**
* Get the shell instance for a given command name
*
* @param \Cake\Console\ConsoleIo $io The IO wrapper for the created shell class.
* @param \Cake\Console\CommandCollection $commands The command collection to find the shell in.
* @param string $name The command name to find
* @return \Cake\Console\Shell|\Cake\Console\Command
*/
protected function getShell(ConsoleIo $io, CommandCollection $commands, $name)
{
$instance = $commands->get($name);
if (is_string($instance)) {
$instance = $this->createShell($instance, $io);
}
if ($instance instanceof Shell) {
$instance->setRootName($this->root);
}
if ($instance instanceof Command) {
$instance->setName("{$this->root} {$name}");
}
if ($instance instanceof CommandCollectionAwareInterface) {
$instance->setCommandCollection($commands);
}
return $instance;
}
/**
* Build the longest command name that exists in the collection
*
* Build the longest command name that matches a
* defined command. This will traverse a maximum of 3 tokens.
*
* @param \Cake\Console\CommandCollection $commands The command collection to check.
* @param array $argv The CLI arguments.
* @return array An array of the resolved name and modified argv.
*/
protected function longestCommandName($commands, $argv)
{
for ($i = 3; $i > 1; $i--) {
$parts = array_slice($argv, 0, $i);
$name = implode(' ', $parts);
if ($commands->has($name)) {
return [$name, array_slice($argv, $i)];
}
}
$name = array_shift($argv);
return [$name, $argv];
}
/**
* Resolve the command name into a name that exists in the collection.
*
* Apply backwards compatible inflections and aliases.
* Will step forward up to 3 tokens in $argv to generate
* a command name in the CommandCollection. More specific
* command names take precedence over less specific ones.
*
* @param \Cake\Console\CommandCollection $commands The command collection to check.
* @param \Cake\Console\ConsoleIo $io ConsoleIo object for errors.
* @param string $name The name
* @return string The resolved class name
* @throws \RuntimeException
*/
protected function resolveName($commands, $io, $name)
{
if (!$name) {
$io->err('<error>No command provided. Choose one of the available commands.</error>', 2);
$name = 'help';
}
if (isset($this->aliases[$name])) {
$name = $this->aliases[$name];
}
if (!$commands->has($name)) {
$name = Inflector::underscore($name);
}
if (!$commands->has($name)) {
throw new RuntimeException(
"Unknown command `{$this->root} {$name}`." .
" Run `{$this->root} --help` to get the list of valid commands."
);
}
return $name;
}
/**
* Execute a Command class.
*
* @param \Cake\Console\Command $command The command to run.
* @param array $argv The CLI arguments to invoke.
* @param \Cake\Console\ConsoleIo $io The console io
* @return int Exit code
*/
protected function runCommand(Command $command, array $argv, ConsoleIo $io)
{
try {
return $command->run($argv, $io);
} catch (StopException $e) {
return $e->getCode();
}
}
/**
* Execute a Shell class.
*
* @param \Cake\Console\Shell $shell The shell to run.
* @param array $argv The CLI arguments to invoke.
* @return int Exit code
*/
protected function runShell(Shell $shell, array $argv)
{
try {
$shell->initialize();
return $shell->runCommand($argv, true);
} catch (StopException $e) {
return $e->getCode();
}
}
/**
* The wrapper for creating shell instances.
*
* @param string $className Shell class name.
* @param \Cake\Console\ConsoleIo $io The IO wrapper for the created shell class.
* @return \Cake\Console\Shell|\Cake\Console\Command
*/
protected function createShell($className, ConsoleIo $io)
{
$shell = $this->factory->create($className);
if ($shell instanceof Shell) {
$shell->setIo($io);
}
return $shell;
}
/**
* Ensure that the application's routes are loaded.
*
* Console commands and shells often need to generate URLs.
*
* @return void
*/
protected function loadRoutes()
{
$builder = Router::createRouteBuilder('/');
if ($this->app instanceof HttpApplicationInterface) {
$this->app->routes($builder);
}
if ($this->app instanceof PluginApplicationInterface) {
$this->app->pluginRoutes($builder);
}
}
}
+148
View File
@@ -0,0 +1,148 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @since 3.5.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
use Cake\Core\App;
use Cake\Core\Configure;
use Cake\Core\Plugin;
use Cake\Filesystem\Folder;
use Cake\Utility\Inflector;
/**
* Used by CommandCollection and CommandTask to scan the filesystem
* for command classes.
*
* @internal
*/
class CommandScanner
{
/**
* Scan CakePHP internals for shells & commands.
*
* @return array A list of command metadata.
*/
public function scanCore()
{
$coreShells = $this->scanDir(
dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Shell' . DIRECTORY_SEPARATOR,
'Cake\Shell\\',
'',
['command_list']
);
$coreCommands = $this->scanDir(
dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Command' . DIRECTORY_SEPARATOR,
'Cake\Command\\',
'',
['command_list']
);
return array_merge($coreShells, $coreCommands);
}
/**
* Scan the application for shells & commands.
*
* @return array A list of command metadata.
*/
public function scanApp()
{
$appNamespace = Configure::read('App.namespace');
$appShells = $this->scanDir(
App::path('Shell')[0],
$appNamespace . '\Shell\\',
'',
[]
);
$appCommands = $this->scanDir(
App::path('Command')[0],
$appNamespace . '\Command\\',
'',
[]
);
return array_merge($appShells, $appCommands);
}
/**
* Scan the named plugin for shells and commands
*
* @param string $plugin The named plugin.
* @return array A list of command metadata.
*/
public function scanPlugin($plugin)
{
if (!Plugin::isLoaded($plugin)) {
return [];
}
$path = Plugin::classPath($plugin);
$namespace = str_replace('/', '\\', $plugin);
$prefix = Inflector::underscore($plugin) . '.';
$commands = $this->scanDir($path . 'Command', $namespace . '\Command\\', $prefix, []);
$shells = $this->scanDir($path . 'Shell', $namespace . '\Shell\\', $prefix, []);
return array_merge($shells, $commands);
}
/**
* Scan a directory for .php files and return the class names that
* should be within them.
*
* @param string $path The directory to read.
* @param string $namespace The namespace the shells live in.
* @param string $prefix The prefix to apply to commands for their full name.
* @param string[] $hide A list of command names to hide as they are internal commands.
* @return array The list of shell info arrays based on scanning the filesystem and inflection.
*/
protected function scanDir($path, $namespace, $prefix, array $hide)
{
$dir = new Folder($path);
$contents = $dir->read(true, true);
if (empty($contents[1])) {
return [];
}
$classPattern = '/(Shell|Command)$/';
$shells = [];
foreach ($contents[1] as $file) {
if (substr($file, -4) !== '.php') {
continue;
}
$shell = substr($file, 0, -4);
if (!preg_match($classPattern, $shell)) {
continue;
}
$name = Inflector::underscore(preg_replace($classPattern, '', $shell));
if (in_array($name, $hide, true)) {
continue;
}
$class = $namespace . $shell;
if (!is_subclass_of($class, Shell::class) && !is_subclass_of($class, Command::class)) {
continue;
}
$shells[] = [
'file' => $path . $file,
'fullName' => $prefix . $name,
'name' => $name,
'class' => $class,
];
}
return $shells;
}
}
@@ -0,0 +1,136 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
use Cake\Error\BaseErrorHandler;
use Cake\Error\FatalErrorException;
use Cake\Error\PHP7ErrorException;
use Exception;
/**
* Error Handler for Cake console. Does simple printing of the
* exception that occurred and the stack trace of the error.
*/
class ConsoleErrorHandler extends BaseErrorHandler
{
/**
* Standard error stream.
*
* @var \Cake\Console\ConsoleOutput
*/
protected $_stderr;
/**
* Options for this instance.
*
* @var array
*/
protected $_options;
/**
* Constructor
*
* @param array $options Options for the error handler.
*/
public function __construct($options = [])
{
if (empty($options['stderr'])) {
$options['stderr'] = new ConsoleOutput('php://stderr');
}
$this->_stderr = $options['stderr'];
$this->_options = $options;
}
/**
* Handle errors in the console environment. Writes errors to stderr,
* and logs messages if Configure::read('debug') is false.
*
* @param \Exception $exception Exception instance.
* @return void
* @throws \Exception When renderer class not found
* @see https://secure.php.net/manual/en/function.set-exception-handler.php
*/
public function handleException(Exception $exception)
{
$this->_displayException($exception);
$this->_logException($exception);
$code = $exception->getCode();
$code = ($code && is_int($code)) ? $code : 1;
$this->_stop($code);
}
/**
* Prints an exception to stderr.
*
* @param \Exception $exception The exception to handle
* @return void
*/
protected function _displayException($exception)
{
$errorName = 'Exception:';
if ($exception instanceof FatalErrorException) {
$errorName = 'Fatal Error:';
}
if ($exception instanceof PHP7ErrorException) {
$exception = $exception->getError();
}
$message = sprintf(
'<error>%s</error> %s in [%s, line %s]',
$errorName,
$exception->getMessage(),
$exception->getFile(),
$exception->getLine()
);
$this->_stderr->write($message);
}
/**
* Prints an error to stderr.
*
* Template method of BaseErrorHandler.
*
* @param array $error An array of error data.
* @param bool $debug Whether or not the app is in debug mode.
* @return void
*/
protected function _displayError($error, $debug)
{
$message = sprintf(
'%s in [%s, line %s]',
$error['description'],
$error['file'],
$error['line']
);
$message = sprintf(
"<error>%s Error:</error> %s\n",
$error['error'],
$message
);
$this->_stderr->write($message);
}
/**
* Stop the execution and set the exit code for the process.
*
* @param int $code The exit code.
* @return void
*/
protected function _stop($code)
{
exit($code);
}
}
+85
View File
@@ -0,0 +1,85 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
/**
* Object wrapper for interacting with stdin
*/
class ConsoleInput
{
/**
* Input value.
*
* @var resource
*/
protected $_input;
/**
* Can this instance use readline?
* Two conditions must be met:
* 1. Readline support must be enabled.
* 2. Handle we are attached to must be stdin.
* Allows rich editing with arrow keys and history when inputting a string.
*
* @var bool
*/
protected $_canReadline;
/**
* Constructor
*
* @param string $handle The location of the stream to use as input.
*/
public function __construct($handle = 'php://stdin')
{
$this->_canReadline = (extension_loaded('readline') && $handle === 'php://stdin');
$this->_input = fopen($handle, 'rb');
}
/**
* Read a value from the stream
*
* @return string|false The value of the stream
*/
public function read()
{
if ($this->_canReadline) {
$line = readline('');
if (strlen($line) > 0) {
readline_add_history($line);
}
return $line;
}
return fgets($this->_input);
}
/**
* Check if data is available on stdin
*
* @param int $timeout An optional time to wait for data
* @return bool True for data available, false otherwise
*/
public function dataAvailable($timeout = 0)
{
$readFds = [$this->_input];
$writeFds = null;
$errorFds = null;
$readyFds = stream_select($readFds, $writeFds, $errorFds, $timeout);
return $readyFds > 0;
}
}
@@ -0,0 +1,196 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
use Cake\Console\Exception\ConsoleException;
use SimpleXMLElement;
/**
* An object to represent a single argument used in the command line.
* ConsoleOptionParser creates these when you use addArgument()
*
* @see \Cake\Console\ConsoleOptionParser::addArgument()
*/
class ConsoleInputArgument
{
/**
* Name of the argument.
*
* @var string
*/
protected $_name;
/**
* Help string
*
* @var string
*/
protected $_help;
/**
* Is this option required?
*
* @var bool
*/
protected $_required;
/**
* An array of valid choices for this argument.
*
* @var string[]
*/
protected $_choices;
/**
* Make a new Input Argument
*
* @param string|array $name The long name of the option, or an array with all the properties.
* @param string $help The help text for this option
* @param bool $required Whether this argument is required. Missing required args will trigger exceptions
* @param string[] $choices Valid choices for this option.
*/
public function __construct($name, $help = '', $required = false, $choices = [])
{
if (is_array($name) && isset($name['name'])) {
foreach ($name as $key => $value) {
$this->{'_' . $key} = $value;
}
} else {
$this->_name = $name;
$this->_help = $help;
$this->_required = $required;
$this->_choices = $choices;
}
}
/**
* Get the value of the name attribute.
*
* @return string Value of this->_name.
*/
public function name()
{
return $this->_name;
}
/**
* Checks if this argument is equal to another argument.
*
* @param \Cake\Console\ConsoleInputArgument $argument ConsoleInputArgument to compare to.
* @return bool
*/
public function isEqualTo(ConsoleInputArgument $argument)
{
return $this->usage() === $argument->usage();
}
/**
* Generate the help for this argument.
*
* @param int $width The width to make the name of the option.
* @return string
*/
public function help($width = 0)
{
$name = $this->_name;
if (strlen($name) < $width) {
$name = str_pad($name, $width, ' ');
}
$optional = '';
if (!$this->isRequired()) {
$optional = ' <comment>(optional)</comment>';
}
if ($this->_choices) {
$optional .= sprintf(' <comment>(choices: %s)</comment>', implode('|', $this->_choices));
}
return sprintf('%s%s%s', $name, $this->_help, $optional);
}
/**
* Get the usage value for this argument
*
* @return string
*/
public function usage()
{
$name = $this->_name;
if ($this->_choices) {
$name = implode('|', $this->_choices);
}
$name = '<' . $name . '>';
if (!$this->isRequired()) {
$name = '[' . $name . ']';
}
return $name;
}
/**
* Check if this argument is a required argument
*
* @return bool
*/
public function isRequired()
{
return (bool)$this->_required;
}
/**
* Check that $value is a valid choice for this argument.
*
* @param string $value The choice to validate.
* @return true
* @throws \Cake\Console\Exception\ConsoleException
*/
public function validChoice($value)
{
if (empty($this->_choices)) {
return true;
}
if (!in_array($value, $this->_choices, true)) {
throw new ConsoleException(
sprintf(
'"%s" is not a valid value for %s. Please use one of "%s"',
$value,
$this->_name,
implode(', ', $this->_choices)
)
);
}
return true;
}
/**
* Append this arguments XML representation to the passed in SimpleXml object.
*
* @param \SimpleXMLElement $parent The parent element.
* @return \SimpleXMLElement The parent with this argument appended.
*/
public function xml(SimpleXMLElement $parent)
{
$option = $parent->addChild('argument');
$option->addAttribute('name', $this->_name);
$option->addAttribute('help', $this->_help);
$option->addAttribute('required', (int)$this->isRequired());
$choices = $option->addChild('choices');
foreach ($this->_choices as $valid) {
$choices->addChild('choice', $valid);
}
return $parent;
}
}
@@ -0,0 +1,264 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
use Cake\Console\Exception\ConsoleException;
use SimpleXMLElement;
/**
* An object to represent a single option used in the command line.
* ConsoleOptionParser creates these when you use addOption()
*
* @see \Cake\Console\ConsoleOptionParser::addOption()
*/
class ConsoleInputOption
{
/**
* Name of the option
*
* @var string
*/
protected $_name;
/**
* Short (1 character) alias for the option.
*
* @var string
*/
protected $_short;
/**
* Help text for the option.
*
* @var string
*/
protected $_help;
/**
* Is the option a boolean option. Boolean options do not consume a parameter.
*
* @var bool
*/
protected $_boolean;
/**
* Default value for the option
*
* @var string|bool
*/
protected $_default;
/**
* Can the option accept multiple value definition.
*
* @var bool
*/
protected $_multiple;
/**
* An array of choices for the option.
*
* @var string[]
*/
protected $_choices;
/**
* Make a new Input Option
*
* @param string|array $name The long name of the option, or an array with all the properties.
* @param string $short The short alias for this option
* @param string $help The help text for this option
* @param bool $boolean Whether this option is a boolean option. Boolean options don't consume extra tokens
* @param string|bool $default The default value for this option.
* @param string[] $choices Valid choices for this option.
* @param bool $multiple Whether this option can accept multiple value definition.
* @throws \Cake\Console\Exception\ConsoleException
*/
public function __construct(
$name,
$short = '',
$help = '',
$boolean = false,
$default = '',
$choices = [],
$multiple = false
) {
if (is_array($name) && isset($name['name'])) {
foreach ($name as $key => $value) {
$this->{'_' . $key} = $value;
}
} else {
$this->_name = $name;
$this->_short = $short;
$this->_help = $help;
$this->_boolean = $boolean;
$this->_default = $default;
$this->_choices = $choices;
$this->_multiple = $multiple;
}
if (strlen($this->_short) > 1) {
throw new ConsoleException(
sprintf('Short option "%s" is invalid, short options must be one letter.', $this->_short)
);
}
}
/**
* Get the value of the name attribute.
*
* @return string Value of this->_name.
*/
public function name()
{
return $this->_name;
}
/**
* Get the value of the short attribute.
*
* @return string Value of this->_short.
*/
public function short()
{
return $this->_short;
}
/**
* Generate the help for this this option.
*
* @param int $width The width to make the name of the option.
* @return string
*/
public function help($width = 0)
{
$default = $short = '';
if ($this->_default && $this->_default !== true) {
$default = sprintf(' <comment>(default: %s)</comment>', $this->_default);
}
if ($this->_choices) {
$default .= sprintf(' <comment>(choices: %s)</comment>', implode('|', $this->_choices));
}
if (strlen($this->_short) > 0) {
$short = ', -' . $this->_short;
}
$name = sprintf('--%s%s', $this->_name, $short);
if (strlen($name) < $width) {
$name = str_pad($name, $width, ' ');
}
return sprintf('%s%s%s', $name, $this->_help, $default);
}
/**
* Get the usage value for this option
*
* @return string
*/
public function usage()
{
$name = (strlen($this->_short) > 0) ? ('-' . $this->_short) : ('--' . $this->_name);
$default = '';
if (strlen($this->_default) > 0 && $this->_default !== true) {
$default = ' ' . $this->_default;
}
if ($this->_choices) {
$default = ' ' . implode('|', $this->_choices);
}
return sprintf('[%s%s]', $name, $default);
}
/**
* Get the default value for this option
*
* @return string|bool
*/
public function defaultValue()
{
return $this->_default;
}
/**
* Check if this option is a boolean option
*
* @return bool
*/
public function isBoolean()
{
return (bool)$this->_boolean;
}
/**
* Check if this option accepts multiple values.
*
* @return bool
*/
public function acceptsMultiple()
{
return (bool)$this->_multiple;
}
/**
* Check that a value is a valid choice for this option.
*
* @param string|bool $value The choice to validate.
* @return true
* @throws \Cake\Console\Exception\ConsoleException
*/
public function validChoice($value)
{
if (empty($this->_choices)) {
return true;
}
if (!in_array($value, $this->_choices, true)) {
throw new ConsoleException(
sprintf(
'"%s" is not a valid value for --%s. Please use one of "%s"',
$value,
$this->_name,
implode(', ', $this->_choices)
)
);
}
return true;
}
/**
* Append the option's xml into the parent.
*
* @param \SimpleXMLElement $parent The parent element.
* @return \SimpleXMLElement The parent with this option appended.
*/
public function xml(SimpleXMLElement $parent)
{
$option = $parent->addChild('option');
$option->addAttribute('name', '--' . $this->_name);
$short = '';
if (strlen($this->_short) > 0) {
$short = '-' . $this->_short;
}
$option->addAttribute('short', $short);
$option->addAttribute('help', $this->_help);
$option->addAttribute('boolean', (int)$this->_boolean);
$option->addChild('default', $this->_default);
$choices = $option->addChild('choices');
foreach ($this->_choices as $valid) {
$choices->addChild('choice', $valid);
}
return $parent;
}
}
@@ -0,0 +1,139 @@
<?php
/**
* ConsoleInputSubcommand file
*
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
use SimpleXMLElement;
/**
* An object to represent a single subcommand used in the command line.
* Created when you call ConsoleOptionParser::addSubcommand()
*
* @see \Cake\Console\ConsoleOptionParser::addSubcommand()
*/
class ConsoleInputSubcommand
{
/**
* Name of the subcommand
*
* @var string
*/
protected $_name = '';
/**
* Help string for the subcommand
*
* @var string
*/
protected $_help = '';
/**
* The ConsoleOptionParser for this subcommand.
*
* @var \Cake\Console\ConsoleOptionParser
*/
protected $_parser;
/**
* Make a new Subcommand
*
* @param string|array $name The long name of the subcommand, or an array with all the properties.
* @param string $help The help text for this option.
* @param \Cake\Console\ConsoleOptionParser|array|null $parser A parser for this subcommand. Either a ConsoleOptionParser, or an
* array that can be used with ConsoleOptionParser::buildFromArray().
*/
public function __construct($name, $help = '', $parser = null)
{
if (is_array($name) && isset($name['name'])) {
foreach ($name as $key => $value) {
$this->{'_' . $key} = $value;
}
} else {
$this->_name = $name;
$this->_help = $help;
$this->_parser = $parser;
}
if (is_array($this->_parser)) {
$this->_parser['command'] = $this->_name;
$this->_parser = ConsoleOptionParser::buildFromArray($this->_parser);
}
}
/**
* Get the value of the name attribute.
*
* @return string Value of this->_name.
*/
public function name()
{
return $this->_name;
}
/**
* Get the raw help string for this command
*
* @return string
*/
public function getRawHelp()
{
return $this->_help;
}
/**
* Generate the help for this this subcommand.
*
* @param int $width The width to make the name of the subcommand.
* @return string
*/
public function help($width = 0)
{
$name = $this->_name;
if (strlen($name) < $width) {
$name = str_pad($name, $width, ' ');
}
return $name . $this->_help;
}
/**
* Get the usage value for this option
*
* @return \Cake\Console\ConsoleOptionParser|false Either false or a ConsoleOptionParser
*/
public function parser()
{
if ($this->_parser instanceof ConsoleOptionParser) {
return $this->_parser;
}
return false;
}
/**
* Append this subcommand to the Parent element
*
* @param \SimpleXMLElement $parent The parent element.
* @return \SimpleXMLElement The parent with this subcommand appended.
*/
public function xml(SimpleXMLElement $parent)
{
$command = $parent->addChild('command');
$command->addAttribute('name', $this->_name);
$command->addAttribute('help', $this->_help);
return $parent;
}
}
+591
View File
@@ -0,0 +1,591 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
use Cake\Console\Exception\StopException;
use Cake\Log\Engine\ConsoleLog;
use Cake\Log\Log;
use RuntimeException;
use SplFileObject;
/**
* A wrapper around the various IO operations shell tasks need to do.
*
* Packages up the stdout, stderr, and stdin streams providing a simple
* consistent interface for shells to use. This class also makes mocking streams
* easy to do in unit tests.
*/
class ConsoleIo
{
/**
* The output stream
*
* @var \Cake\Console\ConsoleOutput
*/
protected $_out;
/**
* The error stream
*
* @var \Cake\Console\ConsoleOutput
*/
protected $_err;
/**
* The input stream
*
* @var \Cake\Console\ConsoleInput
*/
protected $_in;
/**
* The helper registry.
*
* @var \Cake\Console\HelperRegistry
*/
protected $_helpers;
/**
* Output constant making verbose shells.
*
* @var int
*/
const VERBOSE = 2;
/**
* Output constant for making normal shells.
*
* @var int
*/
const NORMAL = 1;
/**
* Output constants for making quiet shells.
*
* @var int
*/
const QUIET = 0;
/**
* The current output level.
*
* @var int
*/
protected $_level = self::NORMAL;
/**
* The number of bytes last written to the output stream
* used when overwriting the previous message.
*
* @var int
*/
protected $_lastWritten = 0;
/**
* Whether or not files should be overwritten
*
* @var bool
*/
protected $forceOverwrite = false;
/**
* Constructor
*
* @param \Cake\Console\ConsoleOutput|null $out A ConsoleOutput object for stdout.
* @param \Cake\Console\ConsoleOutput|null $err A ConsoleOutput object for stderr.
* @param \Cake\Console\ConsoleInput|null $in A ConsoleInput object for stdin.
* @param \Cake\Console\HelperRegistry|null $helpers A HelperRegistry instance
*/
public function __construct(ConsoleOutput $out = null, ConsoleOutput $err = null, ConsoleInput $in = null, HelperRegistry $helpers = null)
{
$this->_out = $out ?: new ConsoleOutput('php://stdout');
$this->_err = $err ?: new ConsoleOutput('php://stderr');
$this->_in = $in ?: new ConsoleInput('php://stdin');
$this->_helpers = $helpers ?: new HelperRegistry();
$this->_helpers->setIo($this);
}
/**
* Get/set the current output level.
*
* @param int|null $level The current output level.
* @return int The current output level.
*/
public function level($level = null)
{
if ($level !== null) {
$this->_level = $level;
}
return $this->_level;
}
/**
* Output at the verbose level.
*
* @param string|string[] $message A string or an array of strings to output
* @param int $newlines Number of newlines to append
* @return int|bool The number of bytes returned from writing to stdout.
*/
public function verbose($message, $newlines = 1)
{
return $this->out($message, $newlines, self::VERBOSE);
}
/**
* Output at all levels.
*
* @param string|string[] $message A string or an array of strings to output
* @param int $newlines Number of newlines to append
* @return int|bool The number of bytes returned from writing to stdout.
*/
public function quiet($message, $newlines = 1)
{
return $this->out($message, $newlines, self::QUIET);
}
/**
* Outputs a single or multiple messages to stdout. If no parameters
* are passed outputs just a newline.
*
* ### Output levels
*
* There are 3 built-in output level. ConsoleIo::QUIET, ConsoleIo::NORMAL, ConsoleIo::VERBOSE.
* The verbose and quiet output levels, map to the `verbose` and `quiet` output switches
* present in most shells. Using ConsoleIo::QUIET for a message means it will always display.
* While using ConsoleIo::VERBOSE means it will only display when verbose output is toggled.
*
* @param string|string[] $message A string or an array of strings to output
* @param int $newlines Number of newlines to append
* @param int $level The message's output level, see above.
* @return int|bool The number of bytes returned from writing to stdout.
*/
public function out($message = '', $newlines = 1, $level = self::NORMAL)
{
if ($level <= $this->_level) {
$this->_lastWritten = (int)$this->_out->write($message, $newlines);
return $this->_lastWritten;
}
return true;
}
/**
* Convenience method for out() that wraps message between <info /> tag
*
* @param string|string[]|null $message A string or an array of strings to output
* @param int $newlines Number of newlines to append
* @param int $level The message's output level, see above.
* @return int|bool The number of bytes returned from writing to stdout.
* @see https://book.cakephp.org/3/en/console-and-shells.html#ConsoleIo::out
*/
public function info($message = null, $newlines = 1, $level = self::NORMAL)
{
$messageType = 'info';
$message = $this->wrapMessageWithType($messageType, $message);
return $this->out($message, $newlines, $level);
}
/**
* Convenience method for err() that wraps message between <warning /> tag
*
* @param string|string[]|null $message A string or an array of strings to output
* @param int $newlines Number of newlines to append
* @return int|bool The number of bytes returned from writing to stderr.
* @see https://book.cakephp.org/3/en/console-and-shells.html#ConsoleIo::err
*/
public function warning($message = null, $newlines = 1)
{
$messageType = 'warning';
$message = $this->wrapMessageWithType($messageType, $message);
return $this->err($message, $newlines);
}
/**
* Convenience method for err() that wraps message between <error /> tag
*
* @param string|string[]|null $message A string or an array of strings to output
* @param int $newlines Number of newlines to append
* @return int|bool The number of bytes returned from writing to stderr.
* @see https://book.cakephp.org/3/en/console-and-shells.html#ConsoleIo::err
*/
public function error($message = null, $newlines = 1)
{
$messageType = 'error';
$message = $this->wrapMessageWithType($messageType, $message);
return $this->err($message, $newlines);
}
/**
* Convenience method for out() that wraps message between <success /> tag
*
* @param string|string[]|null $message A string or an array of strings to output
* @param int $newlines Number of newlines to append
* @param int $level The message's output level, see above.
* @return int|bool The number of bytes returned from writing to stdout.
* @see https://book.cakephp.org/3/en/console-and-shells.html#ConsoleIo::out
*/
public function success($message = null, $newlines = 1, $level = self::NORMAL)
{
$messageType = 'success';
$message = $this->wrapMessageWithType($messageType, $message);
return $this->out($message, $newlines, $level);
}
/**
* Wraps a message with a given message type, e.g. <warning>
*
* @param string $messageType The message type, e.g. "warning".
* @param string|string[] $message The message to wrap.
* @return string|string[] The message wrapped with the given message type.
*/
protected function wrapMessageWithType($messageType, $message)
{
if (is_array($message)) {
foreach ($message as $k => $v) {
$message[$k] = "<{$messageType}>{$v}</{$messageType}>";
}
} else {
$message = "<{$messageType}>{$message}</{$messageType}>";
}
return $message;
}
/**
* Overwrite some already output text.
*
* Useful for building progress bars, or when you want to replace
* text already output to the screen with new text.
*
* **Warning** You cannot overwrite text that contains newlines.
*
* @param array|string $message The message to output.
* @param int $newlines Number of newlines to append.
* @param int|null $size The number of bytes to overwrite. Defaults to the
* length of the last message output.
* @return void
*/
public function overwrite($message, $newlines = 1, $size = null)
{
$size = $size ?: $this->_lastWritten;
// Output backspaces.
$this->out(str_repeat("\x08", $size), 0);
$newBytes = $this->out($message, 0);
// Fill any remaining bytes with spaces.
$fill = $size - $newBytes;
if ($fill > 0) {
$this->out(str_repeat(' ', $fill), 0);
}
if ($newlines) {
$this->out($this->nl($newlines), 0);
}
// Store length of content + fill so if the new content
// is shorter than the old content the next overwrite
// will work.
if ($fill > 0) {
$this->_lastWritten = $newBytes + $fill;
}
}
/**
* Outputs a single or multiple error messages to stderr. If no parameters
* are passed outputs just a newline.
*
* @param string|string[] $message A string or an array of strings to output
* @param int $newlines Number of newlines to append
* @return int|bool The number of bytes returned from writing to stderr.
*/
public function err($message = '', $newlines = 1)
{
return $this->_err->write($message, $newlines);
}
/**
* Returns a single or multiple linefeeds sequences.
*
* @param int $multiplier Number of times the linefeed sequence should be repeated
* @return string
*/
public function nl($multiplier = 1)
{
return str_repeat(ConsoleOutput::LF, $multiplier);
}
/**
* Outputs a series of minus characters to the standard output, acts as a visual separator.
*
* @param int $newlines Number of newlines to pre- and append
* @param int $width Width of the line, defaults to 79
* @return void
*/
public function hr($newlines = 0, $width = 79)
{
$this->out(null, $newlines);
$this->out(str_repeat('-', $width));
$this->out(null, $newlines);
}
/**
* Prompts the user for input, and returns it.
*
* @param string $prompt Prompt text.
* @param string|null $default Default input value.
* @return string Either the default value, or the user-provided input.
*/
public function ask($prompt, $default = null)
{
return $this->_getInput($prompt, null, $default);
}
/**
* Change the output mode of the stdout stream
*
* @param int $mode The output mode.
* @return void
* @see \Cake\Console\ConsoleOutput::setOutputAs()
*/
public function setOutputAs($mode)
{
$this->_out->setOutputAs($mode);
}
/**
* Change the output mode of the stdout stream
*
* @deprecated 3.5.0 Use setOutputAs() instead.
* @param int $mode The output mode.
* @return void
* @see \Cake\Console\ConsoleOutput::outputAs()
*/
public function outputAs($mode)
{
deprecationWarning('ConsoleIo::outputAs() is deprecated. Use ConsoleIo::setOutputAs() instead.');
$this->_out->setOutputAs($mode);
}
/**
* Add a new output style or get defined styles.
*
* @param string|null $style The style to get or create.
* @param array|false|null $definition The array definition of the style to change or create a style
* or false to remove a style.
* @return array|true|null If you are getting styles, the style or null will be returned. If you are creating/modifying
* styles true will be returned.
* @see \Cake\Console\ConsoleOutput::styles()
*/
public function styles($style = null, $definition = null)
{
return $this->_out->styles($style, $definition);
}
/**
* Prompts the user for input based on a list of options, and returns it.
*
* @param string $prompt Prompt text.
* @param string|array $options Array or string of options.
* @param string|null $default Default input value.
* @return string Either the default value, or the user-provided input.
*/
public function askChoice($prompt, $options, $default = null)
{
if ($options && is_string($options)) {
if (strpos($options, ',')) {
$options = explode(',', $options);
} elseif (strpos($options, '/')) {
$options = explode('/', $options);
} else {
$options = [$options];
}
}
$printOptions = '(' . implode('/', $options) . ')';
$options = array_merge(
array_map('strtolower', $options),
array_map('strtoupper', $options),
$options
);
$in = '';
while ($in === '' || !in_array($in, $options)) {
$in = $this->_getInput($prompt, $printOptions, $default);
}
return $in;
}
/**
* Prompts the user for input, and returns it.
*
* @param string $prompt Prompt text.
* @param string|null $options String of options. Pass null to omit.
* @param string|null $default Default input value. Pass null to omit.
* @return string Either the default value, or the user-provided input.
*/
protected function _getInput($prompt, $options, $default)
{
$optionsText = '';
if (isset($options)) {
$optionsText = " $options ";
}
$defaultText = '';
if ($default !== null) {
$defaultText = "[$default] ";
}
$this->_out->write('<question>' . $prompt . "</question>$optionsText\n$defaultText> ", 0);
$result = $this->_in->read();
$result = trim($result);
if ($default !== null && ($result === '' || $result === null)) {
return $default;
}
return $result;
}
/**
* Connects or disconnects the loggers to the console output.
*
* Used to enable or disable logging stream output to stdout and stderr
* If you don't wish all log output in stdout or stderr
* through Cake's Log class, call this function with `$enable=false`.
*
* @param int|bool $enable Use a boolean to enable/toggle all logging. Use
* one of the verbosity constants (self::VERBOSE, self::QUIET, self::NORMAL)
* to control logging levels. VERBOSE enables debug logs, NORMAL does not include debug logs,
* QUIET disables notice, info and debug logs.
* @return void
*/
public function setLoggers($enable)
{
Log::drop('stdout');
Log::drop('stderr');
if ($enable === false) {
return;
}
$outLevels = ['notice', 'info'];
if ($enable === static::VERBOSE || $enable === true) {
$outLevels[] = 'debug';
}
if ($enable !== static::QUIET) {
$stdout = new ConsoleLog([
'types' => $outLevels,
'stream' => $this->_out,
]);
Log::setConfig('stdout', ['engine' => $stdout]);
}
$stderr = new ConsoleLog([
'types' => ['emergency', 'alert', 'critical', 'error', 'warning'],
'stream' => $this->_err,
]);
Log::setConfig('stderr', ['engine' => $stderr]);
}
/**
* Render a Console Helper
*
* Create and render the output for a helper object. If the helper
* object has not already been loaded, it will be loaded and constructed.
*
* @param string $name The name of the helper to render
* @param array $settings Configuration data for the helper.
* @return \Cake\Console\Helper The created helper instance.
*/
public function helper($name, array $settings = [])
{
$name = ucfirst($name);
return $this->_helpers->load($name, $settings);
}
/**
* Create a file at the given path.
*
* This method will prompt the user if a file will be overwritten.
* Setting `forceOverwrite` to true will suppress this behavior
* and always overwrite the file.
*
* If the user replies `a` subsequent `forceOverwrite` parameters will
* be coerced to true and all files will be overwritten.
*
* @param string $path The path to create the file at.
* @param string $contents The contents to put into the file.
* @param bool $forceOverwrite Whether or not the file should be overwritten.
* If true, no question will be asked about whether or not to overwrite existing files.
* @return bool Success.
* @throws \Cake\Console\Exception\StopException When `q` is given as an answer
* to whether or not a file should be overwritten.
*/
public function createFile($path, $contents, $forceOverwrite = false)
{
$this->out();
$forceOverwrite = $forceOverwrite || $this->forceOverwrite;
if (file_exists($path) && $forceOverwrite === false) {
$this->warning("File `{$path}` exists");
$key = $this->askChoice('Do you want to overwrite?', ['y', 'n', 'a', 'q'], 'n');
$key = strtolower($key);
if ($key === 'q') {
$this->error('Quitting.', 2);
throw new StopException('Not creating file. Quitting.');
}
if ($key === 'a') {
$this->forceOverwrite = true;
$key = 'y';
}
if ($key !== 'y') {
$this->out("Skip `{$path}`", 2);
return false;
}
} else {
$this->out("Creating file {$path}");
}
try {
// Create the directory using the current user permissions.
$directory = dirname($path);
if (!file_exists($directory)) {
mkdir($directory, 0777 ^ umask(), true);
}
$file = new SplFileObject($path, 'w');
} catch (RuntimeException $e) {
$this->error("Could not write to `{$path}`. Permission denied.", 2);
return false;
}
$file->rewind();
if ($file->fwrite($contents) > 0) {
$this->out("<success>Wrote</success> `{$path}`");
return true;
}
$this->error("Could not write to `{$path}`.", 2);
return false;
}
}
File diff suppressed because it is too large. Load diff
+363
View File
@@ -0,0 +1,363 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
use InvalidArgumentException;
/**
* Object wrapper for outputting information from a shell application.
* Can be connected to any stream resource that can be used with fopen()
*
* Can generate colorized output on consoles that support it. There are a few
* built in styles
*
* - `error` Error messages.
* - `warning` Warning messages.
* - `info` Informational messages.
* - `comment` Additional text.
* - `question` Magenta text used for user prompts
*
* By defining styles with addStyle() you can create custom console styles.
*
* ### Using styles in output
*
* You can format console output using tags with the name of the style to apply. From inside a shell object
*
* ```
* $this->out('<warning>Overwrite:</warning> foo.php was overwritten.');
* ```
*
* This would create orange 'Overwrite:' text, while the rest of the text would remain the normal color.
* See ConsoleOutput::styles() to learn more about defining your own styles. Nested styles are not supported
* at this time.
*/
class ConsoleOutput
{
/**
* Raw output constant - no modification of output text.
*
* @var int
*/
const RAW = 0;
/**
* Plain output - tags will be stripped.
*
* @var int
*/
const PLAIN = 1;
/**
* Color output - Convert known tags in to ANSI color escape codes.
*
* @var int
*/
const COLOR = 2;
/**
* Constant for a newline.
*
* @var string
*/
const LF = PHP_EOL;
/**
* File handle for output.
*
* @var resource
*/
protected $_output;
/**
* The current output type.
*
* @see setOutputAs() For manipulation.
* @var int
*/
protected $_outputAs = self::COLOR;
/**
* text colors used in colored output.
*
* @var array
*/
protected static $_foregroundColors = [
'black' => 30,
'red' => 31,
'green' => 32,
'yellow' => 33,
'blue' => 34,
'magenta' => 35,
'cyan' => 36,
'white' => 37,
];
/**
* background colors used in colored output.
*
* @var array
*/
protected static $_backgroundColors = [
'black' => 40,
'red' => 41,
'green' => 42,
'yellow' => 43,
'blue' => 44,
'magenta' => 45,
'cyan' => 46,
'white' => 47,
];
/**
* Formatting options for colored output.
*
* @var array
*/
protected static $_options = [
'bold' => 1,
'underline' => 4,
'blink' => 5,
'reverse' => 7,
];
/**
* Styles that are available as tags in console output.
* You can modify these styles with ConsoleOutput::styles()
*
* @var array
*/
protected static $_styles = [
'emergency' => ['text' => 'red'],
'alert' => ['text' => 'red'],
'critical' => ['text' => 'red'],
'error' => ['text' => 'red'],
'warning' => ['text' => 'yellow'],
'info' => ['text' => 'cyan'],
'debug' => ['text' => 'yellow'],
'success' => ['text' => 'green'],
'comment' => ['text' => 'blue'],
'question' => ['text' => 'magenta'],
'notice' => ['text' => 'cyan'],
];
/**
* Construct the output object.
*
* Checks for a pretty console environment. Ansicon and ConEmu allows
* pretty consoles on windows, and is supported.
*
* @param string $stream The identifier of the stream to write output to.
*/
public function __construct($stream = 'php://stdout')
{
$this->_output = fopen($stream, 'wb');
if (
(DIRECTORY_SEPARATOR === '\\' && !(bool)env('ANSICON') && env('ConEmuANSI') !== 'ON') ||
(function_exists('posix_isatty') && !posix_isatty($this->_output))
) {
$this->_outputAs = self::PLAIN;
}
}
/**
* Outputs a single or multiple messages to stdout or stderr. If no parameters
* are passed, outputs just a newline.
*
* @param string|string[] $message A string or an array of strings to output
* @param int $newlines Number of newlines to append
* @return int|bool The number of bytes returned from writing to output.
*/
public function write($message, $newlines = 1)
{
if (is_array($message)) {
$message = implode(static::LF, $message);
}
return $this->_write($this->styleText($message . str_repeat(static::LF, $newlines)));
}
/**
* Apply styling to text.
*
* @param string $text Text with styling tags.
* @return string String with color codes added.
*/
public function styleText($text)
{
if ($this->_outputAs == static::RAW) {
return $text;
}
if ($this->_outputAs == static::PLAIN) {
$tags = implode('|', array_keys(static::$_styles));
return preg_replace('#</?(?:' . $tags . ')>#', '', $text);
}
return preg_replace_callback(
'/<(?P<tag>[a-z0-9-_]+)>(?P<text>.*?)<\/(\1)>/ims',
[$this, '_replaceTags'],
$text
);
}
/**
* Replace tags with color codes.
*
* @param array $matches An array of matches to replace.
* @return string
*/
protected function _replaceTags($matches)
{
/** @var array $style */
$style = $this->styles($matches['tag']);
if (empty($style)) {
return '<' . $matches['tag'] . '>' . $matches['text'] . '</' . $matches['tag'] . '>';
}
$styleInfo = [];
if (!empty($style['text']) && isset(static::$_foregroundColors[$style['text']])) {
$styleInfo[] = static::$_foregroundColors[$style['text']];
}
if (!empty($style['background']) && isset(static::$_backgroundColors[$style['background']])) {
$styleInfo[] = static::$_backgroundColors[$style['background']];
}
unset($style['text'], $style['background']);
foreach ($style as $option => $value) {
if ($value) {
$styleInfo[] = static::$_options[$option];
}
}
return "\033[" . implode(';', $styleInfo) . 'm' . $matches['text'] . "\033[0m";
}
/**
* Writes a message to the output stream.
*
* @param string $message Message to write.
* @return int|bool The number of bytes returned from writing to output.
*/
protected function _write($message)
{
return fwrite($this->_output, $message);
}
/**
* Get the current styles offered, or append new ones in.
*
* ### Get a style definition
*
* ```
* $output->styles('error');
* ```
*
* ### Get all the style definitions
*
* ```
* $output->styles();
* ```
*
* ### Create or modify an existing style
*
* ```
* $output->styles('annoy', ['text' => 'purple', 'background' => 'yellow', 'blink' => true]);
* ```
*
* ### Remove a style
*
* ```
* $this->output->styles('annoy', false);
* ```
*
* @param string|null $style The style to get or create.
* @param array|false|null $definition The array definition of the style to change or create a style
* or false to remove a style.
* @return array|true|null If you are getting styles, the style or null will be returned. If you are creating/modifying
* styles true will be returned.
*/
public function styles($style = null, $definition = null)
{
if ($style === null && $definition === null) {
return static::$_styles;
}
if (is_string($style) && $definition === null) {
return isset(static::$_styles[$style]) ? static::$_styles[$style] : null;
}
if ($definition === false) {
unset(static::$_styles[$style]);
return true;
}
static::$_styles[$style] = $definition;
return true;
}
/**
* Get the output type on how formatting tags are treated.
*
* @return int
*/
public function getOutputAs()
{
return $this->_outputAs;
}
/**
* Set the output type on how formatting tags are treated.
*
* @param int $type The output type to use. Should be one of the class constants.
* @return void
* @throws \InvalidArgumentException in case of a not supported output type.
*/
public function setOutputAs($type)
{
if (!in_array($type, [self::RAW, self::PLAIN, self::COLOR], true)) {
throw new InvalidArgumentException(sprintf('Invalid output type "%s".', $type));
}
$this->_outputAs = $type;
}
/**
* Get/Set the output type to use. The output type how formatting tags are treated.
*
* @deprecated 3.5.0 Use getOutputAs()/setOutputAs() instead.
* @param int|null $type The output type to use. Should be one of the class constants.
* @return int|null Either null or the value if getting.
*/
public function outputAs($type = null)
{
deprecationWarning(
'ConsoleOutput::outputAs() is deprecated. ' .
'Use ConsoleOutput::setOutputAs()/getOutputAs() instead.'
);
if ($type === null) {
return $this->_outputAs;
}
$this->_outputAs = $type;
}
/**
* Clean up and close handles
*/
public function __destruct()
{
if (is_resource($this->_output)) {
fclose($this->_output);
}
}
}
@@ -0,0 +1,31 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://book.cakephp.org/3/en/development/errors.html#error-exception-configuration
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console\Exception;
use Cake\Console\Command;
use Cake\Core\Exception\Exception;
/**
* Exception class for Console libraries. This exception will be thrown from Console library
* classes when they encounter an error.
*/
class ConsoleException extends Exception
{
/**
* Default exception code
*
* @var int
*/
protected $_defaultCode = Command::CODE_ERROR;
}
@@ -0,0 +1,21 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @since 3.1.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console\Exception;
/**
* Used when a Helper cannot be found.
*/
class MissingHelperException extends ConsoleException
{
protected $_messageTemplate = 'Helper class %s could not be found.';
}
@@ -0,0 +1,21 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console\Exception;
/**
* Used when a shell cannot be found.
*/
class MissingShellException extends ConsoleException
{
protected $_messageTemplate = 'Shell class for "%s" could not be found. If you are trying to use a plugin shell, that was loaded via $this->addPlugin(), you may need to update bin/cake.php to match https://github.com/cakephp/app/tree/master/bin/cake.php';
}
@@ -0,0 +1,21 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console\Exception;
/**
* Used when a shell method cannot be found.
*/
class MissingShellMethodException extends ConsoleException
{
protected $_messageTemplate = "Unknown command %1\$s %2\$s.\nFor usage try `cake %1\$s --help`";
}
@@ -0,0 +1,21 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console\Exception;
/**
* Used when a Task cannot be found.
*/
class MissingTaskException extends ConsoleException
{
protected $_messageTemplate = 'Task class %s could not be found.';
}
@@ -0,0 +1,25 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://book.cakephp.org/3/en/development/errors.html#error-exception-configuration
* @since 3.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console\Exception;
/**
* Exception class for halting errors in console tasks
*
* @see \Cake\Console\Shell::_stop()
* @see \Cake\Console\Shell::error()
* @see \Cake\Console\Command::abort()
*/
class StopException extends ConsoleException
{
}
+238
View File
@@ -0,0 +1,238 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
use Cake\Console\Exception\ConsoleException;
use Cake\Utility\Text;
use SimpleXMLElement;
/**
* HelpFormatter formats help for console shells. Can format to either
* text or XML formats. Uses ConsoleOptionParser methods to generate help.
*
* Generally not directly used. Using $parser->help($command, 'xml'); is usually
* how you would access help. Or via the `--help=xml` option on the command line.
*
* Xml output is useful for integration with other tools like IDE's or other build tools.
*/
class HelpFormatter
{
/**
* The maximum number of arguments shown when generating usage.
*
* @var int
*/
protected $_maxArgs = 6;
/**
* The maximum number of options shown when generating usage.
*
* @var int
*/
protected $_maxOptions = 6;
/**
* Option parser.
*
* @var \Cake\Console\ConsoleOptionParser
*/
protected $_parser;
/**
* Alias to display in the output.
*
* @var string
*/
protected $_alias = 'cake';
/**
* Build the help formatter for an OptionParser
*
* @param \Cake\Console\ConsoleOptionParser $parser The option parser help is being generated for.
*/
public function __construct(ConsoleOptionParser $parser)
{
$this->_parser = $parser;
}
/**
* Set the alias
*
* @param string $alias The alias
* @return void
* @throws \Cake\Console\Exception\ConsoleException When alias is not a string.
*/
public function setAlias($alias)
{
if (is_string($alias)) {
$this->_alias = $alias;
} else {
throw new ConsoleException('Alias must be of type string.');
}
}
/**
* Get the help as formatted text suitable for output on the command line.
*
* @param int $width The width of the help output.
* @return string
*/
public function text($width = 72)
{
$parser = $this->_parser;
$out = [];
$description = $parser->getDescription();
if (!empty($description)) {
$out[] = Text::wrap($description, $width);
$out[] = '';
}
$out[] = '<info>Usage:</info>';
$out[] = $this->_generateUsage();
$out[] = '';
$subcommands = $parser->subcommands();
if (!empty($subcommands)) {
$out[] = '<info>Subcommands:</info>';
$out[] = '';
$max = $this->_getMaxLength($subcommands) + 2;
foreach ($subcommands as $command) {
$out[] = Text::wrapBlock($command->help($max), [
'width' => $width,
'indent' => str_repeat(' ', $max),
'indentAt' => 1,
]);
}
$out[] = '';
$out[] = sprintf('To see help on a subcommand use <info>`' . $this->_alias . ' %s [subcommand] --help`</info>', $parser->getCommand());
$out[] = '';
}
$options = $parser->options();
if (!empty($options)) {
$max = $this->_getMaxLength($options) + 8;
$out[] = '<info>Options:</info>';
$out[] = '';
foreach ($options as $option) {
$out[] = Text::wrapBlock($option->help($max), [
'width' => $width,
'indent' => str_repeat(' ', $max),
'indentAt' => 1,
]);
}
$out[] = '';
}
$arguments = $parser->arguments();
if (!empty($arguments)) {
$max = $this->_getMaxLength($arguments) + 2;
$out[] = '<info>Arguments:</info>';
$out[] = '';
foreach ($arguments as $argument) {
$out[] = Text::wrapBlock($argument->help($max), [
'width' => $width,
'indent' => str_repeat(' ', $max),
'indentAt' => 1,
]);
}
$out[] = '';
}
$epilog = $parser->getEpilog();
if (!empty($epilog)) {
$out[] = Text::wrap($epilog, $width);
$out[] = '';
}
return implode("\n", $out);
}
/**
* Generate the usage for a shell based on its arguments and options.
* Usage strings favor short options over the long ones. and optional args will
* be indicated with []
*
* @return string
*/
protected function _generateUsage()
{
$usage = [$this->_alias . ' ' . $this->_parser->getCommand()];
$subcommands = $this->_parser->subcommands();
if (!empty($subcommands)) {
$usage[] = '[subcommand]';
}
$options = [];
foreach ($this->_parser->options() as $option) {
$options[] = $option->usage();
}
if (count($options) > $this->_maxOptions) {
$options = ['[options]'];
}
$usage = array_merge($usage, $options);
$args = [];
foreach ($this->_parser->arguments() as $argument) {
$args[] = $argument->usage();
}
if (count($args) > $this->_maxArgs) {
$args = ['[arguments]'];
}
$usage = array_merge($usage, $args);
return implode(' ', $usage);
}
/**
* Iterate over a collection and find the longest named thing.
*
* @param array $collection The collection to find a max length of.
* @return int
*/
protected function _getMaxLength($collection)
{
$max = 0;
foreach ($collection as $item) {
$max = (strlen($item->name()) > $max) ? strlen($item->name()) : $max;
}
return $max;
}
/**
* Get the help as an xml string.
*
* @param bool $string Return the SimpleXml object or a string. Defaults to true.
* @return string|\SimpleXMLElement See $string
*/
public function xml($string = true)
{
$parser = $this->_parser;
$xml = new SimpleXMLElement('<shell></shell>');
$xml->addChild('command', $parser->getCommand());
$xml->addChild('description', $parser->getDescription());
$subcommands = $xml->addChild('subcommands');
foreach ($parser->subcommands() as $command) {
$command->xml($subcommands);
}
$options = $xml->addChild('options');
foreach ($parser->options() as $option) {
$option->xml($options);
}
$arguments = $xml->addChild('arguments');
foreach ($parser->arguments() as $argument) {
$argument->xml($arguments);
}
$xml->addChild('epilog', $parser->getEpilog());
return $string ? $xml->asXML() : $xml;
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.1.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
use Cake\Core\InstanceConfigTrait;
/**
* Base class for Helpers.
*
* Console Helpers allow you to package up reusable blocks
* of Console output logic. For example creating tables,
* progress bars or ascii art.
*/
abstract class Helper
{
use InstanceConfigTrait;
/**
* Default config for this helper.
*
* @var array
*/
protected $_defaultConfig = [];
/**
* ConsoleIo instance.
*
* @var \Cake\Console\ConsoleIo
*/
protected $_io;
/**
* Constructor.
*
* @param \Cake\Console\ConsoleIo $io The ConsoleIo instance to use.
* @param array $config The settings for this helper.
*/
public function __construct(ConsoleIo $io, array $config = [])
{
$this->_io = $io;
$this->setConfig($config);
}
/**
* This method should output content using `$this->_io`.
*
* @param array $args The arguments for the helper.
* @return void
*/
abstract public function output($args);
}
+101
View File
@@ -0,0 +1,101 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.1.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
use Cake\Console\Exception\MissingHelperException;
use Cake\Core\App;
use Cake\Core\ObjectRegistry;
/**
* Registry for Helpers. Provides features
* for lazily loading helpers.
*
* @extends \Cake\Core\ObjectRegistry<\Cake\Console\Helper>
*/
class HelperRegistry extends ObjectRegistry
{
/**
* Shell to use to set params to tasks.
*
* @var \Cake\Console\ConsoleIo
*/
protected $_io;
/**
* Sets The IO instance that should be passed to the shell helpers
*
* @param \Cake\Console\ConsoleIo $io An io instance.
* @return void
*/
public function setIo(ConsoleIo $io)
{
$this->_io = $io;
}
/**
* Resolve a helper classname.
*
* Will prefer helpers defined in Command\Helper over those
* defined in Shell\Helper.
*
* Part of the template method for Cake\Core\ObjectRegistry::load()
*
* @param string $class Partial classname to resolve.
* @return string|false Either the correct classname or false.
*/
protected function _resolveClassName($class)
{
$name = App::className($class, 'Command/Helper', 'Helper');
if ($name) {
return $name;
}
return App::className($class, 'Shell/Helper', 'Helper');
}
/**
* Throws an exception when a helper is missing.
*
* Part of the template method for Cake\Core\ObjectRegistry::load()
* and Cake\Core\ObjectRegistry::unload()
*
* @param string $class The classname that is missing.
* @param string $plugin The plugin the helper is missing in.
* @return void
* @throws \Cake\Console\Exception\MissingHelperException
*/
protected function _throwMissingClassError($class, $plugin)
{
throw new MissingHelperException([
'class' => $class,
'plugin' => $plugin,
]);
}
/**
* Create the helper instance.
*
* Part of the template method for Cake\Core\ObjectRegistry::load()
*
* @param string $class The classname to create.
* @param string $alias The alias of the helper.
* @param array $settings An array of settings to use for the helper.
* @return \Cake\Console\Helper The constructed helper class.
*/
protected function _create($class, $alias, $settings)
{
return new $class($this->_io, $settings);
}
}
File diff suppressed because it is too large. Load diff
+415
View File
@@ -0,0 +1,415 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
use Cake\Console\Exception\MissingShellException;
use Cake\Console\Exception\StopException;
use Cake\Core\App;
use Cake\Core\Configure;
use Cake\Core\Exception\Exception;
use Cake\Core\Plugin;
use Cake\Log\Log;
use Cake\Shell\Task\CommandTask;
use Cake\Utility\Inflector;
/**
* Shell dispatcher handles dispatching cli commands.
*
* Consult /bin/cake.php for how this class is used in practice.
*/
class ShellDispatcher
{
/**
* Contains arguments parsed from the command line.
*
* @var array
*/
public $args = [];
/**
* List of connected aliases.
*
* @var array
*/
protected static $_aliases = [];
/**
* Constructor
*
* The execution of the script is stopped after dispatching the request with
* a status code of either 0 or 1 according to the result of the dispatch.
*
* @param array $args the argv from PHP
* @param bool $bootstrap Should the environment be bootstrapped.
*/
public function __construct($args = [], $bootstrap = true)
{
set_time_limit(0);
$this->args = (array)$args;
$this->addShortPluginAliases();
if ($bootstrap) {
$this->_initEnvironment();
}
}
/**
* Add an alias for a shell command.
*
* Aliases allow you to call shells by alternate names. This is most
* useful when dealing with plugin shells that you want to have shorter
* names for.
*
* If you re-use an alias the last alias set will be the one available.
*
* ### Usage
*
* Aliasing a shell named ClassName:
*
* ```
* $this->alias('alias', 'ClassName');
* ```
*
* Getting the original name for a given alias:
*
* ```
* $this->alias('alias');
* ```
*
* @param string $short The new short name for the shell.
* @param string|null $original The original full name for the shell.
* @return string|false The aliased class name, or false if the alias does not exist
*/
public static function alias($short, $original = null)
{
$short = Inflector::camelize($short);
if ($original) {
static::$_aliases[$short] = $original;
}
return isset(static::$_aliases[$short]) ? static::$_aliases[$short] : false;
}
/**
* Clear any aliases that have been set.
*
* @return void
*/
public static function resetAliases()
{
static::$_aliases = [];
}
/**
* Run the dispatcher
*
* @param array $argv The argv from PHP
* @param array $extra Extra parameters
* @return int The exit code of the shell process.
*/
public static function run($argv, $extra = [])
{
$dispatcher = new ShellDispatcher($argv);
return $dispatcher->dispatch($extra);
}
/**
* Defines current working environment.
*
* @return void
* @throws \Cake\Core\Exception\Exception
*/
protected function _initEnvironment()
{
if (!$this->_bootstrap()) {
$message = "Unable to load CakePHP core.\nMake sure Cake exists in " . CAKE_CORE_INCLUDE_PATH;
throw new Exception($message);
}
if (function_exists('ini_set')) {
ini_set('html_errors', '0');
ini_set('implicit_flush', '1');
ini_set('max_execution_time', '0');
}
$this->shiftArgs();
}
/**
* Initializes the environment and loads the CakePHP core.
*
* @return bool Success.
*/
protected function _bootstrap()
{
if (!Configure::read('App.fullBaseUrl')) {
Configure::write('App.fullBaseUrl', 'http://localhost');
}
return true;
}
/**
* Dispatches a CLI request
*
* Converts a shell command result into an exit code. Null/True
* are treated as success. All other return values are an error.
*
* @param array $extra Extra parameters that you can manually pass to the Shell
* to be dispatched.
* Built-in extra parameter is :
* - `requested` : if used, will prevent the Shell welcome message to be displayed
* @return int The cli command exit code. 0 is success.
*/
public function dispatch($extra = [])
{
try {
$result = $this->_dispatch($extra);
} catch (StopException $e) {
return $e->getCode();
}
if ($result === null || $result === true) {
return Shell::CODE_SUCCESS;
}
if (is_int($result)) {
return $result;
}
return Shell::CODE_ERROR;
}
/**
* Dispatch a request.
*
* @param array $extra Extra parameters that you can manually pass to the Shell
* to be dispatched.
* Built-in extra parameter is :
* - `requested` : if used, will prevent the Shell welcome message to be displayed
* @return bool|int|null
* @throws \Cake\Console\Exception\MissingShellMethodException
*/
protected function _dispatch($extra = [])
{
$shell = $this->shiftArgs();
if (!$shell) {
$this->help();
return false;
}
if (in_array($shell, ['help', '--help', '-h'])) {
$this->help();
return true;
}
if (in_array($shell, ['version', '--version'])) {
$this->version();
return true;
}
$Shell = $this->findShell($shell);
$Shell->initialize();
return $Shell->runCommand($this->args, true, $extra);
}
/**
* For all loaded plugins, add a short alias
*
* This permits a plugin which implements a shell of the same name to be accessed
* Using the shell name alone
*
* @return array the resultant list of aliases
*/
public function addShortPluginAliases()
{
$plugins = Plugin::loaded();
$io = new ConsoleIo();
$task = new CommandTask($io);
$io->setLoggers(false);
$list = $task->getShellList() + ['app' => []];
$fixed = array_flip($list['app']) + array_flip($list['CORE']);
$aliases = $others = [];
foreach ($plugins as $plugin) {
if (!isset($list[$plugin])) {
continue;
}
foreach ($list[$plugin] as $shell) {
$aliases += [$shell => $plugin];
if (!isset($others[$shell])) {
$others[$shell] = [$plugin];
} else {
$others[$shell] = array_merge($others[$shell], [$plugin]);
}
}
}
foreach ($aliases as $shell => $plugin) {
if (isset($fixed[$shell])) {
Log::write(
'debug',
"command '$shell' in plugin '$plugin' was not aliased, conflicts with another shell",
['shell-dispatcher']
);
continue;
}
$other = static::alias($shell);
if ($other) {
$other = $aliases[$shell];
if ($other !== $plugin) {
Log::write(
'debug',
"command '$shell' in plugin '$plugin' was not aliased, conflicts with '$other'",
['shell-dispatcher']
);
}
continue;
}
if (isset($others[$shell])) {
$conflicts = array_diff($others[$shell], [$plugin]);
if (count($conflicts) > 0) {
$conflictList = implode("', '", $conflicts);
Log::write(
'debug',
"command '$shell' in plugin '$plugin' was not aliased, conflicts with '$conflictList'",
['shell-dispatcher']
);
}
}
static::alias($shell, "$plugin.$shell");
}
return static::$_aliases;
}
/**
* Get shell to use, either plugin shell or application shell
*
* All paths in the loaded shell paths are searched, handles alias
* dereferencing
*
* @param string $shell Optionally the name of a plugin
* @return \Cake\Console\Shell A shell instance.
* @throws \Cake\Console\Exception\MissingShellException when errors are encountered.
*/
public function findShell($shell)
{
$className = $this->_shellExists($shell);
if (!$className) {
$shell = $this->_handleAlias($shell);
$className = $this->_shellExists($shell);
}
if (!$className) {
throw new MissingShellException([
'class' => $shell,
]);
}
return $this->_createShell($className, $shell);
}
/**
* If the input matches an alias, return the aliased shell name
*
* @param string $shell Optionally the name of a plugin or alias
* @return string Shell name with plugin prefix
*/
protected function _handleAlias($shell)
{
$aliased = static::alias($shell);
if ($aliased) {
$shell = $aliased;
}
$class = array_map('Cake\Utility\Inflector::camelize', explode('.', $shell));
return implode('.', $class);
}
/**
* Check if a shell class exists for the given name.
*
* @param string $shell The shell name to look for.
* @return string|false Either the classname or false.
*/
protected function _shellExists($shell)
{
$class = App::className($shell, 'Shell', 'Shell');
if (class_exists($class)) {
return $class;
}
return false;
}
/**
* Create the given shell name, and set the plugin property
*
* @param string $className The class name to instantiate
* @param string $shortName The plugin-prefixed shell name
* @return \Cake\Console\Shell A shell instance.
*/
protected function _createShell($className, $shortName)
{
list($plugin) = pluginSplit($shortName);
$instance = new $className();
$instance->plugin = trim($plugin, '.');
return $instance;
}
/**
* Removes first argument and shifts other arguments up
*
* @return mixed Null if there are no arguments otherwise the shifted argument
*/
public function shiftArgs()
{
return array_shift($this->args);
}
/**
* Shows console help. Performs an internal dispatch to the CommandList Shell
*
* @return void
*/
public function help()
{
$this->args = array_merge(['command_list'], $this->args);
$this->dispatch();
}
/**
* Prints the currently installed version of CakePHP. Performs an internal dispatch to the CommandList Shell
*
* @return void
*/
public function version()
{
$this->args = array_merge(['command_list', '--version'], $this->args);
$this->dispatch();
}
}
+92
View File
@@ -0,0 +1,92 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
use Cake\Console\Exception\MissingTaskException;
use Cake\Core\App;
use Cake\Core\ObjectRegistry;
/**
* Registry for Tasks. Provides features
* for lazily loading tasks.
*
* @extends \Cake\Core\ObjectRegistry<\Cake\Console\Shell>
*/
class TaskRegistry extends ObjectRegistry
{
/**
* Shell to use to set params to tasks.
*
* @var \Cake\Console\Shell
*/
protected $_Shell;
/**
* Constructor
*
* @param \Cake\Console\Shell $Shell Shell instance
*/
public function __construct(Shell $Shell)
{
$this->_Shell = $Shell;
}
/**
* Resolve a task classname.
*
* Part of the template method for Cake\Core\ObjectRegistry::load()
*
* @param string $class Partial classname to resolve.
* @return string|false Either the correct classname or false.
*/
protected function _resolveClassName($class)
{
return App::className($class, 'Shell/Task', 'Task');
}
/**
* Throws an exception when a task is missing.
*
* Part of the template method for Cake\Core\ObjectRegistry::load()
* and Cake\Core\ObjectRegistry::unload()
*
* @param string $class The classname that is missing.
* @param string $plugin The plugin the task is missing in.
* @return void
* @throws \Cake\Console\Exception\MissingTaskException
*/
protected function _throwMissingClassError($class, $plugin)
{
throw new MissingTaskException([
'class' => $class,
'plugin' => $plugin,
]);
}
/**
* Create the task instance.
*
* Part of the template method for Cake\Core\ObjectRegistry::load()
*
* @param string $class The classname to create.
* @param string $alias The alias of the task.
* @param array $settings An array of settings to use for the task.
* @return \Cake\Console\Shell The constructed task class.
*/
protected function _create($class, $alias, $settings)
{
return new $class($this->_Shell->getIo());
}
}
+222
View File
@@ -0,0 +1,222 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 1.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Controller;
use Cake\Core\InstanceConfigTrait;
use Cake\Event\EventListenerInterface;
use Cake\Log\LogTrait;
/**
* Base class for an individual Component. Components provide reusable bits of
* controller logic that can be composed into a controller. Components also
* provide request life-cycle callbacks for injecting logic at specific points.
*
* ### Initialize hook
*
* Like Controller and Table, this class has an initialize() hook that you can use
* to add custom 'constructor' logic. It is important to remember that each request
* (and sub-request) will only make one instance of any given component.
*
* ### Life cycle callbacks
*
* Components can provide several callbacks that are fired at various stages of the request
* cycle. The available callbacks are:
*
* - `beforeFilter(Event $event)`
* Called before the controller's beforeFilter method by default.
* - `startup(Event $event)`
* Called after the controller's beforeFilter method, and before the
* controller action is called.
* - `beforeRender(Event $event)`
* Called before the Controller beforeRender, and before the view class is loaded.
* - `shutdown(Event $event)`
* Called after the action is complete and the view has been rendered but
* before Controller::afterFilter().
* - `beforeRedirect(Event $event $url, Response $response)`
* Called before a redirect is done. Allows you to change the URL that will
* be redirected to by returning a Response instance with new URL set using
* Response::location(). Redirection can be prevented by stopping the event
* propagation.
*
* While the controller is not an explicit argument for the callback methods it
* is the subject of each event and can be fetched using Event::getSubject().
*
* @link https://book.cakephp.org/3/en/controllers/components.html
* @see \Cake\Controller\Controller::$components
*/
class Component implements EventListenerInterface
{
use InstanceConfigTrait;
use LogTrait;
/**
* Request object
*
* @var \Cake\Http\ServerRequest
* @deprecated 3.4.0 Storing references to the request is deprecated. Use Component::getController()
* or callback $event->getSubject() to access the controller & request instead.
*/
public $request;
/**
* Response object
*
* @var \Cake\Http\Response
* @deprecated 3.4.0 Storing references to the response is deprecated. Use Component::getController()
* or callback $event->getSubject() to access the controller & response instead.
*/
public $response;
/**
* Component registry class used to lazy load components.
*
* @var \Cake\Controller\ComponentRegistry
*/
protected $_registry;
/**
* Other Components this component uses.
*
* @var array
*/
public $components = [];
/**
* Default config
*
* These are merged with user-provided config when the component is used.
*
* @var array
*/
protected $_defaultConfig = [];
/**
* A component lookup table used to lazy load component objects.
*
* @var array
*/
protected $_componentMap = [];
/**
* Constructor
*
* @param \Cake\Controller\ComponentRegistry $registry A ComponentRegistry this component can use to lazy load its components
* @param array $config Array of configuration settings.
*/
public function __construct(ComponentRegistry $registry, array $config = [])
{
$this->_registry = $registry;
$controller = $registry->getController();
if ($controller) {
$this->request =& $controller->request;
$this->response =& $controller->response;
}
$this->setConfig($config);
if ($this->components) {
$this->_componentMap = $registry->normalizeArray($this->components);
}
$this->initialize($config);
}
/**
* Get the controller this component is bound to.
*
* @return \Cake\Controller\Controller The bound controller.
*/
public function getController()
{
return $this->_registry->getController();
}
/**
* Constructor hook method.
*
* Implement this method to avoid having to overwrite
* the constructor and call parent.
*
* @param array $config The configuration settings provided to this component.
* @return void
*/
public function initialize(array $config)
{
}
/**
* Magic method for lazy loading $components.
*
* @param string $name Name of component to get.
* @return \Cake\Controller\Component|null A Component object or null.
*/
public function __get($name)
{
if (isset($this->_componentMap[$name]) && !isset($this->{$name})) {
$config = (array)$this->_componentMap[$name]['config'] + ['enabled' => false];
$this->{$name} = $this->_registry->load($this->_componentMap[$name]['class'], $config);
}
if (!isset($this->{$name})) {
return null;
}
return $this->{$name};
}
/**
* Get the Controller callbacks this Component is interested in.
*
* Uses Conventions to map controller events to standard component
* callback method names. By defining one of the callback methods a
* component is assumed to be interested in the related event.
*
* Override this method if you need to add non-conventional event listeners.
* Or if you want components to listen to non-standard events.
*
* @return array
*/
public function implementedEvents()
{
$eventMap = [
'Controller.initialize' => 'beforeFilter',
'Controller.startup' => 'startup',
'Controller.beforeRender' => 'beforeRender',
'Controller.beforeRedirect' => 'beforeRedirect',
'Controller.shutdown' => 'shutdown',
];
$events = [];
foreach ($eventMap as $event => $method) {
if (method_exists($this, $method)) {
$events[$event] = $method;
}
}
return $events;
}
/**
* Returns an array that can be used to describe the internal state of this
* object.
*
* @return array
*/
public function __debugInfo()
{
return [
'components' => $this->components,
'implementedEvents' => $this->implementedEvents(),
'_config' => $this->getConfig(),
];
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,365 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 1.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Controller\Component;
use Cake\Controller\Component;
use Cake\Http\Cookie\Cookie;
use Cake\Http\ServerRequestFactory;
use Cake\I18n\Time;
use Cake\Utility\CookieCryptTrait;
use Cake\Utility\Hash;
use Cake\Utility\Security;
/**
* Cookie Component.
*
* Provides enhanced cookie handling features for use in the controller layer.
* In addition to the basic features offered be Cake\Http\Response, this class lets you:
*
* - Create and read encrypted cookies.
* - Store non-scalar data.
* - Use hash compatible syntax to read/write/delete values.
*
* @link https://book.cakephp.org/3/en/controllers/components/cookie.html
* @deprecated 3.5.0 Use Cake\Http\Middleware\EncryptedCookieMiddleware and Cake\Http\Cookie\Cookie methods instead.
*/
class CookieComponent extends Component
{
use CookieCryptTrait;
/**
* Default config
*
* - `expires` - How long the cookies should last for. Defaults to 1 month.
* - `path` - The path on the server in which the cookie will be available on.
* If path is set to '/foo/', the cookie will only be available within the
* /foo/ directory and all sub-directories such as /foo/bar/ of domain.
* The default value is base path of app. For e.g. if your app is running
* under a subfolder "cakeapp" of document root the path would be "/cakeapp/"
* else it would be "/".
* - `domain` - The domain that the cookie is available. To make the cookie
* available on all subdomains of example.com set domain to '.example.com'.
* - `secure` - Indicates that the cookie should only be transmitted over a
* secure HTTPS connection. When set to true, the cookie will only be set if
* a secure connection exists.
* - `key` - Encryption key used when encrypted cookies are enabled. Defaults to Security.salt.
* - `httpOnly` - Set to true to make HTTP only cookies. Cookies that are HTTP only
* are not accessible in JavaScript. Default false.
* - `encryption` - Type of encryption to use. Defaults to 'aes'.
*
* @var array
*/
protected $_defaultConfig = [
'path' => null,
'domain' => '',
'secure' => false,
'key' => null,
'httpOnly' => false,
'encryption' => 'aes',
'expires' => '+1 month',
];
/**
* Config specific to a given top level key name.
*
* The values in this array are merged with the general config
* to generate the configuration for a given top level cookie name.
*
* @var array
*/
protected $_keyConfig = [];
/**
* Values stored in the cookie.
*
* Accessed in the controller using $this->Cookie->read('Name.key');
*
* @var array
*/
protected $_values = [];
/**
* A map of keys that have been loaded.
*
* Since CookieComponent lazily reads cookie data,
* we need to track which cookies have been read to account for
* read, delete, read patterns.
*
* @var array
*/
protected $_loaded = [];
/**
* A reference to the Controller's Cake\Http\Response object.
* Currently unused.
*
* @var \Cake\Http\Response|null
* @deprecated 3.4.0 Will be removed in 4.0.0
*/
protected $_response;
/**
* Initialize config data and properties.
*
* @param array $config The config data.
* @return void
*/
public function initialize(array $config)
{
if (!$this->_config['key']) {
$this->setConfig('key', Security::getSalt());
}
$controller = $this->_registry->getController();
if ($controller === null) {
$this->request = ServerRequestFactory::fromGlobals();
}
if (empty($this->_config['path'])) {
$this->setConfig('path', $this->getController()->getRequest()->getAttribute('webroot'));
}
}
/**
* Set the configuration for a specific top level key.
*
* ### Examples:
*
* Set a single config option for a key:
*
* ```
* $this->Cookie->configKey('User', 'expires', '+3 months');
* ```
*
* Set multiple options:
*
* ```
* $this->Cookie->configKey('User', [
* 'expires', '+3 months',
* 'httpOnly' => true,
* ]);
* ```
*
* @param string $keyname The top level keyname to configure.
* @param array|string|null $option Either the option name to set, or an array of options to set,
* or null to read config options for a given key.
* @param string|null $value Either the value to set, or empty when $option is an array.
* @return array|null
*/
public function configKey($keyname, $option = null, $value = null)
{
if ($option === null) {
$default = $this->_config;
$local = isset($this->_keyConfig[$keyname]) ? $this->_keyConfig[$keyname] : [];
return $local + $default;
}
if (!is_array($option)) {
$option = [$option => $value];
}
$this->_keyConfig[$keyname] = $option;
return null;
}
/**
* Events supported by this component.
*
* @return array
*/
public function implementedEvents()
{
return [];
}
/**
* Write a value to the response cookies.
*
* You must use this method before any output is sent to the browser.
* Failure to do so will result in header already sent errors.
*
* @param string|array $key Key for the value
* @param mixed $value Value
* @return void
*/
public function write($key, $value = null)
{
if (!is_array($key)) {
$key = [$key => $value];
}
$keys = [];
foreach ($key as $name => $value) {
$this->_load($name);
$this->_values = Hash::insert($this->_values, $name, $value);
$parts = explode('.', $name);
$keys[] = $parts[0];
}
foreach ($keys as $name) {
$this->_write($name, $this->_values[$name]);
}
}
/**
* Read the value of key path from request cookies.
*
* This method will also allow you to read cookies that have been written in this
* request, but not yet sent to the client.
*
* @param string|null $key Key of the value to be obtained.
* @return string or null, value for specified key
*/
public function read($key = null)
{
$this->_load($key);
return Hash::get($this->_values, $key);
}
/**
* Load the cookie data from the request and response objects.
*
* Based on the configuration data, cookies will be decrypted. When cookies
* contain array data, that data will be expanded.
*
* @param string|array $key The key to load.
* @return void
*/
protected function _load($key)
{
$parts = explode('.', $key);
$first = array_shift($parts);
if (isset($this->_loaded[$first])) {
return;
}
$cookie = $this->getController()->getRequest()->getCookie($first);
if ($cookie === null) {
return;
}
$config = $this->configKey($first);
$this->_loaded[$first] = true;
$this->_values[$first] = $this->_decrypt($cookie, $config['encryption'], $config['key']);
}
/**
* Returns true if given key is set in the cookie.
*
* @param string|null $key Key to check for
* @return bool True if the key exists
*/
public function check($key = null)
{
if (empty($key)) {
return false;
}
return $this->read($key) !== null;
}
/**
* Delete a cookie value
*
* You must use this method before any output is sent to the browser.
* Failure to do so will result in header already sent errors.
*
* Deleting a top level key will delete all keys nested within that key.
* For example deleting the `User` key, will also delete `User.email`.
*
* @param string $key Key of the value to be deleted
* @return void
*/
public function delete($key)
{
$this->_load($key);
$this->_values = Hash::remove($this->_values, $key);
$parts = explode('.', $key);
$top = $parts[0];
if (isset($this->_values[$top])) {
$this->_write($top, $this->_values[$top]);
} else {
$this->_delete($top);
}
}
/**
* Set cookie
*
* @param string $name Name for cookie
* @param string $value Value for cookie
* @return void
*/
protected function _write($name, $value)
{
$config = $this->configKey($name);
$expires = new Time($config['expires']);
$controller = $this->getController();
$cookie = new Cookie(
$name,
$this->_encrypt($value, $config['encryption'], $config['key']),
$expires,
$config['path'],
$config['domain'],
(bool)$config['secure'],
(bool)$config['httpOnly']
);
$controller->response = $controller->response->withCookie($cookie);
}
/**
* Sets a cookie expire time to remove cookie value.
*
* This is only done once all values in a cookie key have been
* removed with delete.
*
* @param string $name Name of cookie
* @return void
*/
protected function _delete($name)
{
$config = $this->configKey($name);
$expires = new Time('now');
$controller = $this->getController();
$cookie = new Cookie(
$name,
'',
$expires,
$config['path'],
$config['domain'],
(bool)$config['secure'],
(bool)$config['httpOnly']
);
$controller->response = $controller->response->withExpiredCookie($cookie);
}
/**
* Returns the encryption key to be used.
*
* @return string
*/
protected function _getCookieEncryptionKey()
{
return $this->_config['key'];
}
}
@@ -0,0 +1,188 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Controller\Component;
use Cake\Controller\Component;
use Cake\Event\Event;
use Cake\Http\Cookie\Cookie;
use Cake\Http\Exception\InvalidCsrfTokenException;
use Cake\Http\Response;
use Cake\Http\ServerRequest;
use Cake\I18n\Time;
use Cake\Utility\Security;
/**
* Provides CSRF protection & validation.
*
* This component adds a CSRF token to a cookie. The cookie value is compared to
* request data, or the X-CSRF-Token header on each PATCH, POST,
* PUT, or DELETE request.
*
* If the request data is missing or does not match the cookie data,
* an InvalidCsrfTokenException will be raised.
*
* This component integrates with the FormHelper automatically and when
* used together your forms will have CSRF tokens automatically added
* when `$this->Form->create(...)` is used in a view.
*
* @deprecated 3.5.0 Use Cake\Http\Middleware\CsrfProtectionMiddleware instead.
*/
class CsrfComponent extends Component
{
/**
* Default config for the CSRF handling.
*
* - cookieName = The name of the cookie to send.
* - expiry = How long the CSRF token should last. Defaults to browser session.
* - secure = Whether or not the cookie will be set with the Secure flag. Defaults to false.
* - httpOnly = Whether or not the cookie will be set with the HttpOnly flag. Defaults to false.
* - field = The form field to check. Changing this will also require configuring
* FormHelper.
*
* @var array
*/
protected $_defaultConfig = [
'cookieName' => 'csrfToken',
'expiry' => 0,
'secure' => false,
'httpOnly' => false,
'field' => '_csrfToken',
];
/**
* Warn if CsrfComponent is used together with CsrfProtectionMiddleware
*
* @param array $config The config data.
* @return void
*/
public function initialize(array $config)
{
if ($this->getController()->getRequest()->getParam('_csrfToken') !== false) {
deprecationWarning('Loading CsrfComponent while CsrfProtectionMiddleware is active ' .
'will corrupt CSRF data and form submitting will fail.');
}
}
/**
* Startup callback.
*
* Validates the CSRF token for POST data. If
* the request is a GET request, and the cookie value is absent a cookie will be set.
*
* Once a cookie is set it will be copied into request->getParam('_csrfToken')
* so that application and framework code can easily access the csrf token.
*
* RequestAction requests do not get checked, nor will
* they set a cookie should it be missing.
*
* @param \Cake\Event\Event $event Event instance.
* @return void
*/
public function startup(Event $event)
{
/** @var \Cake\Controller\Controller $controller */
$controller = $event->getSubject();
$request = $controller->getRequest();
$response = $controller->getResponse();
$cookieName = $this->_config['cookieName'];
$cookieData = $request->getCookie($cookieName);
if ($cookieData) {
$request = $request->withParam('_csrfToken', $cookieData);
}
if ($request->is('requested')) {
$controller->setRequest($request);
return;
}
if ($request->is('get') && $cookieData === null) {
list($request, $response) = $this->_setCookie($request, $response);
$controller->setResponse($response);
}
if ($request->is(['put', 'post', 'delete', 'patch']) || $request->getData()) {
$this->_validateToken($request);
$request = $request->withoutData($this->_config['field']);
}
$controller->setRequest($request);
}
/**
* Events supported by this component.
*
* @return array
*/
public function implementedEvents()
{
return [
'Controller.startup' => 'startup',
];
}
/**
* Set the cookie in the response.
*
* Also sets the request->params['_csrfToken'] so the newly minted
* token is available in the request data.
*
* @param \Cake\Http\ServerRequest $request The request object.
* @param \Cake\Http\Response $response The response object.
* @return array An array of the modified request, response.
*/
protected function _setCookie(ServerRequest $request, Response $response)
{
$expiry = new Time($this->_config['expiry']);
$value = hash('sha512', Security::randomBytes(16), false);
$request = $request->withParam('_csrfToken', $value);
$cookie = new Cookie(
$this->_config['cookieName'],
$value,
$expiry,
$request->getAttribute('webroot'),
'',
(bool)$this->_config['secure'],
(bool)$this->_config['httpOnly']
);
$response = $response->withCookie($cookie);
return [$request, $response];
}
/**
* Validate the request data against the cookie token.
*
* @param \Cake\Http\ServerRequest $request The request to validate against.
* @throws \Cake\Http\Exception\InvalidCsrfTokenException when the CSRF token is invalid or missing.
* @return void
*/
protected function _validateToken(ServerRequest $request)
{
$cookie = $request->getCookie($this->_config['cookieName']);
$post = $request->getData($this->_config['field']);
$header = $request->getHeaderLine('X-CSRF-Token');
if (!$cookie) {
throw new InvalidCsrfTokenException(__d('cake', 'Missing CSRF token cookie'));
}
if (!Security::constantEquals($post, $cookie) && !Security::constantEquals($header, $cookie)) {
throw new InvalidCsrfTokenException(__d('cake', 'CSRF token mismatch.'));
}
}
}
@@ -0,0 +1,185 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Controller\Component;
use Cake\Controller\Component;
use Cake\Controller\ComponentRegistry;
use Cake\Http\Exception\InternalErrorException;
use Cake\Utility\Inflector;
use Exception;
/**
* The CakePHP FlashComponent provides a way for you to write a flash variable
* to the session from your controllers, to be rendered in a view with the
* FlashHelper.
*
* @method void success(string $message, array $options = []) Set a message using "success" element
* @method void error(string $message, array $options = []) Set a message using "error" element
*/
class FlashComponent extends Component
{
/**
* The Session object instance
*
* @var \Cake\Http\Session
* @deprecated 3.7.5 This property will be removed in 4.0.0 in favor of `getSession()` method.
*/
protected $_session;
/**
* Default configuration
*
* @var array
*/
protected $_defaultConfig = [
'key' => 'flash',
'element' => 'default',
'params' => [],
'clear' => false,
'duplicate' => true,
];
/**
* Constructor
*
* @param \Cake\Controller\ComponentRegistry $registry A ComponentRegistry for this component
* @param array $config Array of config.
*/
public function __construct(ComponentRegistry $registry, array $config = [])
{
parent::__construct($registry, $config);
$this->_session = $registry->getController()->getRequest()->getSession();
}
/**
* Used to set a session variable that can be used to output messages in the view.
* If you make consecutive calls to this method, the messages will stack (if they are
* set with the same flash key)
*
* In your controller: $this->Flash->set('This has been saved');
*
* ### Options:
*
* - `key` The key to set under the session's Flash key
* - `element` The element used to render the flash message. Default to 'default'.
* - `params` An array of variables to make available when using an element
* - `clear` A bool stating if the current stack should be cleared to start a new one
* - `escape` Set to false to allow templates to print out HTML content
*
* @param string|\Exception $message Message to be flashed. If an instance
* of \Exception the exception message will be used and code will be set
* in params.
* @param array $options An array of options
* @return void
*/
public function set($message, array $options = [])
{
$options += (array)$this->getConfig();
if ($message instanceof Exception) {
if (!isset($options['params']['code'])) {
$options['params']['code'] = $message->getCode();
}
$message = $message->getMessage();
}
if (isset($options['escape']) && !isset($options['params']['escape'])) {
$options['params']['escape'] = $options['escape'];
}
list($plugin, $element) = pluginSplit($options['element']);
if ($plugin) {
$options['element'] = $plugin . '.Flash/' . $element;
} else {
$options['element'] = 'Flash/' . $element;
}
$messages = [];
if (!$options['clear']) {
$messages = (array)$this->getSession()->read('Flash.' . $options['key']);
}
if (!$options['duplicate']) {
foreach ($messages as $existingMessage) {
if ($existingMessage['message'] === $message) {
return;
}
}
}
$messages[] = [
'message' => $message,
'key' => $options['key'],
'element' => $options['element'],
'params' => $options['params'],
];
$this->getSession()->write('Flash.' . $options['key'], $messages);
}
/**
* Magic method for verbose flash methods based on element names.
*
* For example: $this->Flash->success('My message') would use the
* success.ctp element under `src/Template/Element/Flash` for rendering the
* flash message.
*
* If you make consecutive calls to this method, the messages will stack (if they are
* set with the same flash key)
*
* Note that the parameter `element` will be always overridden. In order to call a
* specific element from a plugin, you should set the `plugin` option in $args.
*
* For example: `$this->Flash->warning('My message', ['plugin' => 'PluginName'])` would
* use the warning.ctp element under `plugins/PluginName/src/Template/Element/Flash` for
* rendering the flash message.
*
* @param string $name Element name to use.
* @param array $args Parameters to pass when calling `FlashComponent::set()`.
* @return void
* @throws \Cake\Http\Exception\InternalErrorException If missing the flash message.
*/
public function __call($name, $args)
{
$element = Inflector::underscore($name);
if (count($args) < 1) {
throw new InternalErrorException('Flash message missing.');
}
$options = ['element' => $element];
if (!empty($args[1])) {
if (!empty($args[1]['plugin'])) {
$options = ['element' => $args[1]['plugin'] . '.' . $element];
unset($args[1]['plugin']);
}
$options += (array)$args[1];
}
$this->set($args[0], $options);
}
/**
* Returns current session object from a controller request.
*
* @return \Cake\Http\Session
*/
protected function getSession()
{
return $this->getController()->getRequest()->getSession();
}
}
@@ -0,0 +1,348 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Controller\Component;
use Cake\Controller\Component;
use Cake\Controller\ComponentRegistry;
use Cake\Datasource\Exception\PageOutOfBoundsException;
use Cake\Datasource\Paginator;
use Cake\Http\Exception\NotFoundException;
use InvalidArgumentException;
/**
* This component is used to handle automatic model data pagination. The primary way to use this
* component is to call the paginate() method. There is a convenience wrapper on Controller as well.
*
* ### Configuring pagination
*
* You configure pagination when calling paginate(). See that method for more details.
*
* @link https://book.cakephp.org/3/en/controllers/components/pagination.html
* @mixin \Cake\Datasource\Paginator
*/
class PaginatorComponent extends Component
{
/**
* Default pagination settings.
*
* When calling paginate() these settings will be merged with the configuration
* you provide.
*
* - `maxLimit` - The maximum limit users can choose to view. Defaults to 100
* - `limit` - The initial number of items per page. Defaults to 20.
* - `page` - The starting page, defaults to 1.
* - `whitelist` - A list of parameters users are allowed to set using request
* parameters. Modifying this list will allow users to have more influence
* over pagination, be careful with what you permit.
*
* @var array
*/
protected $_defaultConfig = [
'page' => 1,
'limit' => 20,
'maxLimit' => 100,
'whitelist' => ['limit', 'sort', 'page', 'direction'],
];
/**
* Datasource paginator instance.
*
* @var \Cake\Datasource\Paginator
*/
protected $_paginator;
/**
* {@inheritDoc}
*/
public function __construct(ComponentRegistry $registry, array $config = [])
{
if (isset($config['paginator'])) {
if (!$config['paginator'] instanceof Paginator) {
throw new InvalidArgumentException('Paginator must be an instance of ' . Paginator::class);
}
$this->_paginator = $config['paginator'];
unset($config['paginator']);
} else {
$this->_paginator = new Paginator();
}
parent::__construct($registry, $config);
}
/**
* Events supported by this component.
*
* @return array
*/
public function implementedEvents()
{
return [];
}
/**
* Handles automatic pagination of model records.
*
* ### Configuring pagination
*
* When calling `paginate()` you can use the $settings parameter to pass in pagination settings.
* These settings are used to build the queries made and control other pagination settings.
*
* If your settings contain a key with the current table's alias. The data inside that key will be used.
* Otherwise the top level configuration will be used.
*
* ```
* $settings = [
* 'limit' => 20,
* 'maxLimit' => 100
* ];
* $results = $paginator->paginate($table, $settings);
* ```
*
* The above settings will be used to paginate any Table. You can configure Table specific settings by
* keying the settings with the Table alias.
*
* ```
* $settings = [
* 'Articles' => [
* 'limit' => 20,
* 'maxLimit' => 100
* ],
* 'Comments' => [ ... ]
* ];
* $results = $paginator->paginate($table, $settings);
* ```
*
* This would allow you to have different pagination settings for `Articles` and `Comments` tables.
*
* ### Controlling sort fields
*
* By default CakePHP will automatically allow sorting on any column on the table object being
* paginated. Often times you will want to allow sorting on either associated columns or calculated
* fields. In these cases you will need to define a whitelist of all the columns you wish to allow
* sorting on. You can define the whitelist in the `$settings` parameter:
*
* ```
* $settings = [
* 'Articles' => [
* 'finder' => 'custom',
* 'sortWhitelist' => ['title', 'author_id', 'comment_count'],
* ]
* ];
* ```
*
* Passing an empty array as whitelist disallows sorting altogether.
*
* ### Paginating with custom finders
*
* You can paginate with any find type defined on your table using the `finder` option.
*
* ```
* $settings = [
* 'Articles' => [
* 'finder' => 'popular'
* ]
* ];
* $results = $paginator->paginate($table, $settings);
* ```
*
* Would paginate using the `find('popular')` method.
*
* You can also pass an already created instance of a query to this method:
*
* ```
* $query = $this->Articles->find('popular')->matching('Tags', function ($q) {
* return $q->where(['name' => 'CakePHP'])
* });
* $results = $paginator->paginate($query);
* ```
*
* ### Scoping Request parameters
*
* By using request parameter scopes you can paginate multiple queries in the same controller action:
*
* ```
* $articles = $paginator->paginate($articlesQuery, ['scope' => 'articles']);
* $tags = $paginator->paginate($tagsQuery, ['scope' => 'tags']);
* ```
*
* Each of the above queries will use different query string parameter sets
* for pagination data. An example URL paginating both results would be:
*
* ```
* /dashboard?articles[page]=1&tags[page]=2
* ```
*
* @param \Cake\Datasource\RepositoryInterface|\Cake\Datasource\QueryInterface $object The table or query to paginate.
* @param array $settings The settings/configuration used for pagination.
* @return \Cake\Datasource\ResultSetInterface Query results
* @throws \Cake\Http\Exception\NotFoundException
*/
public function paginate($object, array $settings = [])
{
$request = $this->_registry->getController()->getRequest();
try {
$results = $this->_paginator->paginate(
$object,
$request->getQueryParams(),
$settings
);
$this->_setPagingParams();
} catch (PageOutOfBoundsException $e) {
$this->_setPagingParams();
throw new NotFoundException(null, null, $e);
}
return $results;
}
/**
* Merges the various options that Pagination uses.
* Pulls settings together from the following places:
*
* - General pagination settings
* - Model specific settings.
* - Request parameters
*
* The result of this method is the aggregate of all the option sets combined together. You can change
* config value `whitelist` to modify which options/values can be set using request parameters.
*
* @param string $alias Model alias being paginated, if the general settings has a key with this value
* that key's settings will be used for pagination instead of the general ones.
* @param array $settings The settings to merge with the request data.
* @return array Array of merged options.
*/
public function mergeOptions($alias, $settings)
{
$request = $this->_registry->getController()->getRequest();
return $this->_paginator->mergeOptions(
$request->getQueryParams(),
$this->_paginator->getDefaults($alias, $settings)
);
}
/**
* Set paginator instance.
*
* @param \Cake\Datasource\Paginator $paginator Paginator instance.
* @return self
*/
public function setPaginator(Paginator $paginator)
{
$this->_paginator = $paginator;
return $this;
}
/**
* Get paginator instance.
*
* @return \Cake\Datasource\Paginator
*/
public function getPaginator()
{
return $this->_paginator;
}
/**
* Set paging params to request instance.
*
* @return void
*/
protected function _setPagingParams()
{
$controller = $this->getController();
$request = $controller->getRequest();
$paging = $this->_paginator->getPagingParams() + (array)$request->getParam('paging', []);
$controller->setRequest($request->withParam('paging', $paging));
}
/**
* Proxy getting/setting config options to Paginator.
*
* @deprecated 3.5.0 use setConfig()/getConfig() instead.
* @param string|array|null $key The key to get/set, or a complete array of configs.
* @param mixed|null $value The value to set.
* @param bool $merge Whether to recursively merge or overwrite existing config, defaults to true.
* @return mixed Config value being read, or the object itself on write operations.
*/
public function config($key = null, $value = null, $merge = true)
{
deprecationWarning('PaginatorComponent::config() is deprecated. Use getConfig()/setConfig() instead.');
$return = $this->_paginator->config($key, $value, $merge);
if ($return instanceof Paginator) {
$return = $this;
}
return $return;
}
/**
* Proxy setting config options to Paginator.
*
* @param string|array $key The key to set, or a complete array of configs.
* @param mixed|null $value The value to set.
* @param bool $merge Whether to recursively merge or overwrite existing config, defaults to true.
* @return $this
*/
public function setConfig($key, $value = null, $merge = true)
{
$this->_paginator->setConfig($key, $value, $merge);
return $this;
}
/**
* Proxy getting config options to Paginator.
*
* @param string|null $key The key to get or null for the whole config.
* @param mixed $default The return value when the key does not exist.
* @return mixed Config value being read.
*/
public function getConfig($key = null, $default = null)
{
return $this->_paginator->getConfig($key, $default);
}
/**
* Proxy setting config options to Paginator.
*
* @param string|array $key The key to set, or a complete array of configs.
* @param mixed|null $value The value to set.
* @return $this
*/
public function configShallow($key, $value = null)
{
$this->_paginator->configShallow($key, null);
return $this;
}
/**
* Proxy method calls to Paginator.
*
* @param string $method Method name.
* @param array $args Method arguments.
* @return mixed
*/
public function __call($method, $args)
{
return call_user_func_array([$this->_paginator, $method], $args);
}
}
@@ -0,0 +1,797 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 0.10.4
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Controller\Component;
use Cake\Controller\Component;
use Cake\Controller\ComponentRegistry;
use Cake\Controller\Controller;
use Cake\Core\App;
use Cake\Core\Configure;
use Cake\Core\Exception\Exception;
use Cake\Event\Event;
use Cake\Http\Exception\NotFoundException;
use Cake\Http\Response;
use Cake\Routing\Router;
use Cake\Utility\Exception\XmlException;
use Cake\Utility\Inflector;
use Cake\Utility\Xml;
use RuntimeException;
/**
* Request object for handling alternative HTTP requests
*
* Alternative HTTP requests can come from wireless units like mobile phones, palmtop computers,
* and the like. These units have no use for AJAX requests, and this Component can tell how Cake
* should respond to the different needs of a handheld computer and a desktop machine.
*
* @link https://book.cakephp.org/3/en/controllers/components/request-handling.html
*/
class RequestHandlerComponent extends Component
{
/**
* @var bool
* @deprecated 3.4.0 Unused. Will be removed in 4.0.0
*/
public $enabled = true;
/**
* Contains the file extension parsed out by the Router
*
* Deprecated as of 3.7.0. This property will be made protected in 4.0.0.
*
* @var string|null
* @see \Cake\Routing\Router::extensions()
*/
public $ext;
/**
* The template to use when rendering the given content type.
*
* @var string|null
*/
protected $_renderType;
/**
* Default config
*
* These are merged with user-provided config when the component is used.
*
* - `checkHttpCache` - Whether to check for HTTP cache.
* - `viewClassMap` - Mapping between type and view classes. If undefined
* json, xml, and ajax will be mapped. Defining any types will omit the defaults.
* - `inputTypeMap` - A mapping between types and deserializers for request bodies.
* If undefined json & xml will be mapped. Defining any types will omit the defaults.
* - `enableBeforeRedirect` - Set to false to disable the `beforeRedirect` callback. The
* `beforeRedirect` functionality has been deprecated.
*
* @var array
*/
protected $_defaultConfig = [
'checkHttpCache' => true,
'viewClassMap' => [],
'inputTypeMap' => [],
'enableBeforeRedirect' => true,
];
/**
* Set the layout to be used when rendering the AuthComponent's ajaxLogin element.
*
* @var string
* @deprecated 3.3.11 This feature property is not supported and will
* be removed in 4.0.0
*/
public $ajaxLayout;
/**
* Constructor. Parses the accepted content types accepted by the client using HTTP_ACCEPT
*
* @param \Cake\Controller\ComponentRegistry $registry ComponentRegistry object.
* @param array $config Array of config.
*/
public function __construct(ComponentRegistry $registry, array $config = [])
{
$config += [
'viewClassMap' => [
'json' => 'Json',
'xml' => 'Xml',
'ajax' => 'Ajax',
],
'inputTypeMap' => [
'json' => ['json_decode', true],
'xml' => [[$this, 'convertXml']],
],
];
parent::__construct($registry, $config);
}
/**
* Events supported by this component.
*
* @return array
*/
public function implementedEvents()
{
return [
'Controller.startup' => 'startup',
'Controller.beforeRender' => 'beforeRender',
'Controller.beforeRedirect' => 'beforeRedirect',
];
}
/**
* @param array $config The config data.
* @return void
* @deprecated 3.4.0 Unused. To be removed in 4.0.0
*/
public function initialize(array $config)
{
}
/**
* Set the extension based on the accept headers.
* Compares the accepted types and configured extensions.
* If there is one common type, that is assigned as the ext/content type for the response.
* The type with the highest weight will be set. If the highest weight has more
* than one type matching the extensions, the order in which extensions are specified
* determines which type will be set.
*
* If html is one of the preferred types, no content type will be set, this
* is to avoid issues with browsers that prefer HTML and several other content types.
*
* @param \Cake\Http\ServerRequest $request The request instance.
* @param \Cake\Http\Response $response The response instance.
* @return void
*/
protected function _setExtension($request, $response)
{
$accept = $request->parseAccept();
if (empty($accept) || current($accept)[0] === 'text/html') {
return;
}
$accepts = $response->mapType($accept);
$preferredTypes = current($accepts);
if (array_intersect($preferredTypes, ['html', 'xhtml'])) {
return;
}
$extensions = array_unique(
array_merge(Router::extensions(), array_keys($this->getConfig('viewClassMap')))
);
foreach ($accepts as $types) {
$ext = array_intersect($extensions, $types);
if ($ext) {
$this->ext = current($ext);
break;
}
}
}
/**
* The startup method of the RequestHandler enables several automatic behaviors
* related to the detection of certain properties of the HTTP request, including:
*
* If the XML data is POSTed, the data is parsed into an XML object, which is assigned
* to the $data property of the controller, which can then be saved to a model object.
*
* @param \Cake\Event\Event $event The startup event that was fired.
* @return void
*/
public function startup(Event $event)
{
/** @var \Cake\Controller\Controller $controller */
$controller = $event->getSubject();
$request = $controller->getRequest();
$response = $controller->getResponse();
if ($request->getParam('_ext')) {
$this->ext = $request->getParam('_ext');
}
if (!$this->ext || in_array($this->ext, ['html', 'htm'])) {
$this->_setExtension($request, $response);
}
$isAjax = $request->is('ajax');
$controller->setRequest($request->withParam('isAjax', $isAjax));
if (!$this->ext && $isAjax) {
$this->ext = 'ajax';
}
if ($request->is(['get', 'head', 'options'])) {
return;
}
if ($request->getParsedBody() !== []) {
return;
}
foreach ($this->getConfig('inputTypeMap') as $type => $handler) {
if (!is_callable($handler[0])) {
throw new RuntimeException(sprintf("Invalid callable for '%s' type.", $type));
}
if ($this->requestedWith($type)) {
$input = $request->input(...$handler);
$controller->setRequest($request->withParsedBody((array)$input));
}
}
}
/**
* Helper method to parse xml input data, due to lack of anonymous functions
* this lives here.
*
* @param string $xml XML string.
* @return array Xml array data
*/
public function convertXml($xml)
{
try {
$xml = Xml::build($xml, ['return' => 'domdocument', 'readFile' => false]);
// We might not get child nodes if there are nested inline entities.
if ((int)$xml->childNodes->length > 0) {
return Xml::toArray($xml);
}
return [];
} catch (XmlException $e) {
return [];
}
}
/**
* Handles (fakes) redirects for AJAX requests using requestAction()
*
* @param \Cake\Event\Event $event The Controller.beforeRedirect event.
* @param string|array $url A string or array containing the redirect location
* @param \Cake\Http\Response $response The response object.
* @return \Cake\Http\Response|null The response object if the redirect is caught.
* @deprecated 3.3.5 This functionality will be removed in 4.0.0. You can disable this function
* now by setting the `enableBeforeRedirect` config option to false.
*/
public function beforeRedirect(Event $event, $url, Response $response)
{
if (!$this->getConfig('enableBeforeRedirect')) {
return null;
}
deprecationWarning(
'RequestHandlerComponent::beforeRedirect() is deprecated. ' .
'This functionality will be removed in 4.0.0. Set the `enableBeforeRedirect` ' .
'option to `false` to disable this warning.'
);
$request = $this->getController()->getRequest();
if (!$request->is('ajax')) {
return null;
}
if (empty($url)) {
return null;
}
if (is_array($url)) {
$url = Router::url($url + ['_base' => false]);
}
$query = [];
if (strpos($url, '?') !== false) {
list($url, $querystr) = explode('?', $url, 2);
parse_str($querystr, $query);
}
/** @var \Cake\Controller\Controller $controller */
$controller = $event->getSubject();
$response->body($controller->requestAction($url, [
'return',
'bare' => false,
'environment' => [
'REQUEST_METHOD' => 'GET',
],
'query' => $query,
'cookies' => $request->getCookieParams(),
]));
return $response->withStatus(200);
}
/**
* Checks if the response can be considered different according to the request
* headers, and the caching response headers. If it was not modified, then the
* render process is skipped. And the client will get a blank response with a
* "304 Not Modified" header.
*
* - If Router::extensions() is enabled, the layout and template type are
* switched based on the parsed extension or `Accept` header. For example,
* if `controller/action.xml` is requested, the view path becomes
* `app/View/Controller/xml/action.ctp`. Also if `controller/action` is
* requested with `Accept: application/xml` in the headers the view
* path will become `app/View/Controller/xml/action.ctp`. Layout and template
* types will only switch to mime-types recognized by Cake\Http\Response.
* If you need to declare additional mime-types, you can do so using
* Cake\Http\Response::type() in your controller's beforeFilter() method.
* - If a helper with the same name as the extension exists, it is added to
* the controller.
* - If the extension is of a type that RequestHandler understands, it will
* set that Content-type in the response header.
*
* @param \Cake\Event\Event $event The Controller.beforeRender event.
* @return bool false if the render process should be aborted
* @throws \Cake\Http\Exception\NotFoundException If invoked extension is not configured.
*/
public function beforeRender(Event $event)
{
/** @var \Cake\Controller\Controller $controller */
$controller = $event->getSubject();
$response = $controller->getResponse();
$request = $controller->getRequest();
if ($this->ext && !in_array($this->ext, ['html', 'htm'])) {
if (!$response->getMimeType($this->ext)) {
throw new NotFoundException('Invoked extension not recognized/configured: ' . $this->ext);
}
$this->renderAs($controller, $this->ext);
$response = $controller->response;
} else {
$response = $response->withCharset(Configure::read('App.encoding'));
}
if (
$this->_config['checkHttpCache'] &&
$response->checkNotModified($request)
) {
$controller->setResponse($response);
return false;
}
$controller->setResponse($response);
}
/**
* Returns true if the current call accepts an XML response, false otherwise
*
* @return bool True if client accepts an XML response
* @deprecated 3.7.0 Use RequestHandler::prefers('xml') instead.
*/
public function isXml()
{
deprecationWarning(
'RequestHandlerComponent::isXml() is deprecated. Use RequestHandlerComponent::prefers(\'xml\') instead.'
);
return $this->prefers('xml');
}
/**
* Returns true if the current call accepts an RSS response, false otherwise
*
* @return bool True if client accepts an RSS response
* @deprecated 3.7.0 Use RequestHandler::prefers('rss') instead.
*/
public function isRss()
{
deprecationWarning(
'RequestHandlerComponent::isRss() is deprecated. Use RequestHandlerComponent::prefers(\'rss\') instead.'
);
return $this->prefers('rss');
}
/**
* Returns true if the current call accepts an Atom response, false otherwise
*
* @return bool True if client accepts an Atom response
* @deprecated 3.7.0 Use RequestHandler::prefers('atom') instead.
*/
public function isAtom()
{
deprecationWarning(
'RequestHandlerComponent::isAtom() is deprecated. Use RequestHandlerComponent::prefers(\'atom\') instead.'
);
return $this->prefers('atom');
}
/**
* Returns true if user agent string matches a mobile web browser, or if the
* client accepts WAP content.
*
* @return bool True if user agent is a mobile web browser
* @deprecated 3.7.0 Use ServerRequest::is('mobile') instead.
*/
public function isMobile()
{
deprecationWarning(
'RequestHandlerComponent::isMobile() is deprecated. Use ServerRequest::is(\'mobile\') instead.'
);
$request = $this->getController()->getRequest();
return $request->is('mobile') || $this->accepts('wap');
}
/**
* Returns true if the client accepts WAP content
*
* @return bool
* @deprecated 3.7.0 Use RequestHandler::prefers('wap') instead.
*/
public function isWap()
{
deprecationWarning(
'RequestHandlerComponent::isWap() is deprecated. Use RequestHandlerComponent::prefers(\'wap\') instead.'
);
return $this->prefers('wap');
}
/**
* Determines which content types the client accepts. Acceptance is based on
* the file extension parsed by the Router (if present), and by the HTTP_ACCEPT
* header. Unlike Cake\Http\ServerRequest::accepts() this method deals entirely with mapped content types.
*
* Usage:
*
* ```
* $this->RequestHandler->accepts(['xml', 'html', 'json']);
* ```
*
* Returns true if the client accepts any of the supplied types.
*
* ```
* $this->RequestHandler->accepts('xml');
* ```
*
* Returns true if the client accepts xml.
*
* @param string|array|null $type Can be null (or no parameter), a string type name, or an
* array of types
* @return mixed If null or no parameter is passed, returns an array of content
* types the client accepts. If a string is passed, returns true
* if the client accepts it. If an array is passed, returns true
* if the client accepts one or more elements in the array.
*/
public function accepts($type = null)
{
$controller = $this->getController();
$request = $controller->getRequest();
$response = $controller->getResponse();
$accepted = $request->accepts();
if (!$type) {
return $response->mapType($accepted);
}
if (is_array($type)) {
foreach ($type as $t) {
$t = $this->mapAlias($t);
if (in_array($t, $accepted)) {
return true;
}
}
return false;
}
if (is_string($type)) {
return in_array($this->mapAlias($type), $accepted);
}
return false;
}
/**
* Determines the content type of the data the client has sent (i.e. in a POST request)
*
* @param string|array|null $type Can be null (or no parameter), a string type name, or an array of types
* @return mixed If a single type is supplied a boolean will be returned. If no type is provided
* The mapped value of CONTENT_TYPE will be returned. If an array is supplied the first type
* in the request content type will be returned.
*/
public function requestedWith($type = null)
{
$controller = $this->getController();
$request = $controller->getRequest();
$response = $controller->getResponse();
if (
!$request->is('post') &&
!$request->is('put') &&
!$request->is('patch') &&
!$request->is('delete')
) {
return null;
}
if (is_array($type)) {
foreach ($type as $t) {
if ($this->requestedWith($t)) {
return $t;
}
}
return false;
}
list($contentType) = explode(';', $request->contentType());
if ($type === null) {
return $response->mapType($contentType);
}
if (is_string($type)) {
return ($type === $response->mapType($contentType));
}
}
/**
* Determines which content-types the client prefers. If no parameters are given,
* the single content-type that the client most likely prefers is returned. If $type is
* an array, the first item in the array that the client accepts is returned.
* Preference is determined primarily by the file extension parsed by the Router
* if provided, and secondarily by the list of content-types provided in
* HTTP_ACCEPT.
*
* @param string|array|null $type An optional array of 'friendly' content-type names, i.e.
* 'html', 'xml', 'js', etc.
* @return mixed If $type is null or not provided, the first content-type in the
* list, based on preference, is returned. If a single type is provided
* a boolean will be returned if that type is preferred.
* If an array of types are provided then the first preferred type is returned.
* If no type is provided the first preferred type is returned.
*/
public function prefers($type = null)
{
$controller = $this->getController();
$request = $controller->getRequest();
$response = $controller->getResponse();
$acceptRaw = $request->parseAccept();
if (empty($acceptRaw)) {
return $type ? $type === $this->ext : $this->ext;
}
$accepts = $response->mapType(array_shift($acceptRaw));
if (!$type) {
if (empty($this->ext) && !empty($accepts)) {
return $accepts[0];
}
return $this->ext;
}
$types = (array)$type;
if (count($types) === 1) {
if ($this->ext) {
return in_array($this->ext, $types);
}
return in_array($types[0], $accepts);
}
$intersect = array_values(array_intersect($accepts, $types));
if (!$intersect) {
return false;
}
return $intersect[0];
}
/**
* Sets either the view class if one exists or the layout and template path of the view.
* The names of these are derived from the $type input parameter.
*
* ### Usage:
*
* Render the response as an 'ajax' response.
*
* ```
* $this->RequestHandler->renderAs($this, 'ajax');
* ```
*
* Render the response as an xml file and force the result as a file download.
*
* ```
* $this->RequestHandler->renderAs($this, 'xml', ['attachment' => 'myfile.xml'];
* ```
*
* @param \Cake\Controller\Controller $controller A reference to a controller object
* @param string $type Type of response to send (e.g: 'ajax')
* @param array $options Array of options to use
* @return void
* @see \Cake\Controller\Component\RequestHandlerComponent::respondAs()
*/
public function renderAs(Controller $controller, $type, array $options = [])
{
$defaults = ['charset' => 'UTF-8'];
$viewClassMap = $this->getConfig('viewClassMap');
if (Configure::read('App.encoding') !== null) {
$defaults['charset'] = Configure::read('App.encoding');
}
$options += $defaults;
$builder = $controller->viewBuilder();
if (array_key_exists($type, $viewClassMap)) {
$view = $viewClassMap[$type];
} else {
$view = Inflector::classify($type);
}
$viewClass = null;
if ($builder->getClassName() === null) {
$viewClass = App::className($view, 'View', 'View');
}
if ($viewClass) {
$controller->viewClass = $viewClass;
$builder->setClassName($viewClass);
} else {
if (!$this->_renderType) {
$builder->setTemplatePath($builder->getTemplatePath() . DIRECTORY_SEPARATOR . $type);
} else {
$builder->setTemplatePath(preg_replace(
"/([\/\\\\]{$this->_renderType})$/",
DIRECTORY_SEPARATOR . $type,
$builder->getTemplatePath()
));
}
$this->_renderType = $type;
$builder->setLayoutPath($type);
}
$response = $controller->response;
if ($response->getMimeType($type)) {
$this->respondAs($type, $options);
}
}
/**
* Sets the response header based on type map index name. This wraps several methods
* available on Cake\Http\Response. It also allows you to use Content-Type aliases.
*
* @param string|array $type Friendly type name, i.e. 'html' or 'xml', or a full content-type,
* like 'application/x-shockwave'.
* @param array $options If $type is a friendly type name that is associated with
* more than one type of content, $index is used to select which content-type to use.
* @return bool Returns false if the friendly type name given in $type does
* not exist in the type map, or if the Content-type header has
* already been set by this method.
*/
public function respondAs($type, array $options = [])
{
$defaults = ['index' => null, 'charset' => null, 'attachment' => false];
$options += $defaults;
$cType = $type;
$controller = $this->getController();
$response = $controller->getResponse();
$request = $controller->getRequest();
if (strpos($type, '/') === false) {
$cType = $response->getMimeType($type);
}
if (is_array($cType)) {
if (isset($cType[$options['index']])) {
$cType = $cType[$options['index']];
}
if ($this->prefers($cType)) {
$cType = $this->prefers($cType);
} else {
$cType = $cType[0];
}
}
if (!$type) {
return false;
}
if (!$request->getParam('requested')) {
$response = $response->withType($cType);
}
if (!empty($options['charset'])) {
$response = $response->withCharset($options['charset']);
}
if (!empty($options['attachment'])) {
$response = $response->withDownload($options['attachment']);
}
$controller->setResponse($response);
return true;
}
/**
* Returns the current response type (Content-type header), or null if not alias exists
*
* @return mixed A string content type alias, or raw content type if no alias map exists,
* otherwise null
* @deprecated 3.7.0 Use $response->mapType($response->getType()) instead.
*/
public function responseType()
{
deprecationWarning(
'RequestHandlerComponent::responseType() is deprecated. Use $response->mapType($response->getType()) instead.'
);
$response = $this->getController()->response;
return $response->mapType($response->getType());
}
/**
* Maps a content type alias back to its mime-type(s)
*
* @param string|array $alias String alias to convert back into a content type. Or an array of aliases to map.
* @return string|array|null Null on an undefined alias. String value of the mapped alias type. If an
* alias maps to more than one content type, the first one will be returned. If an array is provided
* for $alias, an array of mapped types will be returned.
*/
public function mapAlias($alias)
{
if (is_array($alias)) {
return array_map([$this, 'mapAlias'], $alias);
}
$response = $this->getController()->response;
$type = $response->getMimeType($alias);
if ($type) {
if (is_array($type)) {
return $type[0];
}
return $type;
}
return null;
}
/**
* Add a new mapped input type. Mapped input types are automatically
* converted by RequestHandlerComponent during the startup() callback.
*
* @param string $type The type alias being converted, ie. json
* @param array $handler The handler array for the type. The first index should
* be the handling callback, all other arguments should be additional parameters
* for the handler.
* @return void
* @throws \Cake\Core\Exception\Exception
* @deprecated 3.1.0 Use setConfig('addInputType', ...) instead.
*/
public function addInputType($type, $handler)
{
deprecationWarning(
'RequestHandlerComponent::addInputType() is deprecated. Use setConfig("inputTypeMap", ...) instead.'
);
if (!is_array($handler) || !isset($handler[0]) || !is_callable($handler[0])) {
throw new Exception('You must give a handler callback.');
}
$this->setConfig('inputTypeMap.' . $type, $handler);
}
/**
* Getter/setter for viewClassMap
*
* @param array|string|null $type The type string or array with format `['type' => 'viewClass']` to map one or more
* @param array|null $viewClass The viewClass to be used for the type without `View` appended
* @return array|string Returns viewClass when only string $type is set, else array with viewClassMap
* @deprecated 3.1.0 Use setConfig('viewClassMap', ...) instead.
*/
public function viewClassMap($type = null, $viewClass = null)
{
deprecationWarning(
'RequestHandlerComponent::viewClassMap() is deprecated. Use setConfig("viewClassMap", ...) instead.'
);
if (!$viewClass && is_string($type)) {
return $this->getConfig('viewClassMap.' . $type);
}
if (is_string($type)) {
$this->setConfig('viewClassMap.' . $type, $viewClass);
} elseif (is_array($type)) {
$this->setConfig('viewClassMap', $type, true);
}
return $this->getConfig('viewClassMap');
}
}
@@ -0,0 +1,673 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 0.10.8
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Controller\Component;
use Cake\Controller\Component;
use Cake\Controller\Controller;
use Cake\Controller\Exception\AuthSecurityException;
use Cake\Controller\Exception\SecurityException;
use Cake\Core\Configure;
use Cake\Event\Event;
use Cake\Http\Exception\BadRequestException;
use Cake\Http\ServerRequest;
use Cake\Routing\Router;
use Cake\Utility\Hash;
use Cake\Utility\Security;
/**
* The Security Component creates an easy way to integrate tighter security in
* your application. It provides methods for various tasks like:
*
* - Restricting which HTTP methods your application accepts.
* - Form tampering protection
* - Requiring that SSL be used.
* - Limiting cross controller communication.
*
* @link https://book.cakephp.org/3/en/controllers/components/security.html
*/
class SecurityComponent extends Component
{
/**
* Default message used for exceptions thrown
*/
const DEFAULT_EXCEPTION_MESSAGE = 'The request has been black-holed';
/**
* Default config
*
* - `blackHoleCallback` - The controller method that will be called if this
* request is black-hole'd.
* - `requireSecure` - List of actions that require an SSL-secured connection.
* - `requireAuth` - List of actions that require a valid authentication key. Deprecated as of 3.2.2
* - `allowedControllers` - Controllers from which actions of the current
* controller are allowed to receive requests.
* - `allowedActions` - Actions from which actions of the current controller
* are allowed to receive requests.
* - `unlockedFields` - Form fields to exclude from POST validation. Fields can
* be unlocked either in the Component, or with FormHelper::unlockField().
* Fields that have been unlocked are not required to be part of the POST
* and hidden unlocked fields do not have their values checked.
* - `unlockedActions` - Actions to exclude from POST validation checks.
* Other checks like requireAuth(), requireSecure() etc. will still be applied.
* - `validatePost` - Whether to validate POST data. Set to false to disable
* for data coming from 3rd party services, etc.
*
* @var array
*/
protected $_defaultConfig = [
'blackHoleCallback' => null,
'requireSecure' => [],
'requireAuth' => [],
'allowedControllers' => [],
'allowedActions' => [],
'unlockedFields' => [],
'unlockedActions' => [],
'validatePost' => true,
];
/**
* Holds the current action of the controller
*
* @var string
*/
protected $_action;
/**
* The Session object
*
* @var \Cake\Http\Session
*/
public $session;
/**
* Component startup. All security checking happens here.
*
* @param \Cake\Event\Event $event An Event instance
* @return mixed
*/
public function startup(Event $event)
{
/** @var \Cake\Controller\Controller $controller */
$controller = $event->getSubject();
$request = $controller->request;
$this->session = $request->getSession();
$this->_action = $request->getParam('action');
$hasData = ($request->getData() || $request->is(['put', 'post', 'delete', 'patch']));
try {
$this->_secureRequired($controller);
$this->_authRequired($controller);
$isNotRequestAction = !$request->getParam('requested');
if ($this->_action === $this->_config['blackHoleCallback']) {
throw new AuthSecurityException(sprintf('Action %s is defined as the blackhole callback.', $this->_action));
}
if (
!in_array($this->_action, (array)$this->_config['unlockedActions']) &&
$hasData &&
$isNotRequestAction &&
$this->_config['validatePost']
) {
$this->_validatePost($controller);
}
} catch (SecurityException $se) {
return $this->blackHole($controller, $se->getType(), $se);
}
$request = $this->generateToken($request);
if ($hasData && is_array($controller->getRequest()->getData())) {
$request = $request->withoutData('_Token');
}
$controller->setRequest($request);
}
/**
* Events supported by this component.
*
* @return array
*/
public function implementedEvents()
{
return [
'Controller.startup' => 'startup',
];
}
/**
* Sets the actions that require a request that is SSL-secured, or empty for all actions
*
* @param string|array|null $actions Actions list
* @return void
*/
public function requireSecure($actions = null)
{
$this->_requireMethod('Secure', (array)$actions);
}
/**
* Sets the actions that require whitelisted form submissions.
*
* Adding actions with this method will enforce the restrictions
* set in SecurityComponent::$allowedControllers and
* SecurityComponent::$allowedActions.
*
* @param string|array $actions Actions list
* @return void
* @deprecated 3.2.2 This feature is confusing and not useful.
*/
public function requireAuth($actions)
{
deprecationWarning('SecurityComponent::requireAuth() will be removed in 4.0.0.');
$this->_requireMethod('Auth', (array)$actions);
}
/**
* Black-hole an invalid request with a 400 error or custom callback. If SecurityComponent::$blackHoleCallback
* is specified, it will use this callback by executing the method indicated in $error
*
* @param \Cake\Controller\Controller $controller Instantiating controller
* @param string $error Error method
* @param \Cake\Controller\Exception\SecurityException|null $exception Additional debug info describing the cause
* @return mixed If specified, controller blackHoleCallback's response, or no return otherwise
* @see \Cake\Controller\Component\SecurityComponent::$blackHoleCallback
* @link https://book.cakephp.org/3/en/controllers/components/security.html#handling-blackhole-callbacks
* @throws \Cake\Http\Exception\BadRequestException
*/
public function blackHole(Controller $controller, $error = '', SecurityException $exception = null)
{
if (!$this->_config['blackHoleCallback']) {
$this->_throwException($exception);
}
return $this->_callback($controller, $this->_config['blackHoleCallback'], [$error, $exception]);
}
/**
* Check debug status and throw an Exception based on the existing one
*
* @param \Cake\Controller\Exception\SecurityException|null $exception Additional debug info describing the cause
* @throws \Cake\Http\Exception\BadRequestException
* @return void
*/
protected function _throwException($exception = null)
{
if ($exception !== null) {
if (!Configure::read('debug') && $exception instanceof SecurityException) {
$exception->setReason($exception->getMessage());
$exception->setMessage(self::DEFAULT_EXCEPTION_MESSAGE);
}
throw $exception;
}
throw new BadRequestException(self::DEFAULT_EXCEPTION_MESSAGE);
}
/**
* Sets the actions that require a $method HTTP request, or empty for all actions
*
* @param string $method The HTTP method to assign controller actions to
* @param array $actions Controller actions to set the required HTTP method to.
* @return void
*/
protected function _requireMethod($method, $actions = [])
{
if (isset($actions[0]) && is_array($actions[0])) {
$actions = $actions[0];
}
$this->setConfig('require' . $method, empty($actions) ? ['*'] : $actions);
}
/**
* Check if access requires secure connection
*
* @param \Cake\Controller\Controller $controller Instantiating controller
* @return bool true if secure connection required
*/
protected function _secureRequired(Controller $controller)
{
if (
is_array($this->_config['requireSecure']) &&
!empty($this->_config['requireSecure'])
) {
$requireSecure = $this->_config['requireSecure'];
if (in_array($this->_action, $requireSecure) || $requireSecure === ['*']) {
if (!$this->getController()->getRequest()->is('ssl')) {
throw new SecurityException(
'Request is not SSL and the action is required to be secure'
);
}
}
}
return true;
}
/**
* Check if authentication is required
*
* @param \Cake\Controller\Controller $controller Instantiating controller
* @return bool true if authentication required
* @deprecated 3.2.2 This feature is confusing and not useful.
*/
protected function _authRequired(Controller $controller)
{
$request = $controller->getRequest();
if (
is_array($this->_config['requireAuth']) &&
!empty($this->_config['requireAuth']) &&
$request->getData()
) {
deprecationWarning('SecurityComponent::requireAuth() will be removed in 4.0.0.');
$requireAuth = $this->_config['requireAuth'];
if (in_array($request->getParam('action'), $requireAuth) || $requireAuth == ['*']) {
if ($request->getData('_Token') === null) {
throw new AuthSecurityException('\'_Token\' was not found in request data.');
}
if ($this->session->check('_Token')) {
$tData = $this->session->read('_Token');
if (
!empty($tData['allowedControllers']) &&
!in_array($request->getParam('controller'), $tData['allowedControllers'])
) {
throw new AuthSecurityException(
sprintf(
'Controller \'%s\' was not found in allowed controllers: \'%s\'.',
$request->getParam('controller'),
implode(', ', (array)$tData['allowedControllers'])
)
);
}
if (
!empty($tData['allowedActions']) &&
!in_array($request->getParam('action'), $tData['allowedActions'])
) {
throw new AuthSecurityException(
sprintf(
'Action \'%s::%s\' was not found in allowed actions: \'%s\'.',
$request->getParam('controller'),
$request->getParam('action'),
implode(', ', (array)$tData['allowedActions'])
)
);
}
} else {
throw new AuthSecurityException('\'_Token\' was not found in session.');
}
}
}
return true;
}
/**
* Validate submitted form
*
* @param \Cake\Controller\Controller $controller Instantiating controller
* @throws \Cake\Controller\Exception\AuthSecurityException
* @return bool true if submitted form is valid
*/
protected function _validatePost(Controller $controller)
{
$token = $this->_validToken($controller);
$hashParts = $this->_hashParts($controller);
$check = hash_hmac('sha1', implode('', $hashParts), Security::getSalt());
if (hash_equals($check, $token)) {
return true;
}
$msg = self::DEFAULT_EXCEPTION_MESSAGE;
if (Configure::read('debug')) {
$msg = $this->_debugPostTokenNotMatching($controller, $hashParts);
}
throw new AuthSecurityException($msg);
}
/**
* Check if token is valid
*
* @param \Cake\Controller\Controller $controller Instantiating controller
* @throws \Cake\Controller\Exception\SecurityException
* @return string fields token
*/
protected function _validToken(Controller $controller)
{
$check = $controller->getRequest()->getData();
$message = '\'%s\' was not found in request data.';
if (!isset($check['_Token'])) {
throw new AuthSecurityException(sprintf($message, '_Token'));
}
if (!isset($check['_Token']['fields'])) {
throw new AuthSecurityException(sprintf($message, '_Token.fields'));
}
if (!isset($check['_Token']['unlocked'])) {
throw new AuthSecurityException(sprintf($message, '_Token.unlocked'));
}
if (Configure::read('debug') && !isset($check['_Token']['debug'])) {
throw new SecurityException(sprintf($message, '_Token.debug'));
}
if (!Configure::read('debug') && isset($check['_Token']['debug'])) {
throw new SecurityException('Unexpected \'_Token.debug\' found in request data');
}
$token = urldecode($check['_Token']['fields']);
if (strpos($token, ':')) {
list($token, ) = explode(':', $token, 2);
}
return $token;
}
/**
* Return hash parts for the Token generation
*
* @param \Cake\Controller\Controller $controller Instantiating controller
* @return array
*/
protected function _hashParts(Controller $controller)
{
$request = $controller->getRequest();
// Start the session to ensure we get the correct session id.
$session = $request->getSession();
$session->start();
$data = $request->getData();
$fieldList = $this->_fieldsList($data);
$unlocked = $this->_sortedUnlocked($data);
return [
Router::url($request->getRequestTarget()),
serialize($fieldList),
$unlocked,
$session->id(),
];
}
/**
* Return the fields list for the hash calculation
*
* @param array $check Data array
* @return array
*/
protected function _fieldsList(array $check)
{
$locked = '';
$token = urldecode($check['_Token']['fields']);
$unlocked = $this->_unlocked($check);
if (strpos($token, ':')) {
list($token, $locked) = explode(':', $token, 2);
}
unset($check['_Token'], $check['_csrfToken']);
$locked = explode('|', $locked);
$unlocked = explode('|', $unlocked);
$fields = Hash::flatten($check);
$fieldList = array_keys($fields);
$multi = $lockedFields = [];
$isUnlocked = false;
foreach ($fieldList as $i => $key) {
if (preg_match('/(\.\d+){1,10}$/', $key)) {
$multi[$i] = preg_replace('/(\.\d+){1,10}$/', '', $key);
unset($fieldList[$i]);
} else {
$fieldList[$i] = (string)$key;
}
}
if (!empty($multi)) {
$fieldList += array_unique($multi);
}
$unlockedFields = array_unique(
array_merge((array)$this->getConfig('disabledFields'), (array)$this->_config['unlockedFields'], $unlocked)
);
foreach ($fieldList as $i => $key) {
$isLocked = (is_array($locked) && in_array($key, $locked));
if (!empty($unlockedFields)) {
foreach ($unlockedFields as $off) {
$off = explode('.', $off);
$field = array_values(array_intersect(explode('.', $key), $off));
$isUnlocked = ($field === $off);
if ($isUnlocked) {
break;
}
}
}
if ($isUnlocked || $isLocked) {
unset($fieldList[$i]);
if ($isLocked) {
$lockedFields[$key] = $fields[$key];
}
}
}
sort($fieldList, SORT_STRING);
ksort($lockedFields, SORT_STRING);
$fieldList += $lockedFields;
return $fieldList;
}
/**
* Get the unlocked string
*
* @param array $data Data array
* @return string
*/
protected function _unlocked(array $data)
{
return urldecode($data['_Token']['unlocked']);
}
/**
* Get the sorted unlocked string
*
* @param array $data Data array
* @return string
*/
protected function _sortedUnlocked($data)
{
$unlocked = $this->_unlocked($data);
$unlocked = explode('|', $unlocked);
sort($unlocked, SORT_STRING);
return implode('|', $unlocked);
}
/**
* Create a message for humans to understand why Security token is not matching
*
* @param \Cake\Controller\Controller $controller Instantiating controller
* @param array $hashParts Elements used to generate the Token hash
* @return string Message explaining why the tokens are not matching
*/
protected function _debugPostTokenNotMatching(Controller $controller, $hashParts)
{
$messages = [];
$expectedParts = json_decode(urldecode($controller->getRequest()->getData('_Token.debug')), true);
if (!is_array($expectedParts) || count($expectedParts) !== 3) {
return 'Invalid security debug token.';
}
$expectedUrl = Hash::get($expectedParts, 0);
$url = Hash::get($hashParts, 0);
if ($expectedUrl !== $url) {
$messages[] = sprintf('URL mismatch in POST data (expected \'%s\' but found \'%s\')', $expectedUrl, $url);
}
$expectedFields = Hash::get($expectedParts, 1);
$dataFields = Hash::get($hashParts, 1);
if ($dataFields) {
$dataFields = unserialize($dataFields);
}
$fieldsMessages = $this->_debugCheckFields(
$dataFields,
$expectedFields,
'Unexpected field \'%s\' in POST data',
'Tampered field \'%s\' in POST data (expected value \'%s\' but found \'%s\')',
'Missing field \'%s\' in POST data'
);
$expectedUnlockedFields = Hash::get($expectedParts, 2);
$dataUnlockedFields = Hash::get($hashParts, 2) ?: null;
if ($dataUnlockedFields) {
$dataUnlockedFields = explode('|', $dataUnlockedFields);
}
$unlockFieldsMessages = $this->_debugCheckFields(
(array)$dataUnlockedFields,
$expectedUnlockedFields,
'Unexpected unlocked field \'%s\' in POST data',
null,
'Missing unlocked field: \'%s\''
);
$messages = array_merge($messages, $fieldsMessages, $unlockFieldsMessages);
return implode(', ', $messages);
}
/**
* Iterates data array to check against expected
*
* @param array $dataFields Fields array, containing the POST data fields
* @param array $expectedFields Fields array, containing the expected fields we should have in POST
* @param string $intKeyMessage Message string if unexpected found in data fields indexed by int (not protected)
* @param string $stringKeyMessage Message string if tampered found in data fields indexed by string (protected)
* @param string $missingMessage Message string if missing field
* @return array Messages
*/
protected function _debugCheckFields($dataFields, $expectedFields = [], $intKeyMessage = '', $stringKeyMessage = '', $missingMessage = '')
{
$messages = $this->_matchExistingFields($dataFields, $expectedFields, $intKeyMessage, $stringKeyMessage);
$expectedFieldsMessage = $this->_debugExpectedFields($expectedFields, $missingMessage);
if ($expectedFieldsMessage !== null) {
$messages[] = $expectedFieldsMessage;
}
return $messages;
}
/**
* Manually add form tampering prevention token information into the provided
* request object.
*
* @param \Cake\Http\ServerRequest $request The request object to add into.
* @return \Cake\Http\ServerRequest The modified request.
*/
public function generateToken(ServerRequest $request)
{
if ($request->is('requested')) {
if ($this->session->check('_Token')) {
$request = $request->withParam('_Token', $this->session->read('_Token'));
}
return $request;
}
$token = [
'allowedControllers' => $this->_config['allowedControllers'],
'allowedActions' => $this->_config['allowedActions'],
'unlockedFields' => $this->_config['unlockedFields'],
];
$this->session->write('_Token', $token);
return $request->withParam('_Token', [
'unlockedFields' => $token['unlockedFields'],
]);
}
/**
* Calls a controller callback method
*
* @param \Cake\Controller\Controller $controller Instantiating controller
* @param string $method Method to execute
* @param array $params Parameters to send to method
* @return mixed Controller callback method's response
* @throws \Cake\Http\Exception\BadRequestException When a the blackholeCallback is not callable.
*/
protected function _callback(Controller $controller, $method, $params = [])
{
if (!is_callable([$controller, $method])) {
throw new BadRequestException('The request has been black-holed');
}
return call_user_func_array([&$controller, $method], empty($params) ? null : $params);
}
/**
* Generate array of messages for the existing fields in POST data, matching dataFields in $expectedFields
* will be unset
*
* @param array $dataFields Fields array, containing the POST data fields
* @param array $expectedFields Fields array, containing the expected fields we should have in POST
* @param string $intKeyMessage Message string if unexpected found in data fields indexed by int (not protected)
* @param string $stringKeyMessage Message string if tampered found in data fields indexed by string (protected)
* @return array Error messages
*/
protected function _matchExistingFields($dataFields, &$expectedFields, $intKeyMessage, $stringKeyMessage)
{
$messages = [];
foreach ((array)$dataFields as $key => $value) {
if (is_int($key)) {
$foundKey = array_search($value, (array)$expectedFields);
if ($foundKey === false) {
$messages[] = sprintf($intKeyMessage, $value);
} else {
unset($expectedFields[$foundKey]);
}
} elseif (is_string($key)) {
if (isset($expectedFields[$key]) && $value !== $expectedFields[$key]) {
$messages[] = sprintf($stringKeyMessage, $key, $expectedFields[$key], $value);
}
unset($expectedFields[$key]);
}
}
return $messages;
}
/**
* Generate debug message for the expected fields
*
* @param array $expectedFields Expected fields
* @param string $missingMessage Message template
* @return string|null Error message about expected fields
*/
protected function _debugExpectedFields($expectedFields = [], $missingMessage = '')
{
if (count($expectedFields) === 0) {
return null;
}
$expectedFieldNames = [];
foreach ((array)$expectedFields as $key => $expectedField) {
if (is_int($key)) {
$expectedFieldNames[] = $expectedField;
} else {
$expectedFieldNames[] = $key;
}
}
return sprintf($missingMessage, implode(', ', $expectedFieldNames));
}
}
Loaded 100 of 2747 files, more files were not shown because too many files have changed in this diff. Show more