Premiere mouture application - Suppression fichiers inutiles

This commit is contained in:
nox 2019-09-18 00:31:59 +02:00
parent 46ec06dcd3
commit a0fd572948
6322 changed files with 648732 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
config/config.php
.idea
tmp
composer.lock

35
composer.json Normal file
View File

@ -0,0 +1,35 @@
{
"name": "nox/gmarche",
"authors": [
{
"name": "nox",
"email": "nox@axiom-team.fr"
}
],
"autoload": {
"psr-4": {
"Framework\\": "src/Framework",
"App\\": "src",
"Tests\\": "tests"
}
},
"require": {
"guzzlehttp/psr7": "^1.4",
"http-interop/response-sender": "^1.0",
"zendframework/zend-expressive-fastroute": "^2.0",
"twig/twig": "^2.4",
"php-di/php-di": "^5.4",
"pagerfanta/pagerfanta": "^2.1",
"middlewares/whoops": "0.4.1",
"doctrine/cache": "1.4",
"intervention/image": "2.4",
"swiftmailer/swiftmailer": "^6.0",
"ramsey/uuid": "^3.7"
},
"require-dev": {
"squizlabs/php_codesniffer": "^3.0",
"phpunit/phpunit": "^6.2",
"robmorgan/phinx": "0.8.1",
"fzaninotto/faker": "^1.8"
}
}

4
config.php Normal file
View File

@ -0,0 +1,4 @@
<?php
return [
'gmarche.prefix' => '/news'
];

View File

@ -0,0 +1,53 @@
<?php
use App\Framework\Twig\UrlExtension;
use Framework\Middleware\CsrfMiddleware;
use Framework\Renderer\RendererInterface;
use Framework\Renderer\TwigRendererFactory;
use Framework\Router;
use Framework\Router\RouterFactory;
use Framework\Router\RouterTwigExtension;
use Framework\Session\PHPSession;
use Framework\Session\SessionInterface;
use Framework\Twig\{
CsrfExtension, FlashExtension, FormExtension, PagerFantaExtension, TextExtension, TimeExtension
};
return [
'env' => \DI\env('ENV', 'production'),
/*'env' => \DI\env('ENV', 'development'),*/
'database.host' => 'localhost',
'database.username' => 'root',
'database.password' => '',
'database.name' => 'gmarche',
'views.path' => dirname(__DIR__) . '/views',
'twig.extensions' => [
\DI\get(RouterTwigExtension::class),
\DI\get(PagerFantaExtension::class),
\DI\get(TextExtension::class),
\DI\get(TimeExtension::class),
\DI\get(FlashExtension::class),
\DI\get(FormExtension::class),
\DI\get(CsrfExtension::class),
\DI\get(UrlExtension::class)
],
SessionInterface::class => \DI\object(PHPSession::class),
CsrfMiddleware::class => \DI\object()->constructor(\DI\get(SessionInterface::class)),
Router::class => \DI\factory(RouterFactory::class),
RendererInterface::class => \DI\factory(TwigRendererFactory::class),
\PDO::class => function (\Psr\Container\ContainerInterface $c) {
return new PDO(
'mysql:host='. $c->get('database.host') . ';dbname=' . $c->get('database.name'),
$c->get('database.username'),
$c->get('database.password'),
[
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]
);
},
// MAILER
'mail.to' => 'admin@gmarche-testmail.com',
'mail.from' => 'no-reply@admin.fr',
Swift_Mailer::class => \DI\factory(\Framework\SwiftMailerFactory::class)
];

85
gm.iml Normal file
View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="App\" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="false" packagePrefix="Tests\" />
<sourceFolder url="file://$MODULE_DIR$/src/Framework" isTestSource="false" packagePrefix="Framework\" />
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/container-interop/container-interop" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/cache" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/instantiator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/lexer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/egulias/email-validator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/fig/http-message-util" />
<excludeFolder url="file://$MODULE_DIR$/vendor/filp/whoops" />
<excludeFolder url="file://$MODULE_DIR$/vendor/fzaninotto/faker" />
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/psr7" />
<excludeFolder url="file://$MODULE_DIR$/vendor/http-interop/http-factory" />
<excludeFolder url="file://$MODULE_DIR$/vendor/http-interop/http-middleware" />
<excludeFolder url="file://$MODULE_DIR$/vendor/http-interop/response-sender" />
<excludeFolder url="file://$MODULE_DIR$/vendor/intervention/image" />
<excludeFolder url="file://$MODULE_DIR$/vendor/middlewares/utils" />
<excludeFolder url="file://$MODULE_DIR$/vendor/middlewares/whoops" />
<excludeFolder url="file://$MODULE_DIR$/vendor/myclabs/deep-copy" />
<excludeFolder url="file://$MODULE_DIR$/vendor/nikic/fast-route" />
<excludeFolder url="file://$MODULE_DIR$/vendor/pagerfanta/pagerfanta" />
<excludeFolder url="file://$MODULE_DIR$/vendor/paragonie/random_compat" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/manifest" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/version" />
<excludeFolder url="file://$MODULE_DIR$/vendor/php-di/invoker" />
<excludeFolder url="file://$MODULE_DIR$/vendor/php-di/php-di" />
<excludeFolder url="file://$MODULE_DIR$/vendor/php-di/phpdoc-reader" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-common" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-docblock" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/type-resolver" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpspec/prophecy" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-code-coverage" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-file-iterator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-text-template" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-timer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-token-stream" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/phpunit" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/phpunit-mock-objects" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/container" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-message" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/log" />
<excludeFolder url="file://$MODULE_DIR$/vendor/ralouphie/getallheaders" />
<excludeFolder url="file://$MODULE_DIR$/vendor/ramsey/uuid" />
<excludeFolder url="file://$MODULE_DIR$/vendor/robmorgan/phinx" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/code-unit-reverse-lookup" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/comparator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/diff" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/environment" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/exporter" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/global-state" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/object-enumerator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/object-reflector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/recursion-context" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/resource-operations" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/version" />
<excludeFolder url="file://$MODULE_DIR$/vendor/squizlabs/php_codesniffer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/swiftmailer/swiftmailer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/config" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/console" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/debug" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/filesystem" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-ctype" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-iconv" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-idn" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-mbstring" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php72" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/yaml" />
<excludeFolder url="file://$MODULE_DIR$/vendor/theseer/tokenizer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/twig/twig" />
<excludeFolder url="file://$MODULE_DIR$/vendor/webimpress/composer-extra-dependency" />
<excludeFolder url="file://$MODULE_DIR$/vendor/webimpress/http-middleware-compatibility" />
<excludeFolder url="file://$MODULE_DIR$/vendor/webmozart/assert" />
<excludeFolder url="file://$MODULE_DIR$/vendor/zendframework/zend-expressive-fastroute" />
<excludeFolder url="file://$MODULE_DIR$/vendor/zendframework/zend-expressive-router" />
<excludeFolder url="file://$MODULE_DIR$/vendor/zendframework/zend-stdlib" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

28
phinx.php Normal file
View File

@ -0,0 +1,28 @@
<?php
require 'public/index.php';
$migrations = [];
$seeds = [];
foreach ($app->getModules() as $module) {
if ($module::MIGRATIONS) {
$migrations[] = $module::MIGRATIONS;
}
if ($module::SEEDS) {
$seeds[] = $module::SEEDS;
}
}
return [
'paths' => [
'migrations' => $migrations,
'seeds' => $seeds
],
'environments' => [
'default_database' => 'development',
'development' => [
'adapter' => 'mysql',
'host' => $app->getContainer()->get('database.host'),
'name' => $app->getContainer()->get('database.name'),
'user' => $app->getContainer()->get('database.username'),
'pass' => $app->getContainer()->get('database.password')
]
]
];

14
phpcs.xml Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" ?>
<ruleset name="MonFramework">
<description>Mes règles de formattage</description>
<arg name="colors"/>
<arg value="p"/>
<file>src</file>
<file>public/index.php</file>
<rule ref="PSR2">
<exclude name="PSR1.Classes.ClassDeclaration.MissingNamespace"/>
</rule>
</ruleset>

7
phpunit.xml Normal file
View File

@ -0,0 +1,7 @@
<phpunit bootstrap="./vendor/autoload.php">
<testsuites>
<testsuite name="Framework">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>

BIN
public/avatar_user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

199
public/css/style.css Normal file
View File

@ -0,0 +1,199 @@
* {
box-sizing: border-box;
}
btn-custom {
background-color: hsl(0, 0%, 79%) !important;
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#134134134", endColorstr="#c9c9c9");
background-image: -khtml-gradient(linear, left top, left bottom, from(#134134134), to(#c9c9c9));
background-image: -moz-linear-gradient(top, #134134134, #c9c9c9);
background-image: -ms-linear-gradient(top, #134134134, #c9c9c9);
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #134134134), color-stop(100%, #c9c9c9));
background-image: -webkit-linear-gradient(top, #134134134, #c9c9c9);
background-image: -o-linear-gradient(top, #134134134, #c9c9c9);
background-image: linear-gradient(#134134134, #c9c9c9);
border-color: #c9c9c9 #c9c9c9 hsl(0, 0%, 68.5%);
color: #333 !important;
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.69);
-webkit-font-smoothing: antialiased;
}
img {
max-width: 100%;
height: auto;
}
a :hover {
background-color: lightgreen;
text-decoration: none;
}
li {
list-style-type: none;
text-color: black;
}
.liste_regions, .liste_antennes {
background-color: lightblue;
}
.fond {
background-image: url("../images/bildreich_1275.jpg");
background-repeat: no-repeat;
background-attachment:fixed;
background-position: center;
background-size: 100% auto;
font-family: Bree serif,"Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 18px;
}
header.header {
width: 389px;
height: 259px;
margin: 0 auto;
margin-bottom: 10px;
text-align: center;
}
header.header img {
width: 389px;
height: 259px;
}
.logo_toile img, .logo_june img {
transition: transform .5s;
}
.logo_toile img:hover, .logo_june img:hover {
transform: scale(1.1) rotate(10deg);
}
.post-header {
width: 820px;
height: 80px;
margin: 0 auto;
color: white;
opacity: 0.9;
font-family: Bree serif, Arial;
font-size: 26px;
}
#la_page {
text-align: center;
width: 1174px;
min-height: 901px;
height:auto;
margin-left: auto;
margin-right: auto;
}
.article {
width:auto;
height:auto;
font-size: 24px;
}
.article2 {
width:auto;
height:auto;
font-size: 24px;
}
.container {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.records_content th, .records_content2 th{
background-color: lightgray;
color: rgba(0,0,0,0.85);
}
.footer {
width:105px;
margin: 5px auto;
text-align: center;
}
/******************************************************************/
/* POUR MOBILES */
/******************************************************************/
@media only screen and (max-width:560px) {
.fond {
background-color: black;
background-image: url("../toureiffel.jpeg");
background-repeat: no-repeat;
background-attachment: fixed;
background-position: left;
background-size: cover;
}
.post-header {
width: auto;
height: auto;
margin-left: 10px;
}
#la_page {
width: auto;
height:auto;
margin-left: auto;
margin-right: auto;
}
.article {
width: auto;
height: auto;
margin-left: 10px;
margin-bottom: 10px;
}
.article2 {
width: auto;
height: auto;
margin-left: 10px;
margin-bottom: 10px;
}
.container button{
float: left;
}
/**************************************/
/* Tableaux responsives */
/**************************************/
.table-responsive table,
.table-responsive thead,
.table-responsive tbody,
.table-responsive tr,
.table-responsive th,
.table-responsive td {
display: block;
}
.table-responsive thead {
display: none;
}
.table-responsive td {
padding-left: 95px !important;
position: relative;
margin-top: -1px;
background: #FFF;
}
.table-responsive td button{
margin-top:-7px;
max-height: 30px;
}
.table-responsive td:nth-child(odd) {
background-color: #eee;
}
.table-responsive td::before {
padding: 10px;
content: attr(data-label);
position: absolute;
top: 0;
left: 0;
width: 85px;
bottom: 0;
background-color: #000;
color: #FFF;
display: flex;
align-items: center;
font-weight: bold;
}
.table-responsive tr {
margin-bottom: 1rem;
}
.table-responsive th + td {
padding-left: 10px;
}
}

BIN
public/images/attilax.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
public/images/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
public/images/logo_home.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

BIN
public/images/nox.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

BIN
public/images/poka.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

62
public/index.php Normal file
View File

@ -0,0 +1,62 @@
<?php
ini_set('display_errors',0);
use App\Account\AccountModule;
use App\Admin\AdminModule;
use App\Auth\AuthModule;
use App\Contact\ContactModule;
use App\Gmarche\GmarcheModule;
use App\Product\ProductModule;
use Framework\Auth\RoleMiddlewareFactory;
use Framework\Middleware\{
CsrfMiddleware,
DispatcherMiddleware,
MethodMiddleware,
RendererRequestMiddleware,
RouterMiddleware,
TrailingSlashMiddleware,
NotFoundMiddleware
};
use GuzzleHttp\Psr7\ServerRequest;
use Middlewares\Whoops;
chdir(dirname(__DIR__));
require 'vendor/autoload.php';
$chemin = $_SERVER['DOCUMENT_ROOT'];
$chemin_new = substr($chemin,0,-7);
$app = (new \Framework\App( $chemin_new .'/config/config.php'))
//$app = (new \Framework\App( '../config/config.php'))
->addModule(AdminModule::class)
->addModule(ContactModule::class)
->addModule(ProductModule::class)
->addModule(GmarcheModule::class)
->addModule(AuthModule::class)
->addModule(AccountModule::class);
$container = $app->getContainer();
// Pose problème
$container->get(\Framework\Router::class)->get('/', \App\Gmarche\Actions\RegionIndexAction::class, 'home');
$app->pipe(Whoops::class);
$app->pipe(TrailingSlashMiddleware::class);
$app->pipe(\App\Auth\ForbiddenMiddleware::class);
// admin pose problème
//$app->pipe(
// $container->get('admin.prefix'),
// $container->get(RoleMiddlewareFactory::class)->makeForRole('admin')
// );
$app->pipe(MethodMiddleware::class)
->pipe(RendererRequestMiddleware::class)
// ->pipe(CsrfMiddleware::class)
->pipe(RouterMiddleware::class)
->pipe(DispatcherMiddleware::class)
->pipe(NotFoundMiddleware::class);
if (php_sapi_name() !== "cli") {
$response = $app->run(ServerRequest::fromGlobals());
\Http\Response\send($response);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

View File

@ -0,0 +1,27 @@
<?php
namespace App\Account;
use App\Account\Action\AccountAction;
use App\Account\Action\AccountEditAction;
use App\Account\Action\SignupAction;
use Framework\Auth\LoggedInMiddleware;
use Framework\Module;
use Framework\Renderer\RendererInterface;
use Framework\Router;
class AccountModule extends Module
{
const MIGRATIONS = __DIR__ . '/migrations';
const DEFINITIONS = __DIR__ . '/definitions.php';
public function __construct(Router $router, RendererInterface $renderer)
{
$renderer->addPath('account', __DIR__ . '/views');
$router->get('/inscription', SignupAction::class, 'account.signup');
$router->post('/inscription', SignupAction::class);
$router->get('/mon-profil', [LoggedInMiddleware::class, AccountAction::class], 'account');
$router->post('/mon-profil', [LoggedInMiddleware::class, AccountEditAction::class]);
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Account\Action;
use Framework\Auth;
use Framework\Renderer\RendererInterface;
use Psr\Http\Message\ServerRequestInterface;
class AccountAction
{
/**
* @var RendererInterface
*/
private $renderer;
/**
* @var Auth
*/
private $auth;
public function __construct(
RendererInterface $renderer,
Auth $auth
) {
$this->renderer = $renderer;
$this->auth = $auth;
}
public function __invoke(ServerRequestInterface $request)
{
$user = $this->auth->getUser();
return $this->renderer->render('@account/account', compact('user'));
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace App\Account\Action;
use App\Auth\UserTable;
use Framework\Auth;
use Framework\Renderer\RendererInterface;
use Framework\Response\RedirectResponse;
use Framework\Session\FlashService;
use Framework\Validator;
use Psr\Http\Message\ServerRequestInterface;
class AccountEditAction
{
/**
* @var RendererInterface
*/
private $renderer;
/**
* @var Auth
*/
private $auth;
/**
* @var FlashService
*/
private $flashService;
/**
* @var UserTable
*/
private $userTable;
public function __construct(
RendererInterface $renderer,
Auth $auth,
FlashService $flashService,
UserTable $userTable
) {
$this->renderer = $renderer;
$this->auth = $auth;
$this->flashService = $flashService;
$this->userTable = $userTable;
}
public function __invoke(ServerRequestInterface $request)
{
$user = $this->auth->getUser();
$params = $request->getParsedBody();
$validator = (new Validator($params))
->confirm('password')
->required('firstname', 'lastname');
if ($validator->isValid()) {
$userParams = [
'firstname' => $params['firstname'],
'lastname' => $params['lastname']
];
if (!empty($params['password'])) {
$userParams['password'] = password_hash($params['password'], PASSWORD_DEFAULT);
}
$this->userTable->update($user->id, $userParams);
$this->flashService->success('Votre compte a bien été mis à jour');
return new RedirectResponse($request->getUri()->getPath());
}
$errors = $validator->getErrors();
return $this->renderer->render('@account/account', compact('user', 'errors'));
}
}

View File

@ -0,0 +1,99 @@
<?php
namespace App\Account\Action;
use App\Auth\DatabaseAuth;
use App\Auth\User;
use App\Auth\UserTable;
use Framework\Database\Hydrator;
use Framework\Renderer\RendererInterface;
use Framework\Response\RedirectResponse;
use Framework\Router;
use Framework\Session\FlashService;
use Framework\Validator;
use Psr\Http\Message\ServerRequestInterface;
class SignupAction
{
/**
* @var RendererInterface
*/
private $renderer;
/**
* @var UserTable
*/
private $userTable;
/**
* @var Router
*/
private $router;
/**
* @var DatabaseAuth
*/
private $auth;
/**
* @var FlashService
*/
private $flashService;
public function __construct(
RendererInterface $renderer,
UserTable $userTable,
Router $router,
DatabaseAuth $auth,
FlashService $flashService
) {
$this->renderer = $renderer;
$this->userTable = $userTable;
$this->router = $router;
$this->auth = $auth;
$this->flashService = $flashService;
}
public function __invoke(ServerRequestInterface $request)
{
if ($request->getMethod() === 'GET') {
return $this->renderer->render('@account/signup');
}
$params = $request->getParsedBody();
$validator = (new Validator($params))
->required('username', 'email', 'password', 'password_confirm', 'firstname', 'lastname', 'created_at')
->length('username', 3)
->length('firstname', 2)
->length('lastname', 2)
->email('email')
->confirm('password')
->length('password', 4)
->unique('username', $this->userTable)
->unique('email', $this->userTable);
if ($validator->isValid()) {
$userParams = [
'username' => $params['username'],
'firstname'=> $params['firstname'],
'lastname'=> $params['lastname'],
'email' => $params['email'],
'password' => password_hash($params['password'], PASSWORD_DEFAULT),
'created_at' => $params['created_at'],
'role' => 'user'
];
$this->userTable->insert($userParams);
$user = Hydrator::hydrate($userParams, User::class);
$user->id = $this->userTable->getPdo()->lastInsertId();
$this->auth->setUser($user);
$this->flashService->success('Votre compte a bien été créé');
return new RedirectResponse($this->router->generateUri('account'));
}
$errors = $validator->getErrors();
return $this->renderer->render('@account/signup', [
'errors' => $errors,
'user' => [
'username' => $params['username'],
'email' => $params['email'],
'firstname'=> $params['firstname'],
'lastname'=> $params['lastname'],
'created_at' => $params['created_at']
]
]);
}
}

74
src/Account/User.php Normal file
View File

@ -0,0 +1,74 @@
<?php
namespace App\Account;
class User extends \App\Auth\User
{
/**
* @var string
*/
public $firstname;
/**
* @var string
*/
public $lastname;
/**
* @var string
*/
private $role;
public function getRoles(): array
{
return [$this->role];
}
/**
* @return string
*/
public function getFirstname(): string
{
return $this->firstname;
}
/**
* @param string $firstname
*/
public function setFirstname(string $firstname)
{
$this->firstname = $firstname;
}
/**
* @return string
*/
public function getLastname(): string
{
return $this->lastname;
}
/**
* @param string $lastname
*/
public function setLastname(string $lastname)
{
$this->lastname = $lastname;
}
/**
* @return mixed
*/
public function getRole()
{
return $this->role;
}
/**
* @param mixed $role
*/
public function setRole($role)
{
$this->role = $role;
}
}

View File

@ -0,0 +1,4 @@
<?php
return [
'auth.entity' => \App\Account\User::class
];

View File

@ -0,0 +1,18 @@
{% extends 'layout.twig' %}
{% block body %}
<div class="container" style="background-color: lightgray;opacity: 0.9;margin: 1rem;padding:1rem;">
<h2>Mon compte</h2>
<form action="" method="post">
{{ csrf_input() }}
{{ field('firstname', user.firstname, 'Prénom') }}
{{ field('lastname', user.lastname, 'Nom') }}
<h2>Changer de mot de passe</h2>
<p>Laissez vide pour ne rien changer</p>
{{ field('password', null, 'Mot de passe', {type: 'password'}) }}
{{ field('password_confirm', null, 'Confirmer le mot de passe', {type: 'password'}) }}
<button class="btn btn-primary">Modifier mes informations</button>
</form>
</div>
{% endblock %}

View File

@ -0,0 +1,16 @@
{% extends 'layout.twig' %}
{% block body %}
<div class="container" style="background-color: lightgray;opacity: 0.9;margin: 1rem;padding:1rem;">
<form action="{{ path('account.signup') }}" method="post">
{{ csrf_input() }}
{{ field('username', user.username, "Pseudo") }}
{{ field('firstname', user.firstname, "Prénom") }} {{ field('lastname', user.lastname, "Nom") }}
{{ field('email', user.email, "Email", {type: 'email'}) }}
{{ field('password', null, "Mot de passe", {type: 'password'}) }}
{{ field('password_confirm', null, "Confirmez le mot de passe", {type: 'password'}) }}
{{ field('created_at', date(), null, {type: 'hidden'}) }}
<button class="btn btn-primary">S'inscrire</button>
</form>
</div>
{% endblock %}

31
src/Admin/AdminModule.php Normal file
View File

@ -0,0 +1,31 @@
<?php
namespace App\Admin;
use App\Product\Actions\ProductIndexAction;
use App\Product\Actions\ProductCrudAction;
use Framework\Module;
use Framework\Renderer\RendererInterface;
use Framework\Renderer\TwigRenderer;
use Framework\Router;
class AdminModule extends Module
{
const DEFINITIONS = __DIR__ . '/config.php';
public function __construct(
RendererInterface $renderer,
Router $router,
AdminTwigExtension $adminTwigExtension,
string $prefix
) {
$renderer->addPath('admin', __DIR__ . '/views');
//$router->get('/machin', ProductCrudAction::class, 'machin.bidule'); // ProductIndexAction
/*if ($renderer instanceof TwigRenderer) {
$renderer->getTwig()->addExtension($adminTwigExtension);
}*/
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Admin;
class AdminTwigExtension extends \Twig_Extension
{
/**
* @var array
*/
private $widgets;
public function __construct(array $widgets)
{
$this->widgets = $widgets;
}
public function getFunctions(): array
{
return [
new \Twig_SimpleFunction('admin_menu', [$this, 'renderMenu'], ['is_safe' => ['html']])
];
}
public function renderMenu(): string
{
return array_reduce($this->widgets, function (string $html, AdminWidgetInterface $widget) {
return $html . $widget->renderMenu();
}, '');
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Admin;
interface AdminWidgetInterface
{
public function render(): string;
public function renderMenu(): string;
}

12
src/Admin/config.php Normal file
View File

@ -0,0 +1,12 @@
<?php
use App\Admin\AdminModule;
use App\Admin\DashboardAction;
return [
'admin.prefix' => '/admin',
'admin.widgets' => [],
\App\Admin\AdminTwigExtension::class => \DI\object()->constructor(\DI\get('admin.widgets')),
AdminModule::class => \DI\object()->constructorParameter('prefix', \DI\get('admin.prefix'))
//DashboardAction::class => \DI\object()->constructorParameter('widgets', \DI\get('admin.widgets'))
];

View File

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html>
<head>
<title>{% block title "Mon site " %}</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/flatpickr/3.0.7/flatpickr.css">
<style>
body {
padding-top: 5rem;
}
</style>
</head>
<body>
<nav class="navbar fixed-top navbar-expand-sm navbar-dark bg-dark">
<a class="navbar-brand" href="{{ path(routePrefix) }}">Ğ1-Marché</a>
<ul class="navbar-nav mr-auto">
{{ admin_menu() }}
</ul>
<div class="navbar-nav">
<form class="nav-item active" method="post" action="{{ path('auth.logout') }}">
{{ csrf_input() }}
<button class="btn-primary btn-danger">Se déconnecter</button>
</form>
</div>
</nav>
<div class="container">
{% if flash('success') %}
<div class="alert alert-success">
{{ flash('success') }}
</div>
{% endif %}
{% if flash('error') %}
<div class="alert alert-danger">
{{ flash('error') }}
</div>
{% endif %}
{% block body %}{% endblock %}
</div><!-- /.container -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js" integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js" integrity="sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/flatpickr/3.0.7/flatpickr.js"></script>
<script>
flatpickr('.datepicker', {
enableTime: true,
altInput: true,
altFormat: 'j F Y, H:i',
dateFormat: 'Y-m-d H:i:S'
})
</script>
</body>
</html>

View File

@ -0,0 +1,24 @@
<?php
namespace App\Auth\Action;
use Framework\Renderer\RendererInterface;
use Psr\Http\Message\ServerRequestInterface;
class LoginAction
{
/**
* @var RendererInterface
*/
private $renderer;
public function __construct(RendererInterface $renderer)
{
$this->renderer = $renderer;
}
public function __invoke(ServerRequestInterface $request)
{
return $this->renderer->render('@auth/login');
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace App\Auth\Action;
use App\Auth\DatabaseAuth;
use Framework\Actions\RouterAwareAction;
use Framework\Renderer\RendererInterface;
use Framework\Response\RedirectResponse;
use Framework\Router;
use Framework\Session\FlashService;
use Framework\Session\SessionInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Expressive\Router\RouterInterface;
class LoginAttemptAction
{
/**
* @var RendererInterface
*/
private $renderer;
/**
* @var DatabaseAuth
*/
private $auth;
/**
* @var SessionInterface
*/
private $session;
/**
* @var RouterInterface
*/
private $router;
use RouterAwareAction;
public function __construct(
RendererInterface $renderer,
DatabaseAuth $auth,
Router $router,
SessionInterface $session
) {
$this->renderer = $renderer;
$this->auth = $auth;
$this->router = $router;
$this->session = $session;
}
public function __invoke(ServerRequestInterface $request)
{
$params = $request->getParsedBody();
$user = $this->auth->login($params['username'], $params['password']);
if ($user) {
$path = $this->session->get('auth.redirect') ?: $this->router->generateUri('gmarche.index');
$this->session->delete('auth.redirect');
return new RedirectResponse($path);
} else {
(new FlashService($this->session))->error('Identifiant ou mot de passe incorrect');
return $this->redirect('auth.login');
}
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Auth\Action;
use App\Auth\DatabaseAuth;
use Framework\Renderer\RendererInterface;
use Framework\Response\RedirectResponse;
use Framework\Session\FlashService;
use Psr\Http\Message\ServerRequestInterface;
class LogoutAction
{
/**
* @var RendererInterface
*/
private $renderer;
/**
* @var DatabaseAuth
*/
private $auth;
/**
* @var FlashService
*/
private $flashService;
public function __construct(RendererInterface $renderer, DatabaseAuth $auth, FlashService $flashService)
{
$this->renderer = $renderer;
$this->auth = $auth;
$this->flashService = $flashService;
}
public function __invoke(ServerRequestInterface $request)
{
$this->auth->logout();
$this->flashService->success('Vous êtes maintenant déconnecté');
return new RedirectResponse('/gmarche');
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace App\Auth\Action;
use App\Auth\Mailer\PasswordResetMailer;
use App\Auth\UserTable;
use Framework\Database\NoRecordException;
use Framework\Renderer\RendererInterface;
use Framework\Response\RedirectResponse;
use Framework\Session\FlashService;
use Framework\Validator;
use Psr\Http\Message\ServerRequestInterface;
class PasswordForgetAction
{
/**
* @var RendererInterface
*/
private $renderer;
/**
* @var UserTable
*/
private $userTable;
/**
* @var PasswordResetMailer
*/
private $mailer;
/**
* @var FlashService
*/
private $flashService;
public function __construct(
RendererInterface $renderer,
UserTable $userTable,
PasswordResetMailer $mailer,
FlashService $flashService
) {
$this->renderer = $renderer;
$this->userTable = $userTable;
$this->mailer = $mailer;
$this->flashService = $flashService;
}
public function __invoke(ServerRequestInterface $request)
{
if ($request->getMethod() === 'GET') {
return $this->renderer->render('@auth/password');
}
$params = $request->getParsedBody();
$validator = (new Validator($params))
->notEmpty('email')
->email('email');
if ($validator->isValid()) {
try {
$user = $this->userTable->findBy('email', $params['email']);
$token = $this->userTable->resetPassword($user->id);
$this->mailer->send($user->email, [
'id' => $user->id,
'token' => $token
]);
$this->flashService->success('Un email vous a été envoyé');
return new RedirectResponse($request->getUri()->getPath());
} catch (NoRecordException $e) {
$errors = ['email' => 'Aucun utilisateur ne correspon à cet email'];
}
} else {
$errors = $validator->getErrors();
}
return $this->renderer->render('@auth/password', compact('errors'));
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace App\Auth\Action;
use App\Auth\User;
use App\Auth\UserTable;
use Framework\Renderer\RendererInterface;
use Framework\Response\RedirectResponse;
use Framework\Router;
use Framework\Session\FlashService;
use Framework\Validator;
use Psr\Http\Message\ServerRequestInterface;
class PasswordResetAction
{
/**
* @var RendererInterface
*/
private $renderer;
/**
* @var UserTable
*/
private $userTable;
/**
* @var Router
*/
private $router;
/**
* @var FlashService
*/
private $flashService;
public function __construct(
RendererInterface $renderer,
UserTable $userTable,
FlashService $flashService,
Router $router
) {
$this->renderer = $renderer;
$this->userTable = $userTable;
$this->router = $router;
$this->flashService = $flashService;
}
public function __invoke(ServerRequestInterface $request)
{
/** @var User $user */
$user = $this->userTable->find($request->getAttribute('id'));
if ($user->getPasswordReset() !== null &&
$user->getPasswordReset() === $request->getAttribute('token') &&
time() - $user->getPasswordResetAt()->getTimestamp() < 600
) {
if ($request->getMethod() === 'GET') {
return $this->renderer->render('@auth/reset');
} else {
$params = $request->getParsedBody();
$validator = (new Validator($params))
->length('password', 4)
->confirm('password');
if ($validator->isValid()) {
$this->userTable->updatePassword($user->getId(), $params['password']);
$this->flashService->success('Votre mot de passe a bien été changé');
return new RedirectResponse($this->router->generateUri('auth.login'));
} else {
$errors = $validator->getErrors();
return $this->renderer->render('@auth/reset', compact('errors'));
}
}
} else {
$this->flashService->error('Token invalid');
return new RedirectResponse($this->router->generateUri('auth.password'));
}
}
}

33
src/Auth/AuthModule.php Normal file
View File

@ -0,0 +1,33 @@
<?php
namespace App\Auth;
use App\Auth\Action\LoginAction;
use App\Auth\Action\LoginAttemptAction;
use App\Auth\Action\LogoutAction;
use App\Auth\Action\PasswordForgetAction;
use App\Auth\Action\PasswordResetAction;
use Framework\Module;
use Framework\Renderer\RendererInterface;
use Framework\Router;
use Framework\Router\Route;
use Psr\Container\ContainerInterface;
class AuthModule extends Module
{
const DEFINITIONS = __DIR__ . '/config.php';
const MIGRATIONS = __DIR__ . '/db/migrations';
const SEEDS = __DIR__ . '/db/seeds';
public function __construct(ContainerInterface $container, Router $router, RendererInterface $renderer)
{
$renderer->addPath('auth', __DIR__ . '/views');
$router->get($container->get('auth.login'), LoginAction::class, 'auth.login');
$router->post($container->get('auth.login'), LoginAttemptAction::class);
$router->post('/logout', LogoutAction::class, 'auth.logout');
$router->any('/password', PasswordForgetAction::class, 'auth.password');
$router->any('/password/reset/{id:\d+}/{token}', PasswordResetAction::class, 'auth.reset');
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Auth;
use Framework\Auth;
class AuthTwigExtension extends \Twig_Extension
{
/**
* @var Auth
*/
private $auth;
public function __construct(Auth $auth)
{
$this->auth = $auth;
}
public function getFunctions()
{
return [
new \Twig_SimpleFunction('current_user', [$this->auth, 'getUser'])
];
}
}

81
src/Auth/DatabaseAuth.php Normal file
View File

@ -0,0 +1,81 @@
<?php
namespace App\Auth;
use Framework\Auth;
use Framework\Auth\User;
use Framework\Database\NoRecordException;
use Framework\Session\SessionInterface;
class DatabaseAuth implements Auth
{
/**
* @var UserTable
*/
private $userTable;
/**
* @var SessionInterface
*/
private $session;
/**
* @var \App\Auth\User
*/
private $user;
public function __construct(UserTable $userTable, SessionInterface $session)
{
$this->userTable = $userTable;
$this->session = $session;
}
public function login(string $username, string $password): ?User
{
if (empty($username) || empty($password)) {
return null;
}
/** @var \App\Auth\User $user */
$user = $this->userTable->findBy('username', $username);
if ($user && password_verify($password, $user->password)) {
$this->setUser($user);
return $user;
}
return null;
}
public function logout(): void
{
$this->session->delete('auth.user');
}
/**
* @return User|null
*/
public function getUser(): ?User
{
if ($this->user) {
return $this->user;
}
$userId = $this->session->get('auth.user');
if ($userId) {
try {
$this->user = $this->userTable->find($userId);
return $this->user;
} catch (NoRecordException $exception) {
$this->session->delete('auth.user');
return null;
}
}
return null;
}
public function setUser(\App\Auth\User $user): void
{
$this->session->set('auth.user', $user->id);
$this->user = $user;
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace App\Auth;
use Framework\Auth\ForbiddenException;
use Framework\Response\RedirectResponse;
use Framework\Session\FlashService;
use Framework\Session\SessionInterface;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class ForbiddenMiddleware implements MiddlewareInterface
{
/**
* @var string
*/
private $loginPath;
/**
* @var SessionInterface
*/
private $session;
public function __construct(string $loginPath, SessionInterface $session)
{
$this->loginPath = $loginPath;
$this->session = $session;
}
/**
* @param ServerRequestInterface $request
* @param DelegateInterface $delegate
* @return ResponseInterface
* @throws \TypeError
*/
public function process(ServerRequestInterface $request, DelegateInterface $delegate): ResponseInterface
{
try {
return $delegate->process($request);
} catch (ForbiddenException $exception) {
return $this->redirectLogin($request);
} catch (\TypeError $error) {
if (strpos($error->getMessage(), \Framework\Auth\User::class) !== false) {
return $this->redirectLogin($request);
}
throw $error;
}
}
public function redirectLogin(ServerRequestInterface $request): ResponseInterface
{
$this->session->set('auth.redirect', $request->getUri()->getPath());
(new FlashService($this->session))->error('Vous devez posséder un compte pour accéder à cette page');
return new RedirectResponse($this->loginPath);
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace App\Auth\Mailer;
use Framework\Renderer\RendererInterface;
class PasswordResetMailer
{
/**
* @var \Swift_Mailer
*/
private $mailer;
/**
* @var RendererInterface
*/
private $renderer;
/**
* @var string
*/
private $from;
public function __construct(\Swift_Mailer $mailer, RendererInterface $renderer, string $from)
{
$this->mailer = $mailer;
$this->renderer = $renderer;
$this->from = $from;
}
public function send(string $to, array $params)
{
$message = new \Swift_Message(
'Réinitialisation de votre mot de passe',
$this->renderer->render('@auth/email/password.text', $params)
);
$message->addPart($this->renderer->render('@auth/email/password.html', $params), 'text/html');
$message->setTo($to);
$message->setFrom($this->from);
$this->mailer->send($message);
}
}

135
src/Auth/User.php Normal file
View File

@ -0,0 +1,135 @@
<?php
namespace App\Auth;
class User implements \Framework\Auth\User
{
public $id;
public $username;
public $email;
public $password;
// public $firstname;
// public $lastname;
public $passwordReset;
public $passwordResetAt;
/**
* @return mixed
*/
public function getFirstname()
{
return $this->firstname;
}
/**
* @return mixed
*/
public function getLastname()
{
return $this->lastname;
}
/**
* @return string
*/
public function getUsername(): string
{
return $this->username;
}
/**
* @return string[]
*/
public function getRoles(): array
{
return [];
}
/**
* @return mixed
*/
public function getPasswordReset()
{
return $this->passwordReset;
}
/**
* @param mixed $passwordReset
*/
public function setPasswordReset($passwordReset)
{
$this->passwordReset = $passwordReset;
}
public function setPasswordResetAt($date)
{
if (is_string($date)) {
$this->passwordResetAt = new \DateTime($date);
} else {
$this->passwordResetAt = $date;
}
}
/**
* @return mixed
*/
public function getPasswordResetAt(): ?\DateTime
{
return $this->passwordResetAt;
}
/**
* @return mixed
*/
public function getEmail()
{
return $this->email;
}
/**
* @param mixed $email
*/
public function setEmail($email)
{
$this->email = $email;
}
/**
* @return mixed
*/
public function getPassword()
{
return $this->password;
}
/**
* @param mixed $password
*/
public function setPassword($password)
{
$this->password = $password;
}
/**
* @return mixed
*/
public function getId()
{
return $this->id;
}
/**
* @param mixed $id
*/
public function setId($id)
{
$this->id = $id;
}
}

36
src/Auth/UserTable.php Normal file
View File

@ -0,0 +1,36 @@
<?php
namespace App\Auth;
use Framework\Database\Table;
use Ramsey\Uuid\Uuid;
class UserTable extends Table
{
protected $table = "users";
public function __construct(\PDO $pdo, string $entity = User::class)
{
$this->entity = $entity;
parent::__construct($pdo);
}
public function resetPassword(int $id): string
{
$token = Uuid::uuid4()->toString();
$this->update($id, [
'password_reset' => $token,
'password_reset_at' => date('Y-m-d H:i:s')
]);
return $token;
}
public function updatePassword(int $id, string $password): void
{
$this->update($id, [
'password' => password_hash($password, PASSWORD_DEFAULT),
'password_reset' => null,
'password_reset_at' => null
]);
}
}

22
src/Auth/config.php Normal file
View File

@ -0,0 +1,22 @@
<?php
use App\Auth\DatabaseAuth;
use App\Auth\ForbiddenMiddleware;
use App\Auth\Mailer\PasswordResetMailer;
use Framework\Auth;
return [
'auth.login' => '/login',
'auth.entity' => \App\Auth\User::class,
'twig.extensions' => \DI\add([
\Di\get(\App\Auth\AuthTwigExtension::class)
]),
Auth\User::class => \DI\factory(function (Auth $auth) {
return $auth->getUser();
})->parameter('auth', \DI\get(Auth::class)),
Auth::class => \DI\get(DatabaseAuth::class),
\App\Auth\UserTable::class => \DI\object()->constructorParameter('entity', \DI\get('auth.entity')),
ForbiddenMiddleware::class => \DI\object()->constructorParameter('loginPath', \DI\get('auth.login')),
PasswordResetMailer::class => \DI\object()->constructorParameter('from', \DI\get('mail.from'))
];

View File

@ -0,0 +1,17 @@
<?php
use Phinx\Seed\AbstractSeed;
class UserSeeder extends AbstractSeed
{
public function run()
{
$this->table('users')
->insert([
'username' => 'admin',
'email' => 'admin@admin.fr',
'password' => password_hash('admin', PASSWORD_DEFAULT)
])
->save();
}
}

View File

@ -0,0 +1,6 @@
<p>
Vous avez demandé la réinitialisation de votre de mot de passe.
</p>
<p>
<a href="{{ domain }}{{ path('auth.reset', {id: id, token: token}) }}">{{ domain }}{{ path('auth.reset', {id: id, token: token}) }}</a>
</p>

View File

@ -0,0 +1,3 @@
Vous avez demandé la réinitialisation de votre de mot de passe.
{{ domain }}{{ path('auth.reset', {id: id, token: token}) }}

27
src/Auth/views/login.twig Normal file
View File

@ -0,0 +1,27 @@
{% extends 'layout.twig' %}
{% block body %}
{% if flash('error') %}
<div class="alert alert-danger">
{{ flash('error') }}
</div>
{% endif %}
<!-- if flash('success') %}
<div class="alert alert-danger">
flash('success') }}
</div>
endif -->
<div class="container" style="background-color: lightgray;opacity: 0.9;margin: 1rem;padding:1rem;">
<form action="{{ path('auth.login') }}" method="post">
{{ csrf_input() }}
{{ field('username', null, 'Nom d\'utilisateur') }}
{{ field('password', null, 'Mot de passe', {type: 'password'}) }}
<p><a href="{{ path('auth.password') }}">Mot de passe oublié ?</a></p>
<button class="btn btn-primary">Se connecter</button>
</form>
</div>
{% endblock %}

View File

@ -0,0 +1,24 @@
{% extends 'layout.twig' %}
{% block body %}
{% if flash('error') %}
<div class="alert alert-danger">
{{ flash('error') }}
</div>
{% endif %}
<!-- if flash('success') %}
<div class="alert alert-success">
flash('success')
</div>
endif -->
<div class="container" style="background-color: lightgray;opacity: 0.9;margin: 1rem;padding:1rem;">
<form action="" method="post">
{{ csrf_input() }}
{{ field('email', null, 'Email', {type: 'email'}) }}
<button class="btn btn-primary">Réinitialiser mon mot de passe</button>
</form>
</div>
{% endblock %}

20
src/Auth/views/reset.twig Normal file
View File

@ -0,0 +1,20 @@
{% extends 'layout.twig' %}
{% block body %}
{% if flash('error') %}
<div class="alert alert-danger">
{{ flash('error') }}
</div>
{% endif %}
<form action="" method="post">
{{ csrf_input() }}
{{ field('password', null, 'Mot de passe', {type: 'password'}) }}
{{ field('password_confirm', null, 'Confirmer le mot de passe', {type: 'password'}) }}
<button class="btn btn-primary">Réinitialiser mon mot de passe</button>
</form>
{% endblock %}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Contact;
use Framework\Renderer\RendererInterface;
use Framework\Response\RedirectResponse;
use Psr\Http\Message\ServerRequestInterface;
class ContactAction
{
/**
* @var RendererInterface
*/
private $renderer;
public function __construct(
RendererInterface $renderer
) {
$this->renderer = $renderer;
}
/**
* @param ServerRequestInterface $request
* @return RedirectResponse|string
*/
public function __invoke(ServerRequestInterface $request)
{
if ($request->getMethod() === 'GET') {
return $this->renderer->render('@contact/contact');
}
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Contact;
use Framework\Module;
use Framework\Renderer\RendererInterface;
use Framework\Router;
class ContactModule extends Module
{
const DEFINITIONS = __DIR__ . '/definitions.php';
public function __construct(Router $router, RendererInterface $renderer)
{
$renderer->addPath('contact', __DIR__);
$router->get('/contact', ContactAction::class, 'contact');
//$router->post('/contact', ContactAction::class);
}
}

133
src/Contact/contact.twig Normal file
View File

@ -0,0 +1,133 @@
{% extends 'layout.twig' %}
{% block title "Ğ1-Marché - Contact" %}
{% block body %}
<style>
/*body .fond {
background-image: url("../../images/background_contacts.jpg");
background-repeat: no-repeat;
background-attachment:fixed;
background-position: center;
background-size: 100% auto;
font-family: Bree serif,"Helvetica Neue", Helvetica, Arial, sans-serif;
}*/
.main {
width: 900px;
font-family: "Bree Serif", Arial;
color: black;
text-align: center;
}
.main h2, h3 {
padding-left: 5px;
padding-top: 5px;
margin-bottom: 15px;
}
.equipe {
width: 427px;
height: 167px;
margin-left: auto;
margin-right: auto;
background: black;
border: 1px solid rgba(255,255,255,0.2);
display:flex;
flex-direction: row;
}
.la_page {
text-align: center;
width: 874px;
margin-left: auto;
margin-right: auto;
}
.avatar {
font-family: 'Kalam', cursive;
border: 2px solid rgba(255,255,255,0.8);
color: white;
text-align: center;
flex-direction: column;
}
.avatar img{
margin-top: 2px;
margin-bottom: -2px;
}
.header {
text-align: center;
}
img.img_header {
width: 294px;
height: 294px;
}
img.nox {
width: 137px;
height: 137px;
}
img.poka {
width: 136px;
height: 136px;
}
img.attilax {
width: 134px;
height: 134px;
}
.cadre {
font-family: Bree Serif;
color:white;
width: 879px;
padding: 10px 20px;
height: auto;
text-align: left;
margin-left: auto;
margin-right: auto;
background: black;
border: solid 0px;
}
#content_contacts {
text-align: justify;
text-justify: inter-word;
}
</style>
{% if explodeUrl()[1]=='contact' %}
<script type="text/javascript">
$('.fond').css('background-image', "url('/images/background_contacts.jpg')");
</script>
{% endif %}
<div class="main" style="background:none;">
<div class="header">
<img class="img_header" src="/images/logo_contacts.jpg" style="width: 294px;height: 294px;" alt="Ğ1-Marché" title="Logo Contact" />
</div>
<p>
<h3 style="text-align:center;">QUI SOMMES-NOUS ?</h3>
</p>
<div class="equipe">
<div class="avatar">
<img class="nox" src="/images/nox.png" alt="" title="nox" />
NOX
</div>
<div class="avatar">
<img class="poka" src="/images/poka.png" alt="" title="Poka" />
POKA
</div>
<div class="avatar">
<img class="attilax" src="/images/attilax.png" alt="" title="Attilax" />
ATTILAX
</div>
</div>
<div class="la_page">
<div class="cadre">
<div id="content_contacts">
<p>Utilisateurs et membres de la Monnaie Libre depuis deux ans, nous
avons voulu créer un outil permettant de gérer et d'organiser les
G-marchés dans toute la France. </p>
<p>Avec Ğ1-MARCHÉ, vous pouvez proposer à l'avance, consulter,
pré-acheter, faire une demande en vue du prochain G-Marché près de
chez vous... Votre ville n'apparaît pas ? Il suffit de nous en faire
la demande, et nous l'intégrons dans le site. Nous vous fournissons
une redirection, un dossier générique avec le code source, vous
n'avez plus qu'à changer l'image du fond et le logo par ceux de
votre choix pour symboliser votre ville, et votre Ğ1-marché local
est prêt... </p>
<h4>Pour nous contacter : gmarche@axiom-team.fr </h4>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,5 @@
<?php
return [
// 'contact.to' => \DI\get('mail.to'),
// \App\Contact\ContactAction::class => \DI\object()->constructorParameter('to', \DI\get('contact.to'))
];

View File

@ -0,0 +1,255 @@
<?php
namespace Framework\Actions;
use App\Gmarche\Table\AntenneTable;
use Framework\Database\Hydrator;
use Framework\Database\Table;
use Framework\Renderer\RendererInterface;
use Framework\Router;
use Framework\Session\FlashService;
use Framework\Validator;
use App\Product\Entity\Product;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
class CrudAction
{
/**
* @var RendererInterface
*/
private $renderer;
/**
* @var Router
*/
private $router;
/**
* @var Table
*/
protected $table;
/**
* @var AntenneTable
*/
protected $table2;
/**
* @var FlashService
*/
private $flash;
/**
* @var string
*/
protected $viewPath;
/**
* @var string
*/
protected $routePrefix;
/**
* @var string
*/
protected $messages = [
'create' => "L'élément truc a bien été créé", /* truc sera remplacé par le vrai nom ensuite */
'edit' => "L'élément truc a bien été modifié", /* truc sera remplacé par le vrai nom ensuite */
'delete' => "L'élément truc a bien été supprimé" /* truc sera remplacé par le vrai nom ensuite */
];
/**
* @var array
*/
protected $acceptedParams = [];
use RouterAwareAction;
public function __construct(
RendererInterface $renderer,
Router $router,
Table $table,
FlashService $flash
) {
$this->renderer = $renderer;
$this->router = $router;
$this->table = $table;
$this->flash = $flash;
}
public function __invoke(Request $request)
{
$this->renderer->addGlobal('viewPath', $this->viewPath);
$this->renderer->addGlobal('routePrefix', $this->routePrefix);
if ($request->getMethod() === 'DELETE') {
return $this->delete($request);
}
if (substr((string)$request->getUri(), -3) === 'new') {
return $this->create($request);
}
if ($request->getAttribute('id')) {
return $this->edit($request);
}
return $this->index($request);
}
/**
* Affiche la liste des éléments
* @param Request $request
* @return string
*/
public function index(Request $request): string
{
$params = $request->getQueryParams();
$items = $this->table->findAll()->paginate(12, $params['p'] ?? 1);
return $this->renderer->render($this->viewPath . '/index', compact('items'));
}
/**
* Edite un élément
* @param Request $request
* @return ResponseInterface|string
*/
public function edit(Request $request)
{
$id = (int)$request->getAttribute('id');
$item = $this->table->find($id);
$antenne = $item->antenneId;
if ($request->getMethod() === 'POST') {
$validator = $this->getValidator($request);
if ($validator->isValid()) {
$this->table->update($id, $this->prePersist($request, $item));
$this->postPersist($request, $item);
// On récupère le nom du produit pour l'afficher dans le message de success
$requete = $this->prePersist($request, $item);
$occurrence = $requete['name'];
$message_edit = $this->messages['edit'];
$message_edit = str_replace('truc', "'".$occurrence."'", $message_edit );
$this->flash->success($message_edit);
$region = $request->getAttribute('region');
$slug = $request->getAttribute('slug');
return $this->redirect($this->routePrefix . '.index',compact('region','slug','antenne' ));
}
$errors = $validator->getErrors();
Hydrator::hydrate($request->getParsedBody(), $item);
}
$errors = $errors ?? '';
return $this->renderer->render(
$this->viewPath . '/edit',
$this->formParams(compact('item', 'errors'))
);
}
/**
* Crée un nouvel élément
* @param Request $request
* @return ResponseInterface|string
*/
public function create(Request $request)
{
$item = $this->getNewEntity();
if ($request->getMethod() === 'POST') {
$validator = $this->getValidator($request);
if ($validator->isValid()) {
$this->table->insert($this->prePersist($request, $item));
$region = $request->getAttribute('region');
$slug = $request->getAttribute('slug');
$antenne = $request->getParsedBody()['antenne_id'];
$this->postPersist($request, $item);
// On récupère le nom du produit pour l'afficher dans le message de success
$requete = $this->prePersist($request, $item);
$occurrence = $requete['name'];
$message_create = $this->messages['create'];
$message_create = str_replace('truc', "'".$occurrence."'", $message_create );
$this->flash->success($this->messages['create']);
return $this->redirect($this->routePrefix . '.index',compact('region','slug','antenne' ));
}
Hydrator::hydrate($request->getParsedBody(), $item);
$errors = $validator->getErrors();
}
$errors = $errors ?? '';
return $this->renderer->render(
$this->viewPath . '/create',
$this->formParams(compact('item', 'errors'))
);
}
/**
* Action de suppression
*
* @param Request $request
* @return ResponseInterface
*/
public function delete(Request $request)
{
$this->table->delete($request->getAttribute('id'));
$region = $request->getAttribute('region');
$slug = $request->getAttribute('slug');
$antenne = $request->getParsedBody()['antenne_id'];
// On récupère le nom du produit pour l'afficher dans le message de success
$occurrence = $request->getParsedBody()['produit_name'];
$message_delete = $this->messages['delete'];
$message_delete = str_replace('truc', "'".$occurrence."'", $message_delete );
$this->flash->success($message_delete);
return $this->redirect($this->routePrefix . '.index',compact('region','slug','antenne' ));
}
/**
* Filtre les paramètres reçus par la requête
*
* @param Request $request
* @return array
*/
protected function prePersist(Request $request, $item): array
{
return array_filter(array_merge($request->getParsedBody(), $request->getUploadedFiles()), function ($key) {
return in_array($key, $this->acceptedParams);
}, ARRAY_FILTER_USE_KEY);
}
/**
* Permet d'effectuer un traitement après la persistence
* @param Request $request
* @param $item
*/
protected function postPersist(Request $request, $item): void
{
}
/**
* Génère le validateur pour valider les données
*
* @param Request $request
* @return Validator
*/
protected function getValidator(Request $request)
{
return new Validator(array_merge($request->getParsedBody(), $request->getUploadedFiles()));
}
/**
* Génère une nouvelle entité pour l'action de création
*
* @return mixed
*/
protected function getNewEntity()
{
$entity = $this->table->getEntity();
return new $entity();
}
/**
* Permet de traiter les paramètres à envoyer à la vue
*
* @param $params
* @return array
*/
protected function formParams(array $params): array
{
return $params;
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Framework\Actions;
use GuzzleHttp\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
/**
* Rajoute des méthodes liées à l'utilisation du Router
*
* Trait RouterAwareAction
* @package Framework\Actions
*/
trait RouterAwareAction
{
/**
* Renvoie une réponse de redirection
*
* @param string $path
* @param array $params
* @return ResponseInterface
*/
public function redirect(string $path, array $params = []): ResponseInterface
{
$redirectUri = $this->router->generateUri($path, $params);
return (new Response())
->withStatus(301)
->withHeader('Location',$redirectUri);
}
}

150
src/Framework/App.php Normal file
View File

@ -0,0 +1,150 @@
<?php
namespace Framework;
use DI\ContainerBuilder;
use Doctrine\Common\Cache\ApcuCache;
use Doctrine\Common\Cache\FilesystemCache;
use Framework\Middleware\CombinedMiddleware;
use Framework\Middleware\RoutePrefixedMiddleware;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class App implements DelegateInterface
{
/**
* List of modules
* @var array
*/
private $modules = [];
/**
* @var array
*/
private $definitions;
/**
* @var ContainerInterface
*/
private $container;
/**
* @var string[]
*/
private $middlewares = [];
/**
* @var int
*/
private $index = 0;
/**
* App constructor.
* @param null|string|array $definitions
*/
public function __construct($definitions = [])
{
if (is_string($definitions)) {
$definitions = [$definitions];
}
if (!$this->isSequential($definitions)) {
$definitions = [$definitions];
}
$this->definitions = $definitions;
}
/**
* Rajoute un module à l'application
*
* @param string $module
* @return App
*/
public function addModule(string $module): self
{
$this->modules[] = $module;
return $this;
}
/**
* Ajoute un middleware
*
* @param string|callable|MiddlewareInterface $routePrefix
* @param null|string|callable|MiddlewareInterface $middleware
* @return App
*/
public function pipe($routePrefix, $middleware = null): self
{
if ($middleware === null) {
$this->middlewares[] = $routePrefix;
} else {
$this->middlewares[] = new RoutePrefixedMiddleware($this->getContainer(), $routePrefix, $middleware);
}
return $this;
}
public function process(ServerRequestInterface $request): ResponseInterface
{
$this->index++;
if ($this->index > 1) {
throw new \Exception();
}
$middleware = new CombinedMiddleware($this->getContainer(), $this->middlewares);
return $middleware->process($request, $this);
}
public function run(ServerRequestInterface $request): ResponseInterface
{
foreach ($this->modules as $module) {
$this->getContainer()->get($module);
}
return $this->process($request);
}
/**
* @return ContainerInterface
*/
public function getContainer(): ContainerInterface
{
if ($this->container === null) {
$builder = new ContainerBuilder();
$env = getenv('ENV') ?: 'production';
if ($env === 'production') {
//$builder->setDefinitionCache(new FilesystemCache('tmp/di'));
$builder->writeProxiesToFile(true, 'tmp/proxies');
}
foreach ($this->definitions as $definition) {
$builder->addDefinitions($definition);
}
foreach ($this->modules as $module) {
if ($module::DEFINITIONS) {
$builder->addDefinitions($module::DEFINITIONS);
}
}
$builder->addDefinitions([
App::class => $this
]);
$this->container = $builder->build();
}
return $this->container;
}
/**
* @return array
*/
public function getModules(): array
{
return $this->modules;
}
private function isSequential(array $array): bool
{
if (empty($array)) {
return true;
}
return array_keys($array) === range(0, count($array) - 1);
}
}

13
src/Framework/Auth.php Normal file
View File

@ -0,0 +1,13 @@
<?php
namespace Framework;
use Framework\Auth\User;
interface Auth
{
/**
* @return User|null
*/
public function getUser(): ?User;
}

View File

@ -0,0 +1,7 @@
<?php
namespace Framework\Auth;
class ForbiddenException extends \Exception
{
}

View File

@ -0,0 +1,31 @@
<?php
namespace Framework\Auth;
use Framework\Auth;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class LoggedInMiddleware implements MiddlewareInterface
{
/**
* @var Auth
*/
private $auth;
public function __construct(Auth $auth)
{
$this->auth = $auth;
}
public function process(ServerRequestInterface $request, DelegateInterface $delegate): ResponseInterface
{
$user = $this->auth->getUser();
if (is_null($user)) {
throw new ForbiddenException();
}
return $delegate->process($request->withAttribute('user', $user));
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Framework\Auth;
use Framework\Auth;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class RoleMiddleware implements MiddlewareInterface
{
/**
* @var Auth
*/
private $auth;
/**
* @var string
*/
private $role;
public function __construct(Auth $auth, string $role)
{
$this->auth = $auth;
$this->role = $role;
}
public function process(ServerRequestInterface $request, DelegateInterface $delegate): ResponseInterface
{
$user = $this->auth->getUser();
if ($user === null || !in_array($this->role, $user->getRoles())) {
throw new ForbiddenException();
}
return $delegate->process($request);
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Framework\Auth;
use Framework\Auth;
class RoleMiddlewareFactory
{
/**
* @var Auth
*/
private $auth;
public function __construct(Auth $auth)
{
$this->auth = $auth;
}
public function makeForRole($role): RoleMiddleware
{
return new RoleMiddleware($this->auth, $role);
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Framework\Auth;
interface User
{
/**
* @return string
*/
public function getUsername(): string;
/**
* @return string[]
*/
public function getRoles(): array;
}

View File

@ -0,0 +1,40 @@
<?php
namespace Framework\Database;
/**
* Transforme un tableau en objet en utilisant les setters
*/
class Hydrator
{
/**
* Transforme un tableau en objet en utilisant les setters
* @param array $array
* @param $object
* @return mixed
*/
public static function hydrate(array $array, $object)
{
$instance = new $object();
foreach ($array as $key => $value) {
$method = self::getSetter($key);
if (method_exists($instance, $method)) {
$instance->$method($value);
} else {
$property = lcfirst(self::getProperty($key));
$instance->$property = $value;
}
}
return $instance;
}
private static function getSetter(string $fieldName): string
{
return 'set' . self::getProperty($fieldName);
}
private static function getProperty(string $fieldName): string
{
return join('', array_map('ucfirst', explode('_', $fieldName)));
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Framework\Database;
class NoRecordException extends \Exception
{
}

View File

@ -0,0 +1,47 @@
<?php
namespace Framework\Database;
use Pagerfanta\Adapter\AdapterInterface;
class PaginatedQuery implements AdapterInterface
{
/**
* @var Query
*/
private $query;
/**
* PaginatedQuery constructor.
* @param Query $query
*/
public function __construct(Query $query)
{
$this->query = $query;
}
/**
* Returns the number of results.
*
* @return integer The number of results.
*/
public function getNbResults(): int
{
return $this->query->count();
}
/**
* Returns an slice of the results.
*
* @param integer $offset The offset.
* @param integer $length The length.
*
* @return \Traversable The slice.
*/
public function getSlice($offset, $length): QueryResult
{
$query = clone $this->query;
return $query->limit($length, $offset)->fetchAll();
}
}

View File

@ -0,0 +1,275 @@
<?php
namespace Framework\Database;
use Pagerfanta\Pagerfanta;
use Traversable;
class Query implements \IteratorAggregate
{
private $select;
private $from;
private $where = [];
private $entity;
private $order = [];
private $limit;
private $joins;
private $pdo;
private $params = [];
public function __construct(?\PDO $pdo = null)
{
$this->pdo = $pdo;
}
/**
* Definit le FROM
* @param string $table
* @param null|string $alias
* @return Query
*/
public function from(string $table, ?string $alias = null): self
{
if ($alias) {
$this->from[$table] = $alias;
} else {
$this->from[] = $table;
}
return $this;
}
/**
* Spécifie les champs à récupérer
* @param string[] ...$fields
* @return Query
*/
public function select(string ...$fields): self
{
$this->select = $fields;
return $this;
}
/**
* Spécifie la limite
* @param int $length
* @param int $offset
* @return Query
*/
public function limit(int $length, int $offset = 0): self
{
$this->limit = "$offset, $length";
return $this;
}
/**
* Spécifie l'ordre de récupération
* @param string $order
* @return Query
*/
public function order(string $order): self
{
$this->order[] = $order;
return $this;
}
/**
* Ajoute une liaison
* @param string $table
* @param string $condition
* @param string $type
* @return Query
*/
public function join(string $table, string $condition, string $type = "left"): self
{
$this->joins[$type][] = [$table, $condition];
return $this;
}
/**
* Définit la condition de récupération
* @param string[] ...$condition
* @return Query
*/
public function where(string ...$condition): self
{
$this->where = array_merge($this->where, $condition);
return $this;
}
/**
* Execute un COUNT() et renvoie la colonne
* @return int
*/
public function count(): int
{
$query = clone $this;
$table = current($this->from);
return $query->select("COUNT($table.id)")->execute()->fetchColumn();
}
/**
* Définit les paramètre pour la requête
* @param array $params
* @return Query
*/
public function params(array $params): self
{
$this->params = array_merge($this->params, $params);
return $this;
}
/**
* Spécifie l'entité à utiliser
* @param string $entity
* @return Query
*/
public function into(string $entity): self
{
$this->entity = $entity;
return $this;
}
/**
* Récupère un résultat
*/
public function fetch()
{
$record = $this->execute()->fetch(\PDO::FETCH_ASSOC);
if ($record === false) {
return false;
}
if ($this->entity) {
return Hydrator::hydrate($record, $this->entity);
}
return $record;
}
/**
* Récupère un résultat
* @param int $columnNumber
* @return mixed
*/
public function fetchColumn(int $columnNumber = 0)
{
return $this->execute()->fetchColumn($columnNumber);
}
/**
* Retournera un résultat ou renvoie une exception
* @return bool|mixed
* @throws NoRecordException
*/
public function fetchOrFail()
{
$record = $this->fetch();
if ($record === false) {
throw new NoRecordException();
}
return $record;
}
/**
* Lance la requête
* @return QueryResult
*/
public function fetchAll(): QueryResult
{
return new QueryResult(
$this->execute()->fetchAll(\PDO::FETCH_ASSOC),
$this->entity
);
}
/**
* Pagine les résultats
* @param int $perPage
* @param int $currentPage
* @return Pagerfanta
*/
public function paginate(int $perPage, int $currentPage = 1): Pagerfanta
{
$paginator = new PaginatedQuery($this);
return (new Pagerfanta($paginator))->setMaxPerPage($perPage)->setCurrentPage($currentPage);
}
/**
* Génère la requête SQL
* @return string
*/
public function __toString()
{
$parts = ['SELECT'];
if ($this->select) {
$parts[] = join(', ', $this->select);
} else {
$parts[] = '*';
}
$parts[] = 'FROM';
$parts[] = $this->buildFrom();
if (!empty($this->joins)) {
foreach ($this->joins as $type => $joins) {
foreach ($joins as [$table, $condition]) {
$parts[] = strtoupper($type) . " JOIN $table ON $condition";
}
}
}
if (!empty($this->where)) {
$parts[] = "WHERE";
$parts[] = "(" . join(') AND (', $this->where) . ')';
}
if (!empty($this->order)) {
$parts[] = 'ORDER BY';
$parts[] = join(', ', $this->order);
}
if ($this->limit) {
$parts[] = 'LIMIT ' . $this->limit;
}
return join(' ', $parts);
}
/**
* Construit le FROM a as b ....
* @return string
*/
private function buildFrom(): string
{
$from = [];
foreach ($this->from as $key => $value) {
if (is_string($key)) {
$from[] = "$key as $value";
} else {
$from[] = $value;
}
}
return join(', ', $from);
}
/**
* Exécute la requête
* @return \PDOStatement
*/
private function execute()
{
$query = $this->__toString();
if (!empty($this->params)) {
$statement = $this->pdo->prepare($query);
$statement->execute($this->params);
return $statement;
}
return $this->pdo->query($query);
}
public function getIterator()
{
return $this->fetchAll();
}
}

View File

@ -0,0 +1,171 @@
<?php
namespace Framework\Database;
/**
* Représente les résultats d'une requête
*/
class QueryResult implements \ArrayAccess, \Iterator
{
/**
* @var array Les enregistrements
*/
private $records;
/**
* @var null|string Entité à utiliser pour hydrater nos objets
*/
private $entity;
/**
* @var int Index servant à l'itération
*/
private $index = 0;
/**
* @var array Sauvegarde les enregistrements déjà hydratés
*/
private $hydratedRecords = [];
public function __construct(array $records, ?string $entity = null)
{
$this->records = $records;
$this->entity = $entity;
}
/**
* Récupère un éléments à l'index définit
* @param int $index
* @return mixed|null|string
*/
public function get(int $index)
{
if ($this->entity) {
if (!isset($this->hydratedRecords[$index])) {
$this->hydratedRecords[$index] = Hydrator::hydrate($this->records[$index], $this->entity);
}
return $this->hydratedRecords[$index];
}
return $this->entity;
}
/**
* Return the current element
* @link http://php.net/manual/en/iterator.current.php
* @return mixed Can return any type.
* @since 5.0.0
*/
public function current()
{
return $this->get($this->index);
}
/**
* Move forward to next element
* @link http://php.net/manual/en/iterator.next.php
* @return void Any returned value is ignored.
* @since 5.0.0
*/
public function next(): void
{
$this->index++;
}
/**
* Return the key of the current element
* @link http://php.net/manual/en/iterator.key.php
* @return mixed scalar on success, or null on failure.
* @since 5.0.0
*/
public function key()
{
return $this->index;
}
/**
* Checks if current position is valid
* @link http://php.net/manual/en/iterator.valid.php
* @return boolean The return value will be casted to boolean and then evaluated.
* Returns true on success or false on failure.
* @since 5.0.0
*/
public function valid()
{
return isset($this->records[$this->index]);
}
/**
* Rewind the Iterator to the first element
* @link http://php.net/manual/en/iterator.rewind.php
* @return void Any returned value is ignored.
* @since 5.0.0
*/
public function rewind()
{
$this->index = 0;
}
/**
* Whether a offset exists
* @link http://php.net/manual/en/arrayaccess.offsetexists.php
* @param mixed $offset <p>
* An offset to check for.
* </p>
* @return boolean true on success or false on failure.
* </p>
* <p>
* The return value will be casted to boolean if non-boolean was returned.
* @since 5.0.0
*/
public function offsetExists($offset)
{
return isset($this->records[$offset]);
}
/**
* Offset to retrieve
* @link http://php.net/manual/en/arrayaccess.offsetget.php
* @param mixed $offset <p>
* The offset to retrieve.
* </p>
* @return mixed Can return all value types.
* @since 5.0.0
*/
public function offsetGet($offset)
{
return $this->get($offset);
}
/**
* Offset to set
* @link http://php.net/manual/en/arrayaccess.offsetset.php
* @param mixed $offset <p>
* The offset to assign the value to.
* </p>
* @param mixed $value <p>
* The value to set.
* </p>
* @return void
* @throws \Exception
* @since 5.0.0
*/
public function offsetSet($offset, $value)
{
throw new \Exception("Can't alter records");
}
/**
* Offset to unset
* @link http://php.net/manual/en/arrayaccess.offsetunset.php
* @param mixed $offset <p>
* The offset to unset.
* </p>
* @return void
* @throws \Exception
* @since 5.0.0
*/
public function offsetUnset($offset)
{
throw new \Exception("Can't alter records");
}
}

View File

@ -0,0 +1,198 @@
<?php
namespace Framework\Database;
use Pagerfanta\Pagerfanta;
class Table
{
/**
* @var null|\PDO
*/
protected $pdo;
/**
* Nom de la table en BDD
* @var string
*/
protected $table;
/**
* Entité à utiliser
* @var string
*/
protected $entity = \stdClass::class;
public function __construct(\PDO $pdo)
{
$this->pdo = $pdo;
}
/**
* Récupère une liste clef valeur de nos enregistrements
*/
public function findList(): array
{
if ( $this->table === 'users') {
$champ = 'username';
} else {
$champ = 'name';
}
//die();
$results = $this->pdo
->query("SELECT id, $champ FROM {$this->table}")
->fetchAll(\PDO::FETCH_NUM);
$list = [];
foreach ($results as $result) {
$list[$result[0]] = $result[1];
}
return $list;
}
/**
* @return Query
*/
public function makeQuery(): Query
{
return (new Query($this->pdo))
->from($this->table, $this->table[0])
->into($this->entity);
}
/**
* Récupère tous les enregistrements
*
* @return Query
*/
public function findAll(): Query
{
return $this->makeQuery();
}
/**
* Récupère une ligne par rapport à un champ
*
* @param string $field
* @param string $value
* @return array
* @throws NoRecordException
*/
public function findBy(string $field, string $value)
{
// echo "field = ".$field;
// die();
return $this->makeQuery()->where("$field = :field")->params(["field" => $value])->fetchOrFail();
}
/**
* Récupère un élément à partir de son ID
*
* @param int $id
* @return mixed
* @throws NoRecordException
*/
public function find(int $id)
{
return $this->makeQuery()->where("id = $id")->fetchOrFail();
}
/**
* Récupère le nbre d'enregistrement
*
* @return int
*/
public function count(): int
{
return $this->makeQuery()->count();
}
/**
* Met à jour un enregistrement au niveau de la base de données
*
* @param int $id
* @param array $params
* @return bool
*/
public function update(int $id, array $params): bool
{
$fieldQuery = $this->buildFieldQuery($params);
$params["id"] = $id;
$query = $this->pdo->prepare("UPDATE {$this->table} SET $fieldQuery WHERE id = :id");
return $query->execute($params);
}
/**
* Crée un nouvel enregistrement
*
* @param array $params
* @return bool
*/
public function insert(array $params): bool
{
$fields = array_keys($params);
$values = join(', ', array_map(function ($field) {
return ':' . $field;
}, $fields));
$fields = join(', ', $fields);
$query = $this->pdo->prepare("INSERT INTO {$this->table} ($fields) VALUES ($values)");
// echo 'query =';
// var_dump($query);
// die();
return $query->execute($params);
}
/**
* Supprime un enregistrment
* @param int $id
* @return bool
*/
public function delete(int $id): bool
{
$query = $this->pdo->prepare("DELETE FROM {$this->table} WHERE id = ?");
return $query->execute([$id]);
}
private function buildFieldQuery(array $params)
{
return join(', ', array_map(function ($field) {
return "$field = :$field";
}, array_keys($params)));
}
/**
* @return mixed
*/
public function getEntity(): string
{
return $this->entity;
}
/**
* @return string
*/
public function getTable(): string
{
return $this->table;
}
/**
* Vérifie qu'un enregistrement existe
* @param $id
* @return bool
*/
public function exists($id): bool
{
$query = $this->pdo->prepare("SELECT id FROM {$this->table} WHERE id = ?");
$query->execute([$id]);
return $query->fetchColumn() !== false;
}
/**
* @return \PDO
*/
public function getPdo(): \PDO
{
return $this->pdo;
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace Framework\Entity;
trait Timestamp
{
/**
* @var \DateTime|null
*/
private $updatedAt;
/**
* @var \DateTime|null
*/
private $createdAt;
/**
* @return \DateTime|null
*/
public function getUpdatedAt(): ?\DateTime
{
return $this->updatedAt;
}
/**
* @param \DateTime|string|null $datetime
*/
public function setUpdatedAt($datetime): void
{
if (is_string($datetime)) {
$this->updatedAt = new \DateTime($datetime);
} else {
$this->updatedAt = $datetime;
}
}
/**
* @return \DateTime|null
*/
public function getCreatedAt(): ?\DateTime
{
return $this->createdAt;
}
/**
* @param \DateTime|string|null $datetime
*/
public function setCreatedAt($datetime): void
{
if (is_string($datetime)) {
$this->createdAt = new \DateTime($datetime);
} else {
$this->createdAt = $datetime;
}
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace Framework\Middleware;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class CombinedMiddleware implements MiddlewareInterface
{
/**
* @var ContainerInterface
*/
private $container;
/**
* @var array
*/
private $middlewares;
public function __construct(ContainerInterface $container, array $middlewares)
{
$this->container = $container;
$this->middlewares = $middlewares;
}
public function process(ServerRequestInterface $request, DelegateInterface $delegate): ResponseInterface
{
$delegate = new CombinedMiddlewareDelegate($this->container, $this->middlewares, $delegate);
return $delegate->process($request);
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace Framework\Middleware;
use GuzzleHttp\Psr7\Response;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class CombinedMiddlewareDelegate implements DelegateInterface
{
/**
* @var string[]
*/
private $middlewares = [];
/**
* @var int
*/
private $index = 0;
/**
* @var ContainerInterface
*/
private $container;
/**
* @var DelegateInterface
*/
private $delegate;
public function __construct(ContainerInterface $container, array $middlewares, DelegateInterface $delegate)
{
$this->middlewares = $middlewares;
$this->container = $container;
$this->delegate = $delegate;
}
public function process(ServerRequestInterface $request): ResponseInterface
{
$middleware = $this->getMiddleware();
if (is_null($middleware)) {
return $this->delegate->process($request);
} elseif (is_callable($middleware)) {
$response = call_user_func_array($middleware, [$request, [$this, 'process']]);
if (is_string($response)) {
return new Response(200, [], $response);
}
return $response;
} elseif ($middleware instanceof MiddlewareInterface) {
return $middleware->process($request, $this);
}
}
/**
* @return object
*/
private function getMiddleware()
{
if (array_key_exists($this->index, $this->middlewares)) {
if (is_string($this->middlewares[$this->index])) {
$middleware = $this->container->get($this->middlewares[$this->index]);
} else {
$middleware = $this->middlewares[$this->index];
}
$this->index++;
return $middleware;
}
return null;
}
}

View File

@ -0,0 +1,113 @@
<?php
namespace Framework\Middleware;
use Framework\Exception\CsrfInvalidException;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class CsrfMiddleware implements MiddlewareInterface
{
/**
* @var string
*/
private $formKey;
/**
* @var string
*/
private $sessionKey;
/**
* @var int
*/
private $limit;
/**
* @var \ArrayAccess
*/
private $session;
public function __construct(
&$session,
int $limit = 50,
string $formKey = '_csrf',
string $sessionKey = 'csrf'
) {
$this->validSession($session);
$this->session = &$session;
$this->formKey = $formKey;
$this->sessionKey = $sessionKey;
$this->limit = $limit;
}
public function process(ServerRequestInterface $request, DelegateInterface $delegate): ResponseInterface
{
if (in_array($request->getMethod(), ['POST', 'PUT', 'DELETE'])) {
$params = $request->getParsedBody() ?: [];
if (!array_key_exists($this->formKey, $params)) {
$this->reject();
} else {
$csrfList = $this->session[$this->sessionKey] ?? [];
if (in_array($params[$this->formKey], $csrfList)) {
$this->useToken($params[$this->formKey]);
return $delegate->process($request);
} else {
$this->reject();
}
}
} else {
return $delegate->process($request);
}
}
public function generateToken(): string
{
$token = bin2hex(random_bytes(16));
$csrfList = $this->session[$this->sessionKey] ?? [];
$csrfList[] = $token;
$this->session[$this->sessionKey] = $csrfList;
$this->limitTokens();
return $token;
}
private function reject(): void
{
throw new CsrfInvalidException();
}
private function useToken($token): void
{
$tokens = array_filter($this->session[$this->sessionKey], function ($t) use ($token) {
return $token !== $t;
});
$this->session[$this->sessionKey] = $tokens;
}
private function limitTokens(): void
{
$tokens = $this->session[$this->sessionKey] ?? [];
if (count($tokens) > $this->limit) {
array_shift($tokens);
}
$this->session[$this->sessionKey] = $tokens;
}
private function validSession($session)
{
if (!is_array($session) && !$session instanceof \ArrayAccess) {
throw new \TypeError('La session passé au middleware CSRF n\'est pas traitable comme un tableau');
}
}
/**
* @return string
*/
public function getFormKey(): string
{
return $this->formKey;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Framework\Middleware;
use Framework\Router;
use GuzzleHttp\Psr7\Response;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class DispatcherMiddleware implements MiddlewareInterface
{
/**
* @var ContainerInterface
*/
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
$route = $request->getAttribute(Router\Route::class);
if (is_null($route)) {
return $delegate->process($request);
}
$callback = $route->getCallback();
if (!is_array($callback)) {
$callback = [$callback];
}
return (new CombinedMiddleware($this->container, $callback))->process($request, $delegate);
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Framework\Middleware;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
class MethodMiddleware implements MiddlewareInterface {
public function process(ServerRequestInterface $request, DelegateInterface $next)
{
$parsedBody = $request->getParsedBody();
if (array_key_exists('_method', $parsedBody) &&
in_array($parsedBody['_method'], ['DELETE', 'PUT'])
) {
$request = $request->withMethod($parsedBody['_method']);
}
return $next->process($request);
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Framework\Middleware;
use GuzzleHttp\Psr7\Response;
use Psr\Http\Message\ServerRequestInterface;
class NotFoundMiddleware {
public function __invoke(ServerRequestInterface $request, callable $next)
{
return new Response(404, [], '<html><head><link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css"></head>
<div style="padding: 5rem; height:100%; width:100%;
background:url(\'../images/bildreich_1275.jpg\') no-repeat;background-size: cover;">
<div class="container" style="padding: 2rem;margin: 5rem 2rem;width:15rem;height:14rem;background-color: #c9c9c9;">
<h4>Erreur 404</h4><br />Cette url n\'existe pas.<br /><br />
<a class="btn btn-primary btn-md" href="https://gmarche.monnaie-libre.fr/gmarche" role="button">
Retour à l\'accueil</a></div></div></html>');
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace Framework\Middleware;
use Framework\Renderer\RendererInterface;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class RendererRequestMiddleware implements MiddlewareInterface
{
/**
* @var RendererInterface
*/
private $renderer;
public function __construct(RendererInterface $renderer)
{
$this->renderer = $renderer;
}
public function process(ServerRequestInterface $request, DelegateInterface $delegate): ResponseInterface
{
$domain = sprintf(
'%s://%s%s',
$request->getUri()->getScheme(),
$request->getUri()->getHost(),
$request->getUri()->getPort() ? ':' . $request->getUri()->getPort() : ''
);
$this->renderer->addGlobal('domain', $domain);
return $delegate->process($request);
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace Framework\Middleware;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class RoutePrefixedMiddleware implements MiddlewareInterface
{
/**
* @var ContainerInterface
*/
private $container;
/**
* @var string
*/
private $prefix;
/**
* @var string|MiddlewareInterface
*/
private $middleware;
public function __construct(ContainerInterface $container, string $prefix, $middleware)
{
$this->container = $container;
$this->prefix = $prefix;
$this->middleware = $middleware;
}
public function process(ServerRequestInterface $request, DelegateInterface $delegate): ResponseInterface
{
$path = $request->getUri()->getPath();
if (strpos($path, $this->prefix) === 0) {
if (is_string($this->middleware)) {
return $this->container->get($this->middleware)->process($request, $delegate);
} else {
return $this->middleware->process($request, $delegate);
}
}
return $delegate->process($request);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace Framework\Middleware;
use Framework\Router;
use Psr\Http\Message\ServerRequestInterface;
class RouterMiddleware {
/**
* @var Router
*/
private $router;
public function __construct(Router $router)
{
$this->router = $router;
}
public function __invoke(ServerRequestInterface $request, callable $next)
{
$route = $this->router->match($request);
if (is_null($route)) {
return $next($request);
}
$params = $route->getParams();
$request = array_reduce(array_keys($params), function ($request, $key) use ($params) {
return $request->withAttribute($key, $params[$key]);
}, $request);
$request = $request->withAttribute(get_class($route), $route);
return $next($request);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace Framework\Middleware;
use Psr\Http\Message\ServerRequestInterface;
class TrailingSlashMiddleware {
public function __invoke(ServerRequestInterface $request, callable $next)
{
$uri = $request->getUri()->getPath();
if (!empty($uri) && $uri[-1] === "/") {
return (new \GuzzleHttp\Psr7\Response())
->withStatus(301)
->withHeader('Location', substr($uri, 0, -1));
}
return $next($request);
}
}

11
src/Framework/Module.php Normal file
View File

@ -0,0 +1,11 @@
<?php
namespace Framework;
class Module
{
const DEFINITIONS = null;
const MIGRATIONS = null;
const SEEDS = null;
}

View File

@ -0,0 +1,89 @@
<?php
namespace Framework\Renderer;
class PHPRenderer implements RendererInterface
{
const DEFAULT_NAMESPACE = '__MAIN';
private $paths = [];
/**
* Variables globalement accessibles pour toutes les vues
* @var array
*/
private $globals = [];
public function __construct(?string $defaultPath = null)
{
if (!is_null($defaultPath)) {
$this->addPath($defaultPath);
}
}
/**
* Permet de rajouter un chemin pour charger les vues
* @param string $namespace
* @param null|string $path
*/
public function addPath(string $namespace, ?string $path = null): void
{
if (is_null($path)) {
$this->paths[self::DEFAULT_NAMESPACE] = $namespace;
} else {
$this->paths[$namespace] = $path;
}
}
/**
* Rend une vue
* Le chemin peut être précisé avec des namespaces rajoutés via addPath()
* $this->render('@gmarche/view');
* $this->render('view');
* @param string $view
* @param array $params
* @return string
*/
public function render(string $view, array $params = []): string
{
if ($this->hasNamespace($view)) {
$path = $this->replaceNamespace($view) . '.php';
} else {
$path = $this->paths[self::DEFAULT_NAMESPACE] . DIRECTORY_SEPARATOR . $view . '.php';
}
ob_start();
$renderer = $this;
extract($this->globals);
extract($params);
require($path);
return ob_get_clean();
}
/**
* Permet de rajouter des variables globales à toutes les vues
*
* @param string $key
* @param mixed $value
*/
public function addGlobal(string $key, $value): void
{
$this->globals[$key] = $value;
}
private function hasNamespace(string $view): bool
{
return $view[0] === '@';
}
private function getNamespace(string $view): string
{
return substr($view, 1, strpos($view, '/') - 1);
}
private function replaceNamespace(string $view): string
{
$namespace = $this->getNamespace($view);
return str_replace('@' . $namespace, $this->paths[$namespace], $view);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace Framework\Renderer;
interface RendererInterface
{
/**
* Rajoute un chemin pour charger les vues
* @param string $namespace
* @param null|string $path
*/
public function addPath(string $namespace, ?string $path = null): void;
/**
* Rend une vue
* Le chemin peut être précisé avec des namespaces rajoutés via addPath()
* $this->render('@gmarche/view');
* $this->render('view');
* @param string $view
* @param array $params
* @return string
*/
public function render(string $view, array $params = []): string;
/**
* Rajoute des variables globales à toutes les vues
*
* @param string $key
* @param mixed $value
*/
public function addGlobal(string $key, $value): void;
}

View File

@ -0,0 +1,56 @@
<?php
namespace Framework\Renderer;
class TwigRenderer implements RendererInterface
{
private $twig;
public function __construct(\Twig_Environment $twig)
{
$this->twig = $twig;
}
/**
* Permet de rajouter un chamin pour charger les vues
* @param string $namespace
* @param null|string $path
*/
public function addPath(string $namespace, ?string $path = null): void
{
$this->twig->getLoader()->addPath($path, $namespace);
}
/**
* Permet de rendre une vue
* Le chemin peut être précisé avec des namespace rajoutés via addPath()
* $this->render('@blog/view');
* $this->render('view');
* @param string $view
* @param array $params
* @return string
*/
public function render(string $view, array $params = []): string
{
return $this->twig->render($view . '.twig', $params);
}
/**
* Permet de rajouter des variables globales à toutes les vues
*
* @param string $key
* @param mixed $value
*/
public function addGlobal(string $key, $value): void
{
$this->twig->addGlobal($key, $value);
}
/**
* @return \Twig_Environment
*/
public function getTwig(): \Twig_Environment
{
return $this->twig;
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Framework\Renderer;
use Psr\Container\ContainerInterface;
use Twig\Extension\DebugExtension;
class TwigRendererFactory
{
public function __invoke(ContainerInterface $container): TwigRenderer
{
//$debug = $container->get('env') !== 'production';
$debug = true;
$viewPath = $container->get('views.path');
$loader = new \Twig_Loader_Filesystem($viewPath);
$twig = new \Twig_Environment($loader, [
//'debug' => $debug,
'debug' => true,
'cache' => $debug ? false : 'tmp/views',
'auto_reload' => $debug
]);
$twig->addExtension(new DebugExtension());
if ($container->has('twig.extensions')) {
foreach ($container->get('twig.extensions') as $extension) {
$twig->addExtension($extension);
}
}
return new TwigRenderer($twig);
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Framework\Response;
use GuzzleHttp\Psr7\Response;
class RedirectResponse extends Response
{
public function __construct(string $url)
{
parent::__construct(301, ['Location' => $url]);
}
}

125
src/Framework/Router.php Normal file
View File

@ -0,0 +1,125 @@
<?php
namespace Framework;
use Framework\Router\Route;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Expressive\Router\FastRouteRouter;
use Zend\Expressive\Router\Route as ZendRoute;
/**
* Register and match routes
*/
class Router
{
/**
* @var FastRouteRouter
*/
private $router;
public function __construct(?string $cache = null)
{
$this->router = new FastRouteRouter(null, null, [
FastRouteRouter::CONFIG_CACHE_ENABLED => !is_null($cache),
FastRouteRouter::CONFIG_CACHE_FILE => $cache
]);
}
/**
* @param string $path
* @param string|callable $callable
* @param string $name
*/
public function get(string $path, $callable, ?string $name = null)
{
$this->router->addRoute(new ZendRoute($path, $callable, ['GET'], $name));
}
/**
* @param string $path
* @param string|callable $callable
* @param string $name
*/
public function post(string $path, $callable, ?string $name = null)
{
$this->router->addRoute(new ZendRoute($path, $callable, ['POST'], $name));
}
/**
* @param string $path
* @param string|callable $callable
* @param string $name
*/
public function delete(string $path, $callable, ?string $name = null)
{
$this->router->addRoute(new ZendRoute($path, $callable, ['DELETE'], $name));
}
/**
* @param string $path
* @param $callable
* @param null|string $name
*/
public function any(string $path, $callable, ?string $name = null)
{
$this->router->addRoute(new ZendRoute($path, $callable, ['DELETE', 'POST', 'GET', 'PUT'], $name));
}
/**
* Génère les routes du CRUD
*
* @param string $prefixPath
* @param $callable
* @param string $prefixName
*/
public function crud(string $prefixPath, $callable, string $prefixName)
{
$this->get("$prefixPath", $callable, "$prefixName.index");
$this->get("$prefixPath/new", $callable, "$prefixName.create");
$this->post("$prefixPath/new", $callable);
$this->get("$prefixPath/{id:\d+}", $callable, "$prefixName.edit");
$this->post("$prefixPath/{id:\d+}", $callable);
$this->delete("$prefixPath/{id:\d+}", $callable, "$prefixName.delete");
}
/* public function crud_product(string $prefixPath, $callable, string $prefixName)
{
$this->get("$prefixPath", $callable, "$prefixName.index");
$this->get("$prefixPath/new", $callable, "$prefixName.create");
$this->post("$prefixPath/new", $callable);
$this->get("$prefixPath/{id:\d+}", $callable, "$prefixName.edit");
$this->post("$prefixPath/{id:\d+}", $callable);
$this->delete("$prefixPath/{id:\d+}", $callable, "$prefixName.delete");
}*/
/**
* @param ServerRequestInterface $request
* @return Route|null
*/
public function match(ServerRequestInterface $request): ?Route
{
//echo "<br />request = ";
//echo "<br />";
//var_dump($request);
//die();
$result = $this->router->match($request);
if ($result->isSuccess()) {
return new Route(
$result->getMatchedRouteName(),
$result->getMatchedMiddleware(),
$result->getMatchedParams()
);
}
return null;
}
public function generateUri(string $name, array $params = [], array $queryParams = []): ?string
{
$uri = $this->router->generateUri($name, $params);
if (!empty($queryParams)) {
return $uri . '?' . http_build_query($queryParams);
}
return $uri;
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace Framework\Router;
/**
* Class Route
* Represent a matched route
*/
class Route
{
/**
* @var string
*/
private $name;
/**
* @var callable
*/
private $callback;
/**
* @var array
*/
private $parameters;
/**
* Route constructor.
* @param string $name
* @param string|callable $callback
* @param array $parameters
*/
public function __construct(string $name, $callback, array $parameters)
{
$this->name = $name;
$this->callback = $callback;
$this->parameters = $parameters;
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @return string|callable
*/
public function getCallback()
{
return $this->callback;
}
/**
* Retrieve the URL parameters
* @return string[]
*/
public function getParams(): array
{
return $this->parameters;
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Framework\Router;
use Framework\Router;
use Psr\Container\ContainerInterface;
class RouterFactory
{
public function __invoke(ContainerInterface $container)
{
$cache = null;
if ($container->get('env') === 'production') {
$cache = 'tmp/routes';
}
return new Router($cache);
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Framework\Router;
use Framework\Router;
class RouterTwigExtension extends \Twig_Extension
{
/**
* @var Router
*/
private $router;
public function __construct(Router $router)
{
$this->router = $router;
}
public function getFunctions()
{
return [
new \Twig_SimpleFunction('path', [$this, 'pathFor']),
new \Twig_SimpleFunction('is_subpath', [$this, 'isSubPath'])
];
}
public function pathFor(string $path, array $params = []): string
{
return $this->router->generateUri($path, $params);
}
public function isSubpath(string $path): bool
{
$uri = $_SERVER['REQUEST_URI'] ?? '/';
$expectedUri = $this->router->generateUri($path);
return strpos($uri, $expectedUri) !== false;
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Framework\Session;
class ArraySession implements SessionInterface
{
private $session = [];
/**
* Récupère une information en Session
* @param string $key
* @param mixed $default
* @return mixed
*/
public function get(string $key, $default = null)
{
if (array_key_exists($key, $this->session)) {
return $this->session[$key];
}
return $default;
}
/**
* Ajoute une information en Session
*
* @param string $key
* @param $value
* @return mixed
*/
public function set(string $key, $value): void
{
$this->session[$key] = $value;
}
/**
* Supprime une clef en session
* @param string $key
*/
public function delete(string $key): void
{
unset($this->session[$key]);
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace Framework\Session;
class FlashService
{
/**
* @var SessionInterface
*/
private $session;
private $sessionKey = 'flash';
private $messages;
public function __construct(SessionInterface $session)
{
$this->session = $session;
}
public function success(string $message)
{
$flash = $this->session->get($this->sessionKey, []);
$flash['success'] = $message;
$this->session->set($this->sessionKey, $flash);
}
public function error(string $message)
{
$flash = $this->session->get($this->sessionKey, []);
$flash['error'] = $message;
$this->session->set($this->sessionKey, $flash);
}
public function get(string $type): ?string
{
if (is_null($this->messages)) {
$this->messages = $this->session->get($this->sessionKey, []);
$this->session->delete($this->sessionKey);
}
if (array_key_exists($type, $this->messages)) {
return $this->messages[$type];
}
return null;
}
}

View File

@ -0,0 +1,118 @@
<?php
namespace Framework\Session;
class PHPSession implements SessionInterface, \ArrayAccess
{
/**
* Assure que la Session est démarrée
*/
private function ensureStarted()
{
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
}
/**
* Récupère une information en Session
* @param string $key
* @param mixed $default
* @return mixed
*/
public function get(string $key, $default = null)
{
$this->ensureStarted();
if (array_key_exists($key, $_SESSION)) {
return $_SESSION[$key];
}
return $default;
}
/**
* Ajoute une information en Session
*
* @param string $key
* @param $value
* @return mixed
*/
public function set(string $key, $value): void
{
$this->ensureStarted();
$_SESSION[$key] = $value;
}
/**
* Supprime une clef en session
* @param string $key
*/
public function delete(string $key): void
{
$this->ensureStarted();
unset($_SESSION[$key]);
}
/**
* Whether a offset exists
* @link http://php.net/manual/en/arrayaccess.offsetexists.php
* @param mixed $offset <p>
* An offset to check for.
* </p>
* @return boolean true on success or false on failure.
* </p>
* <p>
* The return value will be casted to boolean if non-boolean was returned.
* @since 5.0.0
*/
public function offsetExists($offset)
{
$this->ensureStarted();
return array_key_exists($offset, $_SESSION);
}
/**
* Offset to retrieve
* @link http://php.net/manual/en/arrayaccess.offsetget.php
* @param mixed $offset <p>
* The offset to retrieve.
* </p>
* @return mixed Can return all value types.
* @since 5.0.0
*/
public function offsetGet($offset)
{
return $this->get($offset);
}
/**
* Offset to set
* @link http://php.net/manual/en/arrayaccess.offsetset.php
* @param mixed $offset <p>
* The offset to assign the value to.
* </p>
* @param mixed $value <p>
* The value to set.
* </p>
* @return void
* @since 5.0.0
*/
public function offsetSet($offset, $value)
{
return $this->set($offset, $value);
}
/**
* Offset to unset
* @link http://php.net/manual/en/arrayaccess.offsetunset.php
* @param mixed $offset <p>
* The offset to unset.
* </p>
* @return void
* @since 5.0.0
*/
public function offsetUnset($offset)
{
$this->delete($offset);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Framework\Session;
interface SessionInterface
{
/**
* Récupère une information en Session
* @param string $key
* @param mixed $default
* @return mixed
*/
public function get(string $key, $default = null);
/**
* Ajoute une information en Session
*
* @param string $key
* @param $value
* @return mixed
*/
public function set(string $key, $value): void;
/**
* Supprime une clef en session
* @param string $key
*/
public function delete(string $key): void;
}

Some files were not shown because too many files have changed in this diff Show More