Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
qzminski committed Jun 7, 2022
1 parent 76170ae commit 70ca75d
Show file tree
Hide file tree
Showing 23 changed files with 884 additions and 0 deletions.
24 changes: 24 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# EditorConfig is awesome: http://EditorConfig.org

# top-most EditorConfig file
root = true

# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.{php,twig,yml}]
indent_style = space
indent_size = 4

[*.{svg,min.css,min.js}]
insert_final_newline = false

[*.html5]
indent_style = space
indent_size = 2

[*.md]
trim_trailing_whitespace = false
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/tools/*/vendor
/vendor
/composer.lock
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Page Password for Contao Open Source CMS

[![](https://img.shields.io/packagist/v/codefog/contao-page-password.svg)](https://packagist.org/packages/codefog/contao-page-password)
[![](https://img.shields.io/packagist/l/codefog/contao-page-password.svg)](https://github.com/codefog/contao-page-password/blob/main/LICENSE)
[![](https://img.shields.io/packagist/dt/codefog/contao-page-password.svg)](https://packagist.org/packages/codefog/contao-page-password)

Page Password is a bundle for the [Contao CMS](https://contao.org).

The extension allows you to protect the selected pages with a password.

## Installation

Install the bundle via Composer:

```
composer require codefog/contao-page-password
```

## Configuration

1. Create a frontend module of "Page password" type.
2. Create a hidden page with the created frontend module on it. This will be a page that is displayed when a visitor is not authenticated.
3. Edit the settings of chosen pages and enable the "Protect page with password" checkbox. Then, enter your password and choose the hidden page you have just created.
4. Done!

![](docs/screenshot.png)

## Copyright

This project has been created and is maintained by [Codefog](https://codefog.pl).
53 changes: 53 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "codefog/contao-page-password",
"type": "contao-bundle",
"description": "The extension allows you to protect the selected pages with a password.",
"keywords": ["contao", "page", "password", "protection", "protected"],
"license": "LGPL-3.0-or-later",
"authors": [
{
"name": "Codefog",
"homepage": "https://codefog.pl"
}
],
"funding": [
{
"type": "github",
"url": "https://github.com/codefog"
}
],
"support": {
"issues": "https://github.com/codefog/contao-page-password/issues",
"source": "https://github.com/codefog/contao-page-password"
},
"require": {
"php": "^8.1",
"contao/core-bundle": "^4.13",
"doctrine/dbal": "^3.0"
},
"require-dev": {
"contao/manager-plugin": "^2.0"
},
"conflict": {
"contao/manager-plugin": "<2.0 || >=3.0"
},
"autoload": {
"psr-4": {
"Codefog\\PagePasswordBundle\\": "src/"
}
},
"extra": {
"contao-manager-plugin": "Codefog\\PagePasswordBundle\\ContaoManager\\Plugin"
},
"scripts": {
"cs-fixer": "@php tools/ecs/vendor/bin/ecs check src/ --fix --ansi"
},
"config": {
"allow-plugins": {
"contao-components/installer": true,
"contao/manager-plugin": true,
"contao-community-alliance/composer-plugin": true,
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}
7 changes: 7 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
services:
_defaults:
autoconfigure: true
autowire: true

Codefog\PagePasswordBundle\:
resource: ../src/
4 changes: 4 additions & 0 deletions contao/dca/tl_module.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?php

// Palettes
$GLOBALS['TL_DCA']['tl_module']['palettes']['page_password'] = '{title_legend},name,headline,type;{template_legend:hide},customTpl;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
36 changes: 36 additions & 0 deletions contao/dca/tl_page.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

// Palettes
\Contao\CoreBundle\DataContainer\PaletteManipulator::create()
->addLegend('password_legend', 'routing_legend', \Contao\CoreBundle\DataContainer\PaletteManipulator::POSITION_AFTER)
->addField('passwordProtected', 'password_legend', \Contao\CoreBundle\DataContainer\PaletteManipulator::POSITION_APPEND)
->applyToPalette('regular', 'tl_page')
->applyToPalette('forward', 'tl_page')
->applyToPalette('redirect', 'tl_page')
;

$GLOBALS['TL_DCA']['tl_page']['palettes']['__selector__'][] = 'passwordProtected';
$GLOBALS['TL_DCA']['tl_page']['subpalettes']['passwordProtected'] = 'password,passwordPage';

// Fields
$GLOBALS['TL_DCA']['tl_page']['fields']['passwordProtected'] = [
'exclude' => true,
'filter' => true,
'inputType' => 'checkbox',
'eval' => ['submitOnChange' => true, 'tl_class' => 'clr'],
'sql' => "char(1) COLLATE ascii_bin NOT NULL default ''",
];

$GLOBALS['TL_DCA']['tl_page']['fields']['password'] = [
'exclude' => true,
'inputType' => 'text',
'eval' => ['mandatory' => true, 'maxlength' => 64, 'decodeEntities' => true, 'tl_class' => 'w50'],
'sql' => ['type' => 'string', 'length' => 64, 'default' => ''],
];

$GLOBALS['TL_DCA']['tl_page']['fields']['passwordPage'] = [
'exclude' => true,
'inputType' => 'pageTree',
'eval' => ['mandatory' => true, 'fieldType' => 'radio', 'tl_class' => 'clr'],
'sql' => ['type' => 'integer', 'unsigned' => true, 'default' => 0],
];
6 changes: 6 additions & 0 deletions contao/languages/en/default.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

// Miscellaneous
$GLOBALS['TL_LANG']['MSC']['pagePassword'] = 'Password';
$GLOBALS['TL_LANG']['MSC']['pagePasswordInvalid'] = &$GLOBALS['TL_LANG']['ERR']['invalidPass'];
$GLOBALS['TL_LANG']['MSC']['pagePasswordSubmit'] = 'Submit';
4 changes: 4 additions & 0 deletions contao/languages/en/modules.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?php

// Frontend modules
$GLOBALS['TL_LANG']['FMD']['page_password'] = ['Page password'];
9 changes: 9 additions & 0 deletions contao/languages/en/tl_page.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

// Fields
$GLOBALS['TL_LANG']['tl_page']['passwordProtected'] = ['Protect page with password', 'Allow viewing the page only by entering a correct password. The setting is inherited by subpages.'];
$GLOBALS['TL_LANG']['tl_page']['password'] = ['Password', 'Please enter the page password.'];
$GLOBALS['TL_LANG']['tl_page']['passwordPage'] = ['Password page', 'Please choose the page which visitor will see if they are not authenticated. The selected page should contain the page password frontend module.'];

// Legends
$GLOBALS['TL_LANG']['tl_page']['password_legend'] = 'Password protection';
24 changes: 24 additions & 0 deletions contao/templates/modules/mod_page_password.html5
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php $this->extend('block_unsearchable'); ?>

<?php $this->block('content'); ?>

<form id="<?= $this->formId ?>" method="post">
<div class="formbody">
<input type="hidden" name="FORM_SUBMIT" value="<?= $this->formId ?>">
<input type="hidden" name="REQUEST_TOKEN" value="<?= $this->requestToken ?>">

<?php if ($this->error): ?>
<p class="error"><?= $this->error ?></p>
<?php endif; ?>
<div class="fields">
<div class="widget widget-password">
<label for="ctrl_<?= $this->formId ?>"><?= $this->trans('MSC.pagePassword') ?></label>
<input type="password" name="password" id="ctrl_<?= $this->formId ?>" class="text password" value="" autocomplete="" required>
</div>
</div>
<div class="widget widget-submit">
<button type="submit" class="submit"><?= $this->trans('MSC.pagePasswordSubmit') ?></button>
</div>
</form>

<?php $this->endblock(); ?>
Binary file added docs/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions ecs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

use PhpCsFixer\Fixer\Comment\HeaderCommentFixer;
use PhpCsFixer\Fixer\Whitespace\MethodChainingIndentationFixer;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symplify\EasyCodingStandard\ValueObject\Option;

return static function (ContainerConfigurator $containerConfigurator): void {
$containerConfigurator->import(__DIR__.'/tools/ecs/vendor/contao/easy-coding-standard/config/contao.php');

$parameters = $containerConfigurator->parameters();
$parameters->set(Option::PARALLEL, true);

$parameters->set(Option::SKIP, [
'/contao/*',
HeaderCommentFixer::class => null,
MethodChainingIndentationFixer::class => [
'*/DependencyInjection/Configuration.php',
],
]);

$parameters->set(Option::LINE_ENDING, "\n");
$parameters->set(Option::CACHE_DIRECTORY, sys_get_temp_dir().'/ecs_default_cache');
};
51 changes: 51 additions & 0 deletions src/Authenticator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace Codefog\PagePasswordBundle;

use Contao\PageModel;
use Symfony\Component\HttpFoundation\Session\SessionInterface;

class Authenticator
{
public function __construct(private SessionInterface $session)
{
}

public function isPageProtected(PageModel $pageModel): bool
{
$pageModel->loadDetails();

return $pageModel->passwordProtected && $pageModel->password;
}

public function isAuthenticated(PageModel $pageModel): bool
{
if (!$pageModel->passwordId) {
return false;
}

if (!$this->session->isStarted()) {
return false;
}

return $this->session->get($this->getSessionKey($pageModel)) === $pageModel->password;
}

public function authenticate(PageModel $pageModel, string $password): bool
{
$pageModel->loadDetails();

if (!$pageModel->passwordId || $pageModel->password !== $password) {
return false;
}

$this->session->set($this->getSessionKey($pageModel), $password);

return true;
}

private function getSessionKey(PageModel $pageModel): string
{
return sprintf('contao-page-password-%s', $pageModel->passwordId);
}
}
15 changes: 15 additions & 0 deletions src/CodefogPagePasswordBundle.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Codefog\PagePasswordBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class CodefogPagePasswordBundle extends Bundle
{
public function getPath(): string
{
return \dirname(__DIR__);
}
}
24 changes: 24 additions & 0 deletions src/ContaoManager/Plugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Codefog\PagePasswordBundle\ContaoManager;

use Codefog\PagePasswordBundle\CodefogPagePasswordBundle;
use Contao\CoreBundle\ContaoCoreBundle;
use Contao\ManagerPlugin\Bundle\BundlePluginInterface;
use Contao\ManagerPlugin\Bundle\Config\BundleConfig;
use Contao\ManagerPlugin\Bundle\Parser\ParserInterface;

class Plugin implements BundlePluginInterface
{
/**
* {@inheritdoc}
*/
public function getBundles(ParserInterface $parser)
{
return [
(new BundleConfig(CodefogPagePasswordBundle::class))->setLoadAfter([ContaoCoreBundle::class]),
];
}
}
57 changes: 57 additions & 0 deletions src/Controller/FrontendModule/PagePasswordController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

namespace Codefog\PagePasswordBundle\Controller\FrontendModule;

use Codefog\PagePasswordBundle\Authenticator;
use Codefog\PagePasswordBundle\EventSubscriber\AuthenticateSubscriber;
use Contao\Controller;
use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
use Contao\CoreBundle\Csrf\ContaoCsrfTokenManager;
use Contao\CoreBundle\Exception\PageNotFoundException;
use Contao\CoreBundle\ServiceAnnotation\FrontendModule;
use Contao\ModuleModel;
use Contao\PageModel;
use Contao\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\Translation\TranslatorInterface;

/**
* @FrontendModule("page_password", category="application")
*/
class PagePasswordController extends AbstractFrontendModuleController
{
public function __construct(
private Authenticator $authenticator,
private ContaoCsrfTokenManager $tokenManager,
private TranslatorInterface $translator,
)
{
}

protected function getResponse(Template $template, ModuleModel $model, Request $request): ?Response
{
if (!$request->attributes->has(AuthenticateSubscriber::REQUEST_ATTRIBUTE)
|| ($sourcePageModel = PageModel::findPublishedById($request->attributes->getInt(AuthenticateSubscriber::REQUEST_ATTRIBUTE))) === null
) {
throw new PageNotFoundException();
}

$formId = sprintf('page-password-%s', $model->id);

if ($request->request->get('FORM_SUBMIT') === $formId) {
$result = $this->authenticator->authenticate($sourcePageModel, $request->request->get('password'));

if ($result) {
Controller::reload();
} else {
$template->error = $this->translator->trans('MSC.pagePasswordInvalid', [], 'contao_default');
}
}

$template->formId = $formId;
$template->requestToken = $this->tokenManager->getDefaultTokenValue();

return $template->getResponse();
}
}
Loading

0 comments on commit 70ca75d

Please sign in to comment.