Skip to content

Commit

Permalink
Merge pull request #5 from HavokInspiration/view-mode
Browse files Browse the repository at this point in the history
Add a View mode
  • Loading branch information
HavokInspiration committed Nov 18, 2015
2 parents c71efcc + f814c0c commit 5ee54ef
Show file tree
Hide file tree
Showing 13 changed files with 456 additions and 10 deletions.
43 changes: 34 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ for your CakePHP website / applications.
## Requirements

- PHP 5.4.16
- CakePHP 3
- CakePHP 3.0.0+ (tested up to 3.1.4)

## Recommanded packages

Expand Down Expand Up @@ -169,6 +169,39 @@ DispatcherFactory::add('Wrench.MaintenanceMode', [
]);
```

#### View Mode

The View Mode gives you the ability to use a View to render the maintenance page.
This gives you the ability to leverage helpers and the layout / template system of the framework.
It accepts multiple parameters :
- **code** : The HTTP status code of the redirect response. Default to 503.
- **headers** : Array of additional headers to pass along the redirect response. Default to empty.
- **view** : Array of parameters to pass to the View class constructor. Only the following options are supported :
- **className** : Fully qualified class name of the View class to use. Default to AppView
- **templatePath** : Path to the template you wish to display (relative to your ``src/Template`` directory). You can use plugin dot notation.
- **template** : Template name to use. Default to "maintenance".
- **plugin** : Theme where to find the layout and template
- **theme** : Same thing than plugin
- **layout** : Layout name to use. Default to "default"
- **layoutPath** : Path to the layout you wish to display (relative to your ``src/Template`` directory). You can use plugin dot notation. Default to "Layout"

```php
// Will load a template ``src/Template/Maintenance/maintenance.ctp``
// in a layout ``src/Template/Layout/Maintenance/maintenance.ctp``
DispatcherFactory::add('Wrench.MaintenanceMode', [
'mode' => [
'className' => 'Wrench\Mode\View',
'config' => [
'view' => [
'templatePath' => 'Maintenance',
'layout' => 'maintenance',
'layoutPath' => 'Maintenance'
]
]
]
]);
```

### Creating a custom mode

If you have special needs, you can create your own maintenance mode.
Expand Down Expand Up @@ -234,14 +267,6 @@ You can of course use both ``for`` and ``when`` options at the same time.

More details and examples about the ``for`` and ``when`` options in the [CakePHP Cookbook](http://book.cakephp.org/3.0/en/development/dispatch-filters.html#conditionally-applying-filters).

## To do

- [x] Add a direct output mode
- [ ] Add a "View" layer mode
- [x] Document how to build a custom mode
- [x] Implement, test and write about passing a Mode instance
- [x] Test and write about the ``when`` and ``for`` options

## License

Copyright (c) 2015, Yves Piquel and licensed under [The MIT License](http://opensource.org/licenses/mit-license.php).
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
},
"autoload": {
"psr-4": {
"Wrench\\": "src"
"Wrench\\": "src",
"App\\": "tests/test_app/App"
}
},
"autoload-dev": {
Expand Down
105 changes: 105 additions & 0 deletions src/Mode/View.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php
/**
* Copyright (c) Yves Piquel (http://www.havokinspiration.fr)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Yves Piquel (http://www.havokinspiration.fr)
* @link http://github.com/HavokInspiration/wrench
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Wrench\Mode;

use Cake\Core\Configure;
use Cake\Network\Request;
use Cake\Network\Response;

/**
* `Output` Maintenance Mode.
* When used, it will send the content of the configured file as a response
*/
class View extends Mode
{

/**
* Default config
*
* - `code` : The status code to be sent along with the response.
* - `view` : Array of parameters to pass to the View class constructor. Only the following options are supported :
* - `className` : Fully qualified class name of the View class to use. Default to AppView
* - `templatePath` : Path to the template you wish to display (relative to your ``src/Template`` directory).
* You can use plugin dot notation.
* - `template` : Template name to use. Default to "template".
* - `plugin` : Theme where to find the layout and template
* - `theme` : Same thing than plugin
* - `layout` : Layout name to use. Default to "default"
* - `layoutPath` : Path to the layout you wish to display (relative to your ``src/Template/Layout``directory).
* You can use plugin dot notation. Default to "Layout"
* All other options are not supported (they might work though)
* - `headers` : Additional headers to be set with the response
*
* @var array
*/
protected $_defaultConfig = [
'code' => 503,
'view' => [
'className' => 'App\View\AppView',
'templatePath' => null,
'template' => 'maintenance',
'plugin' => null,
'theme' => null,
'layout' => null,
'layoutPath' => null
],
'headers' => []
];

/**
* {@inheritDoc}
*
* Will set the location where to redirect the request with the specified code
* and optional additional headers.
*/
public function process(Request $request, Response $response)
{
$this->_backwardCompatibility();

$className = $this->config('view.className');
if (empty($className)) {
$className = 'App\View\AppView';
}

$viewConfig = $this->config('view') ?: [];
$view = new $className($request, $response, null, $viewConfig);
$response->body($view->render());
$response->statusCode($this->config('code'));

$headers = $this->config('headers');
if (!empty($headers)) {
$response->header($headers);
}
return $response;
}

/**
* Generate correct View constructor parameter key if CakePHP version
* is below 3.1 where important changes were introduced regarding naming
* Also compensate for a fix that is not existant prior to 3.0.5 in the InstanceConfigTrait
*
* @return void
*/
protected function _backwardCompatibility()
{
if ($this->config('view') === null) {
$this->config('view', $this->_defaultConfig['view']);
}

if (version_compare(Configure::version(), '3.1.0', '<')) {
$config = $this->config('view');
$config['view'] = $this->config('view.template');
$config['viewPath'] = $this->config('view.templatePath');
$this->config('view', $config);
}
}
}
185 changes: 185 additions & 0 deletions tests/TestCase/Mode/ViewTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<?php
/**
* Copyright (c) Yves Piquel (http://www.havokinspiration.fr)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Yves Piquel (http://www.havokinspiration.fr)
* @link http://github.com/HavokInspiration/wrench
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Wrench\Test\TestCase\Mode;

use Cake\Core\Configure;
use Cake\Core\Plugin;
use Cake\Event\Event;
use Cake\Network\Request;
use Cake\TestSuite\TestCase;
use Wrench\Routing\Filter\MaintenanceModeFilter;

class ViewTest extends TestCase
{

/**
* @inheritDoc
*/
public function setup()
{
Plugin::load(['TestPlugin']);
}

/**
* @inheritDoc
*/
public function tearDown()
{
parent::tearDown();
Plugin::unload('TestPlugin');
Configure::write('Wrench.enable', false);
}

/**
* Test the View filter mode without params
* @return void
*/
public function testViewModeNoParams()
{
Configure::write('Wrench.enable', true);

$filter = new MaintenanceModeFilter([
'mode' => [
'className' => 'Wrench\Mode\View'
]
]);

$request = new Request();
$response = $this->getMock('Cake\Network\Response', ['statusCode']);
$response->expects($this->once())
->method('statusCode')
->with(503);

$result = $filter->beforeDispatch(new Event('name', null, ['request' => $request, 'response' => $response]));
$expected = "Layout Header\nThis is an element<div>test</div>This app is undergoing maintenanceLayout Footer";
$this->assertEquals($expected, $result->body());
}

/**
* Test the View with custom params
* @return void
*/
public function testViewModeCustomParams()
{
Configure::write('Wrench.enable', true);

$filter = new MaintenanceModeFilter([
'mode' => [
'className' => 'Wrench\Mode\View',
'config' => [
'code' => 404,
'view' => [
'templatePath' => 'Maintenance',
'layout' => 'maintenance',
'layoutPath' => 'Maintenance'
]
]
]
]);

$request = new Request();
$response = $this->getMock('Cake\Network\Response', ['statusCode']);
$response->expects($this->once())
->method('statusCode')
->with(404);

$result = $filter->beforeDispatch(new Event('name', null, ['request' => $request, 'response' => $response]));
$expected = "Maintenance Header\nI'm in a sub-directoryMaintenance Footer";
$this->assertEquals($expected, $result->body());
}

/**
* Test the View with custom params and plugins
* @return void
*/
public function testViewModeCustomParamsPlugin()
{
Configure::write('Wrench.enable', true);

$filter = new MaintenanceModeFilter([
'mode' => [
'className' => 'Wrench\Mode\View',
'config' => [
'code' => 404,
'view' => [
'template' => 'maintenance',
'templatePath' => 'Maintenance',
'layout' => 'maintenance',
'plugin' => 'TestPlugin',
'layoutPath' => 'Maintenance',
]
]
]
]);

$request = new Request();
$response = $this->getMock('Cake\Network\Response', ['statusCode']);
$response->expects($this->once())
->method('statusCode')
->with(404);

$result = $filter->beforeDispatch(new Event('name', null, ['request' => $request, 'response' => $response]));
$expected = "Plugin Maintenance Header\nI'm in a plugin sub-directoryPlugin Maintenance Footer";
$this->assertEquals($expected, $result->body());

$filter = new MaintenanceModeFilter([
'mode' => [
'className' => 'Wrench\Mode\View',
'config' => [
'code' => 404,
'view' => [
'template' => 'maintenance',
'templatePath' => 'Maintenance',
'layout' => 'maintenance',
'theme' => 'TestPlugin',
'layoutPath' => 'Maintenance',
]
]
]
]);

$request = new Request();
$response = $this->getMock('Cake\Network\Response', ['statusCode']);
$response->expects($this->once())
->method('statusCode')
->with(404);

$result = $filter->beforeDispatch(new Event('name', null, ['request' => $request, 'response' => $response]));
$expected = "Plugin Maintenance Header\nI'm in a plugin sub-directoryPlugin Maintenance Footer";
$this->assertEquals($expected, $result->body());

$filter = new MaintenanceModeFilter([
'mode' => [
'className' => 'Wrench\Mode\View',
'config' => [
'code' => 404,
'view' => [
'template' => 'TestPlugin.maintenance',
'templatePath' => 'Maintenance',
'layout' => 'TestPlugin.maintenance',
'layoutPath' => 'Maintenance',
]
]
]
]);

$request = new Request();
$response = $this->getMock('Cake\Network\Response', ['statusCode']);
$response->expects($this->once())
->method('statusCode')
->with(404);

$result = $filter->beforeDispatch(new Event('name', null, ['request' => $request, 'response' => $response]));
$expected = "Plugin Maintenance Header\nI'm in a plugin sub-directoryPlugin Maintenance Footer";
$this->assertEquals($expected, $result->body());
}
}
8 changes: 8 additions & 0 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@
]
]);

Configure::write('App', [
'namespace' => 'App',
'paths' => [
'plugins' => [APP . 'Plugin' . DS],
'templates' => [APP . 'Template' . DS]
]
]);

Plugin::load('Wrench', [
'path' => dirname(dirname(__FILE__)) . DS,
]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php
/**
* Copyright (c) Yves Piquel (http://www.havokinspiration.fr)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Yves Piquel (http://www.havokinspiration.fr)
* @link http://github.com/HavokInspiration/wrench
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
?>
Plugin Maintenance Header
<?= $this->fetch('content'); ?>
Plugin Maintenance Footer
Loading

0 comments on commit 5ee54ef

Please sign in to comment.