]', 'Home:default');
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/app/Model/Security/Authenticator/UserAuthenticator.php:
--------------------------------------------------------------------------------
1 | qm->findOne(UserQuery::ofEmail($username));
32 |
33 | if ($user === null) {
34 | throw new AuthenticationException('The username is incorrect.', self::IdentityNotFound);
35 | } elseif (!$user->isActivated()) {
36 | throw new AuthenticationException('The user is not active.', self::InvalidCredential);
37 | } elseif (!$this->passwords->verify($password, $user->getPasswordHash())) {
38 | throw new AuthenticationException('The password is incorrect.', self::InvalidCredential);
39 | }
40 |
41 | $user->changeLoggedAt();
42 | $this->em->flush();
43 |
44 | return $this->createIdentity($user);
45 | }
46 |
47 | protected function createIdentity(User $user): IIdentity
48 | {
49 | return $user->toIdentity();
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/app/Model/Security/Authorizator/StaticAuthorizator.php:
--------------------------------------------------------------------------------
1 | addRoles();
17 | $this->addResources();
18 | $this->addPermissions();
19 | }
20 |
21 | /**
22 | * Setup roles
23 | */
24 | protected function addRoles(): void
25 | {
26 | $this->addRole(User::ROLE_ADMIN);
27 | }
28 |
29 | /**
30 | * Setup resources
31 | */
32 | protected function addResources(): void
33 | {
34 | $this->addResource('Admin:Home');
35 | }
36 |
37 | /**
38 | * Setup ACL
39 | */
40 | protected function addPermissions(): void
41 | {
42 | $this->allow(User::ROLE_ADMIN, [
43 | 'Admin:Home',
44 | ]);
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/app/Model/Security/Identity.php:
--------------------------------------------------------------------------------
1 | data['name'] ?? '', $this->data['surname'] ?? '');
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/app/Model/Security/Passwords.php:
--------------------------------------------------------------------------------
1 | isInRole(User::ROLE_ADMIN);
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/app/Model/Utils/Arrays.php:
--------------------------------------------------------------------------------
1 | isAjax()) {
22 | $this->redrawControl('flashes');
23 | }
24 |
25 | return parent::flashMessage($message, $type);
26 | }
27 |
28 | public function flashInfo(string $message): stdClass
29 | {
30 | return $this->flashMessage($message, 'info');
31 | }
32 |
33 | public function flashSuccess(string $message): stdClass
34 | {
35 | return $this->flashMessage($message, 'success');
36 | }
37 |
38 | public function flashWarning(string $message): stdClass
39 | {
40 | return $this->flashMessage($message, 'warning');
41 | }
42 |
43 | public function flashError(string $message): stdClass
44 | {
45 | return $this->flashMessage($message, 'danger');
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/app/UI/Control/TModuleUtils.php:
--------------------------------------------------------------------------------
1 | getName();
20 |
21 | // Validate presenter has a proper name
22 | if ($name === null) {
23 | throw new InvalidStateException('Presenter doesn\'t have a name');
24 | }
25 |
26 | $parts = explode(':', $name);
27 |
28 | return current($parts);
29 | }
30 |
31 | /**
32 | * Is current module active?
33 | *
34 | * @param string $module Module name
35 | */
36 | public function isModuleCurrent(string $module): bool
37 | {
38 | return strpos($this->getAction(true), $module) !== false;
39 | }
40 |
41 | /**
42 | * Gets template dir
43 | */
44 | public function getTemplateDir(): string
45 | {
46 | $fileName = self::getReflection()->getFileName();
47 |
48 | // Validate if class is not in PHP core
49 | if ($fileName === false) {
50 | throw new InvalidStateException('Class is defined in the PHP core or in a PHP extension');
51 | }
52 |
53 | $realpath = realpath(dirname($fileName) . '/../templates');
54 |
55 | // Validate if file exists
56 | if ($realpath === false) {
57 | throw new InvalidStateException('File does not exist');
58 | }
59 |
60 | return $realpath;
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/app/UI/Control/TTranslate.php:
--------------------------------------------------------------------------------
1 | getContext()->getByType(ITranslator::class)->translate($message);
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/app/UI/Form/BaseForm.php:
--------------------------------------------------------------------------------
1 | addCondition(self::FILLED)
15 | ->addRule(self::MAX_LENGTH, null, 255)
16 | ->addRule(self::NUMERIC);
17 |
18 | return $input;
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/app/UI/Form/FormFactory.php:
--------------------------------------------------------------------------------
1 | create();
11 | }
12 |
13 | public function forBackend(): BaseForm
14 | {
15 | return $this->create();
16 | }
17 |
18 | private function create(): BaseForm
19 | {
20 | return new BaseForm();
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/app/UI/Modules/Admin/BaseAdminPresenter.php:
--------------------------------------------------------------------------------
1 | user->isAllowed('Admin:Home')) {
16 | $this->flashError('You cannot access this with user role');
17 | $this->redirect(App::DESTINATION_FRONT_HOMEPAGE);
18 | }
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/app/UI/Modules/Admin/Home/HomePresenter.php:
--------------------------------------------------------------------------------
1 | addText('order', 'Order name')
21 | ->setRequired(true);
22 | $form->addSubmit('send', 'OK');
23 |
24 | $form->onSuccess[] = function (Form $form): void {
25 | $this->dispatcher->dispatch(new OrderCreated($form->values->order), OrderCreated::NAME);
26 | };
27 |
28 | return $form;
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/app/UI/Modules/Admin/Home/templates/default.latte:
--------------------------------------------------------------------------------
1 | {block #content}
2 | SECRET PAGE!
3 | Logout
4 |
5 |
6 |
7 | Orders
8 | {control orderForm}
9 |
--------------------------------------------------------------------------------
/app/UI/Modules/Admin/Sign/SignPresenter.php:
--------------------------------------------------------------------------------
1 | user->isLoggedIn()) {
28 | $this->redirect(App::DESTINATION_AFTER_SIGN_IN);
29 | }
30 | }
31 |
32 | public function actionOut(): void
33 | {
34 | if ($this->user->isLoggedIn()) {
35 | $this->user->logout();
36 | $this->flashSuccess('_front.sign.out.success');
37 | }
38 |
39 | $this->redirect(App::DESTINATION_AFTER_SIGN_OUT);
40 | }
41 |
42 | public function processLoginForm(BaseForm $form): void
43 | {
44 | try {
45 | $this->user->setExpiration($form->values->remember ? '14 days' : '20 minutes');
46 | $this->user->login($form->values->email, $form->values->password);
47 | } catch (AuthenticationException $e) {
48 | $form->addError('Invalid username or password');
49 |
50 | return;
51 | }
52 |
53 | $this->redirect(App::DESTINATION_AFTER_SIGN_IN);
54 | }
55 |
56 | protected function createComponentLoginForm(): BaseForm
57 | {
58 | $form = $this->formFactory->forBackend();
59 | $form->addEmail('email')
60 | ->setRequired(true);
61 | $form->addPassword('password')
62 | ->setRequired(true);
63 | $form->addCheckbox('remember')
64 | ->setDefaultValue(true);
65 | $form->addSubmit('submit');
66 | $form->onSuccess[] = [$this, 'processLoginForm'];
67 |
68 | return $form;
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/app/UI/Modules/Admin/Sign/templates/in.latte:
--------------------------------------------------------------------------------
1 | {block #content}
2 |
30 |
--------------------------------------------------------------------------------
/app/UI/Modules/Admin/templates/@layout.latte:
--------------------------------------------------------------------------------
1 | {layout '../../Base/templates/@layout.latte'}
2 |
3 | {block #head}
4 |
5 |
6 |
7 | {/block}
8 |
--------------------------------------------------------------------------------
/app/UI/Modules/Base/BaseError4xxPresenter.php:
--------------------------------------------------------------------------------
1 | getRequest() !== null && $this->getRequest()->isMethod(Request::FORWARD)) {
21 | return;
22 | }
23 |
24 | $this->error();
25 | }
26 |
27 | public function renderDefault(BadRequestException $exception): void
28 | {
29 | $rf1 = new ComponentReflection(static::class);
30 | $fileName = $rf1->getFileName();
31 |
32 | // Validate if class is not in PHP core
33 | if ($fileName === false) {
34 | throw new InvalidStateException('Class is defined in the PHP core or in a PHP extension');
35 | }
36 |
37 | $dir = dirname($fileName);
38 |
39 | // Load template 403.latte or 404.latte or ... 4xx.latte
40 | $file = $dir . '/templates/' . $exception->getCode() . '.latte';
41 | $this->template->setFile(is_file($file) ? $file : $dir . '/templates/4xx.latte');
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/app/UI/Modules/Base/BaseErrorPresenter.php:
--------------------------------------------------------------------------------
1 | getParameter('exception');
27 |
28 | if ($e instanceof Throwable) {
29 | $code = $e->getCode();
30 | $level = ($code >= 400 && $code <= 499) ? LogLevel::WARNING : LogLevel::ERROR;
31 |
32 | Debugger::log(sprintf(
33 | 'Code %s: %s in %s:%s',
34 | $code,
35 | $e->getMessage(),
36 | $e->getFile(),
37 | $e->getLine()
38 | ), $level);
39 |
40 | Debugger::log($e, ILogger::EXCEPTION);
41 | }
42 |
43 | if ($e instanceof BadRequestException) {
44 | [$module, , $sep] = Helpers::splitName($request->getPresenterName());
45 |
46 | return new ForwardResponse($request->setPresenterName($module . $sep . 'Error4xx'));
47 | }
48 |
49 | return new CallbackResponse(function (IRequest $httpRequest, IResponse $httpResponse): void {
50 | $header = $httpResponse->getHeader('Content-Type');
51 | if ($header !== null && preg_match('#^text/html(?:;|$)#', $header) !== false) {
52 | require __DIR__ . '/templates/500.phtml';
53 | }
54 | });
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/app/UI/Modules/Base/BasePresenter.php:
--------------------------------------------------------------------------------
1 | user->isLoggedIn()) {
14 | if ($this->user->getLogoutReason() === User::LogoutInactivity) {
15 | $this->flashInfo('You have been logged out for inactivity');
16 | }
17 |
18 | $this->redirect(
19 | App::DESTINATION_SIGN_IN,
20 | ['backlink' => $this->storeRequest()]
21 | );
22 | }
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/app/UI/Modules/Base/UnsecuredPresenter.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Server Error
5 |
6 |
13 |
14 |
15 |
16 |
Server Error
17 |
18 |
We're sorry! The server encountered an internal error and
19 | was unable to complete your request. Please try again later.
20 |
21 |
error 500
22 |
23 |
24 |
25 |
28 |
--------------------------------------------------------------------------------
/app/UI/Modules/Base/templates/@layout.latte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {block #title|stripHtml|trim}Webapp Skeleton{/} | Contributte
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | {block #head}{/}
20 |
21 |
22 | {block #main}
23 |
24 | {include #content}
25 |
26 | {/}
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/UI/Modules/Front/BaseFrontPresenter.php:
--------------------------------------------------------------------------------
1 | Access Denied
3 |
4 | You do not have permission to view this page. Please try contact the web
5 | site administrator if you believe you should be able to view this page.
6 |
7 | error 403
8 |
--------------------------------------------------------------------------------
/app/UI/Modules/Front/Error4xx/templates/404.latte:
--------------------------------------------------------------------------------
1 | {block #content}
2 | Page Not Found
3 |
4 | The page you requested could not be found. It is possible that the address is
5 | incorrect, or that the page no longer exists. Please use a search engine to find
6 | what you are looking for.
7 |
8 | error 404
9 |
--------------------------------------------------------------------------------
/app/UI/Modules/Front/Error4xx/templates/405.latte:
--------------------------------------------------------------------------------
1 | {block #content}
2 | Method Not Allowed
3 |
4 | The requested method is not allowed for the URL.
5 |
6 | error 405
7 |
--------------------------------------------------------------------------------
/app/UI/Modules/Front/Error4xx/templates/410.latte:
--------------------------------------------------------------------------------
1 | {block #content}
2 | Page Not Found
3 |
4 | The page you requested has been taken off the site. We apologize for the inconvenience.
5 |
6 | error 410
7 |
--------------------------------------------------------------------------------
/app/UI/Modules/Front/Error4xx/templates/4xx.latte:
--------------------------------------------------------------------------------
1 | {block #content}
2 | Oops...
3 |
4 | Your browser sent a request that this server could not understand or process.
5 |
--------------------------------------------------------------------------------
/app/UI/Modules/Front/Home/HomePresenter.php:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 | {/}
8 |
9 | {block #main}
10 |
38 | {/}
39 |
--------------------------------------------------------------------------------
/app/UI/Modules/Mailing/Home/HomePresenter.php:
--------------------------------------------------------------------------------
1 | mailBuilderFactory = $mailBuilderFactory;
18 | }
19 |
20 | public function actionDefault(): void
21 | {
22 | $mail = $this->mailBuilderFactory->create();
23 | $mail->setSubject('Example');
24 | $mail->addTo('foo@example.com');
25 |
26 | $mail->setTemplateFile(__DIR__ . '/templates/Emails/email.latte');
27 | $mail->setParameters([
28 | 'title' => 'Title',
29 | 'content' => 'Lorem ipsum dolor sit amet',
30 | ]);
31 |
32 | $mail->send();
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/app/UI/Modules/Mailing/Home/templates/@layout.latte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {ifset title}{include title|stripHtml} | {/ifset}Nette Web
8 |
9 |
10 |
11 | {$flash->message}
12 |
13 | {include content}
14 |
15 | {block scripts}
16 |
17 | {/block}
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/UI/Modules/Mailing/Home/templates/Emails/email.latte:
--------------------------------------------------------------------------------
1 | {layout $_config->layout}
2 |
3 | {block #header}
4 | {$title}
5 | {/block}
6 |
7 | {block #content}
8 | {$content}
9 | {/block}
10 |
--------------------------------------------------------------------------------
/app/UI/Modules/Mailing/Home/templates/Home/default.latte:
--------------------------------------------------------------------------------
1 | {* This is the welcome page, you can delete it *}
2 |
3 | {block content}
4 |
5 |
Congratulations!
6 |
7 |
8 |
9 |
You have successfully created your Nette Web project.
10 |
11 |
12 | If you are exploring Nette for the first time, you should read the
13 | Quick Start , documentation ,
14 | tutorials and forum .
15 |
16 |
We hope you enjoy Nette!
17 |
18 |
19 |
39 |
--------------------------------------------------------------------------------
/app/UI/Modules/Pdf/Home/HomePresenter.php:
--------------------------------------------------------------------------------
1 | createPdf();
18 | $pdf->setSaveMode(PdfResponse::INLINE);
19 |
20 | $this->sendResponse($pdf);
21 | }
22 |
23 | public function actionDownloadPdf(): void
24 | {
25 | $pdf = $this->createPdf();
26 | $pdf->setSaveMode(PdfResponse::DOWNLOAD);
27 |
28 | $this->sendResponse($pdf);
29 | }
30 |
31 | private function createPdf(): PdfResponse
32 | {
33 | /** @var Template $template */
34 | $template = $this->createTemplate();
35 | $template->setFile(__DIR__ . '/../../../../../resources/pdf/example.latte');
36 | $template->title = 'Contributte PDF example';
37 |
38 | $this->pdfResponse->setTemplate($template->renderToString());
39 | $this->pdfResponse->documentTitle = 'Contributte PDF example'; // creates filename 2012-06-30-my-super-title.pdf
40 | $this->pdfResponse->pageFormat = 'A4-L'; // wide format
41 | $this->pdfResponse->getMPDF()->SetFooter('|Contributte PDF|'); // footer
42 |
43 | return $this->pdfResponse;
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/app/UI/Modules/Pdf/Home/templates/default.latte:
--------------------------------------------------------------------------------
1 | {block #content}
2 |
3 |
Contributte PDF example
4 |
5 |
6 |
7 |
Welcome in example of using contributte/pdf.
8 |
9 |
View generated PDF
10 |
11 |
Download generated PDF
12 |
13 |
14 |
15 |
35 |
--------------------------------------------------------------------------------
/app/UI/Modules/Pdf/templates/@layout.latte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {ifset title}{include title|stripHtml} | {/ifset}Nette Web
8 |
9 |
10 |
11 | {$flash->message}
12 |
13 | {include content}
14 |
15 | {block scripts}
16 |
17 | {/block}
18 |
19 |
20 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | =8.1",
16 |
17 | "contributte/bootstrap": "^0.6.0",
18 | "contributte/application": "^0.5.0",
19 | "contributte/cache": "^0.6.0",
20 | "contributte/console": "^0.10.0",
21 | "contributte/console-extra": "^0.8.0",
22 | "contributte/event-dispatcher": "^0.10.0",
23 | "contributte/event-dispatcher-extra": "^0.10.0",
24 | "contributte/di": "^0.5.0",
25 | "contributte/forms": "^0.5.0",
26 | "contributte/http": "^0.4.0",
27 | "contributte/latte": "^0.5.0",
28 | "contributte/mail": "^0.7.0",
29 | "contributte/mailing": "^0.5.0",
30 | "contributte/monolog": "^0.5.0",
31 | "contributte/security": "^0.4.0",
32 | "contributte/utils": "^0.6.0",
33 | "contributte/tracy": "^0.6.0",
34 | "contributte/pdf": "^7.0.0",
35 |
36 | "nettrine/annotations": "^0.7.0",
37 | "nettrine/orm": "^0.8.0",
38 | "nettrine/dbal": "^0.8.0",
39 | "nettrine/cache": "^0.3.0",
40 | "nettrine/migrations": "^0.9.1",
41 | "nettrine/fixtures": "^0.8.0"
42 | },
43 | "require-dev": {
44 | "contributte/qa": "^0.3",
45 | "contributte/tester": "^0.2",
46 | "contributte/phpstan": "^0.1",
47 | "contributte/dev": "^0.5",
48 | "mockery/mockery": "^1.3.0",
49 | "nelmio/alice": "^3.5.8",
50 | "phpstan/phpstan-doctrine": "^1.3.40"
51 | },
52 | "autoload": {
53 | "psr-4": {
54 | "App\\": "app",
55 | "Database\\": "db"
56 | }
57 | },
58 | "autoload-dev": {
59 | "psr-4": {
60 | "Tests\\": "tests"
61 | }
62 | },
63 | "prefer-stable": true,
64 | "minimum-stability": "dev",
65 | "config": {
66 | "allow-plugins": {
67 | "dealerdirect/phpcodesniffer-composer-installer": true,
68 | "composer/package-versions-deprecated": true
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/config/app/parameters.neon:
--------------------------------------------------------------------------------
1 | # Parameters
2 | #
3 | parameters:
4 |
5 | # System
6 | system:
7 | error:
8 | email: dev@dev.dev
9 | presenter: Front:Error
10 |
11 | # Database
12 | database:
13 | driver: pdo_pgsql
14 | port: 5432
15 |
16 | # Project
17 | project:
18 | rev: "1.0.0"
19 |
20 | # File
21 | files:
22 | root: %appDir%/../data/files
23 |
24 | # Mailing
25 | mailing:
26 | from: webapp@localhost
27 | from_name: Webapp
28 |
29 | # STMP
30 | smtp:
31 | host: localhost
32 | username: fake
33 | password: fake
34 |
--------------------------------------------------------------------------------
/config/app/services.neon:
--------------------------------------------------------------------------------
1 | # Services
2 | #
3 | services:
4 | # Forms ===================
5 | - App\UI\Form\FormFactory
6 |
7 | # Latte ===================
8 | latte.latteFactory:
9 | setup:
10 | - addFilter(datetime, App\Model\Latte\Filters::datetime)
11 | - addFilter(neon, App\Model\Latte\Filters::neon)
12 | - addFilter(json, App\Model\Latte\Filters::json)
13 |
14 | latte.templateFactory:
15 | class: App\Model\Latte\TemplateFactory
16 |
17 | # Security ================
18 | nette.userStorage:
19 | setup:
20 | - setNamespace("Webapp")
21 |
22 | security.passwords: App\Model\Security\Passwords
23 | security.user: App\Model\Security\SecurityUser
24 | security.authenticator: App\Model\Security\Authenticator\UserAuthenticator
25 | security.authorizator: App\Model\Security\Authorizator\StaticAuthorizator
26 |
27 | # Routing ================
28 | - App\Model\Router\RouterFactory
29 | router:
30 | type: Nette\Application\IRouter
31 | factory: @App\Model\Router\RouterFactory::create
32 |
33 | # Domain =================
34 | - App\Domain\User\CreateUserFacade
35 |
36 | # Console ================
37 | - App\Console\HelloCommand
38 |
39 | - App\Model\Database\QueryManager
40 |
41 | latte:
42 | macros:
43 | - App\Model\Latte\Macros::register
44 |
--------------------------------------------------------------------------------
/config/env/base.neon:
--------------------------------------------------------------------------------
1 | # Core Config
2 | includes:
3 | # Application
4 | - ../app/parameters.neon
5 | - ../app/services.neon
6 |
7 | # Extensions
8 | - ../ext/contributte.neon
9 | - ../ext/nettrine.neon
10 |
11 | php:
12 | date.timezone: Europe/Prague
13 | output_buffering: 4096
14 |
15 | # Nette section
16 | session:
17 | autoStart: smart
18 | #cookieDomain: '?->getUrl()->getDomain(4)'(@Nette\Http\IRequest)
19 | cookieHttponly: true
20 | #cookiePath: '?->getUrl()->getBasePath()'(@Nette\Http\IRequest) # Cookie path same as $basePath
21 | cookieSamesite: Lax
22 | debugger: false
23 | expiration: 1 year
24 | name: SID
25 | #savePath: %tempDir%/session
26 | sidBitsPerCharacter: 6 # 4-6
27 | sidLength: 128 # 22-250
28 | useCookies: true
29 | useOnlyCookies: true
30 | useStrictMode: true
31 |
32 | http:
33 | cookieSecure: auto
34 |
35 | application:
36 | catchExceptions: %productionMode%
37 | errorPresenter: %system.error.presenter%
38 | mapping:
39 | Admin: [App\UI\Modules\Admin, *, *\*Presenter]
40 | Front: [App\UI\Modules\Front, *, *\*Presenter]
41 | Mailing: [App\UI\Modules\Mailing, *, *\*Presenter]
42 | Pdf: [App\UI\Modules\Pdf, *, *\*Presenter]
43 |
44 | di:
45 | debugger: true
46 |
47 | tracy:
48 | email: %system.error.email%
49 | logSeverity: E_ALL
50 | strictMode: yes
51 |
--------------------------------------------------------------------------------
/config/env/dev.neon:
--------------------------------------------------------------------------------
1 | # Development Config
2 | includes:
3 | - base.neon
4 |
5 |
6 | # Nettrine ===================
7 | nettrine.cache:
8 | # driver: Doctrine\Common\Cache\ApcuCache
9 |
10 |
11 | # Services ===================
12 | services:
13 | # mail.mailer: Contributte\Mail\Mailer\TraceableMailer(Contributte\Mail\Mailer\FileMailer(%tempDir%/mails))
14 |
--------------------------------------------------------------------------------
/config/env/prod.neon:
--------------------------------------------------------------------------------
1 | # Production Config
2 | includes:
3 | - base.neon
4 |
--------------------------------------------------------------------------------
/config/env/test.neon:
--------------------------------------------------------------------------------
1 | # Test Config
2 | includes:
3 | - base.neon
4 |
5 |
6 | # Post/Mailing ===============
7 | contributte.post:
8 |
9 | # Nettrine ===================
10 | nettrine.orm:
11 | configuration:
12 | autoGenerateProxyClasses: ::constant(Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS)
13 |
14 | nettrine.cache:
15 | driver: Doctrine\Common\Cache\ArrayCache
16 |
17 |
18 | # Services ===================
19 | services:
20 | security.userStorage: Tests\Toolkit\Nette\DummyUserStorage
21 |
22 | mail.mailer: Contributte\Mail\Mailer\FileMailer(%appDir%/../tests/tmp/mails)
23 |
24 | # Parameters =================
25 | parameters:
26 | database:
27 | driver: pdo_sqlite
28 | host: test
29 | dbname: test
30 | user: test
31 | password: test
32 |
--------------------------------------------------------------------------------
/config/ext/contributte.neon:
--------------------------------------------------------------------------------
1 | # Extension > Contributte
2 | #
3 | extensions:
4 | contributte.console: Contributte\Console\DI\ConsoleExtension(%consoleMode%)
5 | contributte.console.extra: Contributte\Console\Extra\DI\ConsoleBridgesExtension(%consoleMode%)
6 | contributte.events: Contributte\EventDispatcher\DI\EventDispatcherExtension
7 | contributte.events2nette: Contributte\Events\Extra\DI\EventBridgesExtension
8 | contributte.monolog: Contributte\Monolog\DI\MonologExtension
9 | contributte.mailing: Contributte\Mailing\DI\MailingExtension
10 | contributte.post: Contributte\Mail\DI\MailExtension
11 |
12 | contributte.events:
13 | debug: %debugMode%
14 |
15 | contributte.console:
16 | url: http://localhost/
17 |
18 | contributte.mailing:
19 | template:
20 | config:
21 | layout: %appDir%/resources/mail/@layout.latte
22 |
23 | contributte.monolog:
24 | holder:
25 | enabled: true
26 | hook:
27 | toTracy: false
28 | channel:
29 | default:
30 | handlers:
31 | - Monolog\Handler\RotatingFileHandler(%appDir%/../var/log/syslog.log, 30, Monolog\Logger::WARNING)
32 | processors:
33 | - Monolog\Processor\WebProcessor()
34 | - Monolog\Processor\IntrospectionProcessor()
35 | - Monolog\Processor\MemoryPeakUsageProcessor()
36 | - Monolog\Processor\ProcessIdProcessor()
37 |
38 | services:
39 | -
40 | factory: Contributte\PdfResponse\PdfResponse
41 | setup:
42 | - $mpdfConfig([tempDir: %tempDir%/mpdf])
43 |
--------------------------------------------------------------------------------
/config/ext/nettrine.neon:
--------------------------------------------------------------------------------
1 | # Extension > Nettrine
2 | #
3 | extensions:
4 | nettrine.annotations: Nettrine\Annotations\DI\AnnotationsExtension
5 | nettrine.cache: Nettrine\Cache\DI\CacheExtension
6 | nettrine.migrations: Nettrine\Migrations\DI\MigrationsExtension
7 | nettrine.fixtures: Nettrine\Fixtures\DI\FixturesExtension
8 |
9 | # Dbal
10 | nettrine.dbal: Nettrine\DBAL\DI\DbalExtension
11 | nettrine.dbal.console: Nettrine\DBAL\DI\DbalConsoleExtension(%consoleMode%)
12 |
13 | # Orm
14 | nettrine.orm: Nettrine\ORM\DI\OrmExtension
15 | nettrine.orm.cache: Nettrine\ORM\DI\OrmCacheExtension
16 | nettrine.orm.console: Nettrine\ORM\DI\OrmConsoleExtension(%consoleMode%)
17 | nettrine.orm.annotations: Nettrine\ORM\DI\OrmAnnotationsExtension
18 |
19 | nettrine.dbal:
20 | debug:
21 | panel: %debugMode%
22 | configuration:
23 | sqlLogger: Nettrine\DBAL\Logger\PsrLogger(@Monolog\Logger)
24 | connection:
25 | driver: %database.driver%
26 | host: %database.host%
27 | user: %database.user%
28 | password: %database.password%
29 | dbname: %database.dbname%
30 | port: %database.port%
31 | serverVersion: 10.0
32 |
33 | nettrine.orm:
34 | entityManagerDecoratorClass: App\Model\Database\EntityManagerDecorator
35 | configuration:
36 | autoGenerateProxyClasses: %debugMode%
37 |
38 | nettrine.orm.annotations:
39 | mapping:
40 | App\Domain: %appDir%/Domain
41 |
42 | nettrine.migrations:
43 | table: doctrine_migrations
44 | column: version
45 | directory: %rootDir%/db/Migrations
46 | namespace: Database\Migrations
47 | versionsOrganization: null
48 |
49 | nettrine.fixtures:
50 | paths:
51 | - %rootDir%/db/Fixtures
52 |
--------------------------------------------------------------------------------
/config/local.neon.example:
--------------------------------------------------------------------------------
1 | # Host Config
2 | parameters:
3 |
4 | # Database
5 | database:
6 | host: 0.0.0.0
7 | dbname: contributte
8 | user: contributte
9 | password: contributte
10 |
11 | # For 0.0.0.0 instead of localhost
12 | # php -S 0.0.0.0:8000 -t www
13 | # session:
14 | # cookieDomain: "?->getUrl()->getDomain(4)"(@Nette\Http\IRequest)
15 |
16 | # STMP server
17 | # E.q. mailtrap is cool.
18 | # smtp:
19 | # host: smtp.mailtrap.io
20 | # username: "***"
21 | # password: ***
22 | # port: 2525
23 |
--------------------------------------------------------------------------------
/db/Fixtures/AbstractFixture.php:
--------------------------------------------------------------------------------
1 | faker = Factory::create();
27 | }
28 |
29 | public function setContainer(Container $container): void
30 | {
31 | $this->container = $container;
32 | }
33 |
34 | public function getOrder(): int
35 | {
36 | return 0;
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/db/Fixtures/UserFixture.php:
--------------------------------------------------------------------------------
1 | getUsers() as $user) {
20 | $entity = new User(
21 | $user['name'],
22 | $user['surname'],
23 | $user['email'],
24 | $user['username'],
25 | $this->container->getByType(Passwords::class)->hash('admin')
26 | );
27 | $entity->activate();
28 | $entity->setRole($user['role']);
29 |
30 | $manager->persist($entity);
31 | }
32 | $manager->flush();
33 | }
34 |
35 | /**
36 | * @return mixed[]
37 | */
38 | protected function getUsers(): iterable
39 | {
40 | yield ['email' => 'admin@admin.cz', 'name' => 'Contributte', 'surname' => 'Admin', 'username' => 'contributte', 'role' => User::ROLE_ADMIN];
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/db/Migrations/Version20190407153346.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
24 |
25 | $this->addSql('CREATE SEQUENCE "user_id_seq" INCREMENT BY 1 MINVALUE 1 START 1');
26 | $this->addSql('CREATE TABLE "user" (id INT NOT NULL, name VARCHAR(255) NOT NULL, surname VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, username VARCHAR(255) NOT NULL, state INT NOT NULL, password VARCHAR(255) NOT NULL, role VARCHAR(255) NOT NULL, last_logged_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id))');
27 | $this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649E7927C74 ON "user" (email)');
28 | $this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649F85E0677 ON "user" (username)');
29 | }
30 |
31 | public function down(Schema $schema) : void
32 | {
33 | // this down() migration is auto-generated, please modify it to your needs
34 | $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
35 |
36 | $this->addSql('DROP SEQUENCE "user_id_seq" CASCADE');
37 | $this->addSql('DROP TABLE "user"');
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.4'
2 |
3 | services:
4 | nginx:
5 | build:
6 | context: .
7 | dockerfile: ./.docker/nginx/Dockerfile
8 | volumes:
9 | - ./:/var/www/html/
10 | - ./.docker/nginx/nginx.conf:/etc/nginx/nginx.conf
11 | - ./.docker/nginx/sites/:/etc/nginx/sites-available
12 | - ./.docker/nginx/conf.d/:/etc/nginx/conf.d
13 | depends_on:
14 | - php
15 | ports:
16 | - 8080:80
17 | - 8443:443
18 |
19 | php:
20 | build:
21 | context: .
22 | dockerfile: ./.docker/php/Dockerfile
23 | volumes:
24 | - ./:/var/www/html/
25 | depends_on:
26 | - database
27 | environment:
28 | NETTE_DEBUG: 1
29 | PHP_EXTENSION_XDEBUG: 1
30 | PHP_EXTENSION_PGSQL: 1
31 | PHP_EXTENSION_PDO_PGSQL: 1
32 | PHP_EXTENSION_MYSQLI: 0
33 | PHP_EXTENSION_GD: 1
34 | PHP_EXTENSION_INTL: 1
35 | STARTUP_COMMAND_1: composer install
36 | STARTUP_COMMAND_2: NETTE_DEBUG=1 php bin/console migrations:migrate --no-interaction --allow-no-migration
37 | STARTUP_COMMAND_3: NETTE_DEBUG=1 php bin/console doctrine:fixtures:load --no-interaction
38 |
39 | database:
40 | image: dockette/postgres:10
41 | environment:
42 | - POSTGRES_PASSWORD=contributte
43 | - POSTGRES_USER=contributte
44 | - POSTGRES_DB=contributte
45 | volumes:
46 | - .docker/data/postgres:/var/lib/postgresql/data
47 |
48 | adminer:
49 | image: dockette/adminer:dg
50 | ports:
51 | - 8080:80
52 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - vendor/contributte/phpstan/phpstan.neon
3 | - vendor/phpstan/phpstan-doctrine/extension.neon
4 |
5 | parameters:
6 | level: 9
7 | phpVersion: 80100
8 |
9 | tmpDir: %currentWorkingDirectory%/var/tmp/phpstan
10 |
11 | fileExtensions:
12 | - php
13 | - phpt
14 |
15 | paths:
16 | - app
17 | - bin
18 |
19 | doctrine:
20 | objectManagerLoader: .build/phpstan-doctrine.php
21 | ormRepositoryClass: App\Model\Database\Repository\AbstractRepository
22 |
--------------------------------------------------------------------------------
/resources/mail/@layout.latte:
--------------------------------------------------------------------------------
1 | {layout $_defaults->layout}
2 |
3 | {block #title}
4 | Webapp Skeleton
5 | {/block}
6 |
7 | {block #copyright}
8 | Webapp Skeleton
9 | {/block}
10 |
--------------------------------------------------------------------------------
/resources/mail/signIn/signIn.latte:
--------------------------------------------------------------------------------
1 | {layout $_config->layout}
2 |
3 | {block #header}
4 | Automatické přihlášení
5 | {/block}
6 |
7 | {block #content}
8 | Přihlášení
9 | Přihlásit se
10 | {/block}
11 |
--------------------------------------------------------------------------------
/resources/pdf/example.latte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {$title}
8 |
9 |
10 |
11 |
12 | Lorem ipsum
13 |
14 | Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Neque porro quisquam est, qui dolorem ipsum quia
15 | dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et
16 | dolore magnam aliquam quaerat voluptatem. Sed elit dui, pellentesque a, faucibus vel, interdum nec, diam.
17 | Etiam posuere lacus quis dolor. Aliquam ante. Sed vel lectus. Donec odio tempus molestie, porttitor ut,
18 | iaculis quis, sem. Aliquam erat volutpat. Fusce wisi. Cum sociis natoque penatibus et magnis dis parturient
19 | montes, nascetur ridiculus mus. Nullam eget nisl. Etiam bibendum elit eget erat.
20 |
21 | ReadMe
22 | Usage
23 | How to prepare PDF from template
24 |
25 | public function actionPdf ()
27 | {
28 | $template = $this ->createTemplate();
29 | $template->setFile(__DIR__ . "/path/to/template.latte" );
30 | $template->someValue = 123 ;
31 |
32 |
33 | $pdf = new \Contributte\PdfResponse\PdfResponse($template);
34 |
35 |
36 | $pdf->documentTitle = date("Y-m-d" ) . " My super title" ;
38 | $pdf->pageFormat = "A4-L" ;
39 | $pdf->getMPDF()->setFooter("|© www.mysite.com|" );
40 |
41 |
42 | $this ->sendResponse($pdf);
43 | }
44 |
45 | Save file to server
46 | public function actionPdf ()
49 | {
50 | $template = $this ->createTemplate();
51 | $template->setFile(__DIR__ . "/path/to/template.latte" );
52 |
53 | $pdf = new \Contributte\PdfResponse\PdfResponse($template);
54 |
55 | $pdf->save(__DIR__ . "/path/to/directory" );
57 | $pdf->save(__DIR__ . "/path/to/directory" , "filename" );
60 | }
61 |
62 | Attach file to an email
63 | public function actionPdf ()
66 | {
67 | $template = $this ->createTemplate();
68 | $template->setFile(__DIR__ . "/path/to/template.latte" );
69 |
70 | $pdf = new \Contributte\PdfResponse\PdfResponse($template);
71 |
72 | $savedFile = $pdf->save(__DIR__ . "/path/to/directory" );
73 | $mail = new Nette\Mail\Message;
74 | $mail->addTo("john@doe.com" );
75 | $mail->addAttachment($savedFile);
76 | $mailer = new SendmailMailer();
77 | $mailer->send($mail);
78 | }
79 |
80 | Force file to download
81 | public function actionPdf ()
84 | {
85 | $template = $this ->createTemplate();
86 | $template->setFile(__DIR__ . "/path/to/template.latte" );
87 |
88 | $pdf = new \Contributte\PdfResponse\PdfResponse($template);
89 | $pdf->setSaveMode(PdfResponse::DOWNLOAD);
90 | $this ->sendResponse($pdf);
91 | }
92 |
93 | Force file to display in a browser
94 | public function actionPdf ()
97 | {
98 | $template = $this ->createTemplate();
99 | $template->setFile(__DIR__ . "/path/to/template.latte" );
100 |
101 | $pdf = new \Contributte\PdfResponse\PdfResponse($template);
102 | $pdf->setSaveMode(PdfResponse::INLINE);
103 | $this ->sendResponse($pdf);
104 | }
105 |
106 | Set a pdf background easily
107 | public function actionPdf ()
110 | {
111 | $pdf = new \Contributte\PdfResponse\PdfResponse('' );
113 | $pdf->setBackgroundTemplate(__DIR__ . "/path/to/an/existing/file.pdf" );
114 |
115 |
116 | $mpdf = $pdf->getMPDF();
117 | $mpdf->WriteFixedPosHTML('hello world' , 1 , 10 , 10 , 10 );
120 |
121 |
122 | $mpdf->AddPage();
123 |
124 |
125 | $mpdf->page = 3 ;
126 |
127 | $this ->sendResponse($pdf);
128 | }
129 |
130 | Create pdf with latte only
131 | public function actionPdf ()
134 | {
135 | $latte = new Latte\Engine;
136 | $latte->setTempDirectory('/path/to/cache' );
137 | $latte->addFilter('money' , function ($val) { return ...; });
141 |
142 | $latte->onCompile[] = function ($latte) {
144 | $latte->addMacro(...);
145 | };
146 |
147 | $template = $latte->renderToString(__DIR__ . "/path/to/template.latte" );
148 |
149 | $pdf = new \Contributte\PdfResponse\PdfResponse($template);
150 | $this ->sendResponse($pdf);
151 | }
152 |
153 |
154 | Configuration of custom temp dir for mPDF in PdfResponse
155 |
156 | services:
157 | - factory: Contributte\PdfResponse\PdfResponse
158 | setup:
159 | - $mpdfConfig([tempDir: %tempDir%/mpdf])
161 |
162 | See also
163 |
167 |
168 | Thanks for testing, reporting and contributing.
169 |
170 |
171 |
172 |
--------------------------------------------------------------------------------
/resources/tracy/500.json:
--------------------------------------------------------------------------------
1 | {
2 | "code": 500,
3 | "error": "Internal server error"
4 | }
--------------------------------------------------------------------------------
/resources/tracy/500.phtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Server Error
9 |
228 |
229 |
230 |
231 | 500
232 | Unexpected Error :(
233 |
250 |
251 |
252 |
--------------------------------------------------------------------------------
/resources/tracy/500.txt:
--------------------------------------------------------------------------------
1 | Internal server error
--------------------------------------------------------------------------------
/ruleset.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Contributte
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/tests/.coveralls.yml:
--------------------------------------------------------------------------------
1 | # for php-coveralls
2 | service_name: github-actions
3 | coverage_clover: coverage.xml
4 | json_path: coverage.json
5 |
--------------------------------------------------------------------------------
/tests/.gitignore:
--------------------------------------------------------------------------------
1 | # Folders - recursive
2 | *.expected
3 | *.actual
4 |
5 | # Folders
6 | /tmp
7 |
8 | # Files
9 | /*.log
10 | /*.html
11 |
--------------------------------------------------------------------------------
/tests/Cases/E2E/Container/EntrypointTest.php:
--------------------------------------------------------------------------------
1 | createContainer();
34 | $container->getByType(WebApplication::class);
35 |
36 | Assert::type(Container::class, $container);
37 | }
38 |
39 | public function testCli(): void
40 | {
41 | $container = Bootstrap::boot()->createContainer();
42 | $container->getByType(ConsoleApplication::class);
43 |
44 | Assert::type(Container::class, $container);
45 | }
46 |
47 | public function testTest(): void
48 | {
49 | $container = Bootstrap::boot()->createContainer();
50 | $container->getByType(Container::class);
51 |
52 | Assert::type(Container::class, $container);
53 | }
54 |
55 | }
56 |
57 | (new EntrypointTest())->run();
58 |
--------------------------------------------------------------------------------
/tests/Cases/E2E/Database/MappingTest.phpt:
--------------------------------------------------------------------------------
1 | createContainer();
15 |
16 | /** @var EntityManagerDecorator $em */
17 | $em = $container->getByType(EntityManagerDecorator::class);
18 |
19 | // Validation
20 | $validator = new SchemaValidator($em);
21 | $validations = $validator->validateMapping();
22 | foreach ($validations as $fails) {
23 | foreach ((array) $fails as $fail) {
24 | Assert::fail($fail);
25 | }
26 | }
27 |
28 | Assert::count(0, $validations);
29 | });
30 |
--------------------------------------------------------------------------------
/tests/Cases/E2E/Latte/CompilerTest.phpt:
--------------------------------------------------------------------------------
1 | createContainer();
19 |
20 | /** @var ITemplateFactory $templateFactory */
21 | $templateFactory = $container->getByType(ITemplateFactory::class);
22 | Assert::type(TemplateFactory::class, $templateFactory);
23 |
24 | /** @var Template $template */
25 | $template = $templateFactory->createTemplate();
26 | $finder = Finder::findFiles('*.latte')->from(APP_DIR);
27 |
28 | try {
29 | /** @var SplFileInfo $file */
30 | foreach ($finder as $file) {
31 | $template->getLatte()->warmupCache($file->getRealPath());
32 | }
33 | } catch (Throwable $e) {
34 | Assert::fail(sprintf('Template compilation failed ([%s] %s)', $e::class, $e->getMessage()));
35 | }
36 | });
37 |
--------------------------------------------------------------------------------
/tests/Cases/Unit/Model/Utils/Strings.phpt:
--------------------------------------------------------------------------------
1 | identity = $identity;
17 | }
18 |
19 | public function clearAuthentication(bool $clearIdentity): void
20 | {
21 | $this->identity = null;
22 | }
23 |
24 | /**
25 | * @return array{bool, ?IIdentity, ?int}
26 | */
27 | public function getState(): array
28 | {
29 | return [$this->identity !== null, $this->identity, null];
30 | }
31 |
32 | public function setExpiration(?string $expire, bool $clearIdentity): void
33 | {
34 | // Do nothing
35 | }
36 |
37 | public function setNamespace(string $namespace): self
38 | {
39 | return $this;
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/tests/Toolkit/Tests.php:
--------------------------------------------------------------------------------
1 |
2 | RewriteEngine On
3 | # RewriteBase /
4 |
5 | ## Redirect from non-www na www.example.com
6 | #RewriteCond %{HTTP_HOST} ^([^\.]+\.[^\.]+)$ [NC]
7 | #RewriteRule ^(.*)$ http://www.%1/$1 [R=301,L]
8 |
9 | ## Redirect from www to non-www
10 | #RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
11 | #RewriteRule ^(.*)$ http://%1/$1 [R=301,L]
12 |
13 | # Prevents files starting with dot to be viewed by browser
14 | RewriteRule /\.|^\.(?!well-known/) - [F]
15 |
16 | # Front controller
17 | RewriteCond %{REQUEST_FILENAME} !-f
18 | RewriteCond %{REQUEST_FILENAME} !-d
19 | RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz|map)$ index.php [L]
20 |
21 |
--------------------------------------------------------------------------------
/www/assets/admin.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | height: 100%;
4 | }
5 |
6 | body {
7 | display: -ms-flexbox;
8 | display: flex;
9 | -ms-flex-align: center;
10 | align-items: center;
11 | padding-top: 40px;
12 | padding-bottom: 40px;
13 | background-color: #f5f5f5;
14 | }
15 |
16 | .form-signin {
17 | width: 100%;
18 | max-width: 420px;
19 | padding: 15px;
20 | margin: auto;
21 | }
22 |
23 | .form-label-group {
24 | position: relative;
25 | margin-bottom: 1rem;
26 | }
27 |
28 | .form-label-group > input,
29 | .form-label-group > label {
30 | height: 3.125rem;
31 | padding: .75rem;
32 | }
33 |
34 | .form-label-group > label {
35 | position: absolute;
36 | top: 0;
37 | left: 0;
38 | display: block;
39 | width: 100%;
40 | margin-bottom: 0; /* Override default `` margin */
41 | line-height: 1.5;
42 | color: #495057;
43 | pointer-events: none;
44 | cursor: text; /* Match the input under the label */
45 | border: 1px solid transparent;
46 | border-radius: .25rem;
47 | transition: all .1s ease-in-out;
48 | }
49 |
50 | .form-label-group input::-webkit-input-placeholder {
51 | color: transparent;
52 | }
53 |
54 | .form-label-group input:-ms-input-placeholder {
55 | color: transparent;
56 | }
57 |
58 | .form-label-group input::-ms-input-placeholder {
59 | color: transparent;
60 | }
61 |
62 | .form-label-group input::-moz-placeholder {
63 | color: transparent;
64 | }
65 |
66 | .form-label-group input::placeholder {
67 | color: transparent;
68 | }
69 |
70 | .form-label-group input:not(:placeholder-shown) {
71 | padding-top: 1.25rem;
72 | padding-bottom: .25rem;
73 | }
74 |
75 | .form-label-group input:not(:placeholder-shown) ~ label {
76 | padding-top: .25rem;
77 | padding-bottom: .25rem;
78 | font-size: 12px;
79 | color: #777;
80 | }
81 |
82 | .bd-placeholder-img {
83 | font-size: 1.125rem;
84 | text-anchor: middle;
85 | }
86 |
87 | @media (min-width: 768px) {
88 | .bd-placeholder-img-lg {
89 | font-size: 3.5rem;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/www/assets/admin.js:
--------------------------------------------------------------------------------
1 | console.log('Check https://contributte.org');
2 |
--------------------------------------------------------------------------------
/www/assets/front.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Globals
3 | */
4 |
5 | /* Links */
6 | a,
7 | a:focus,
8 | a:hover {
9 | color: #fff;
10 | }
11 |
12 | /* Custom default button */
13 | .btn-secondary,
14 | .btn-secondary:hover,
15 | .btn-secondary:focus {
16 | color: #333;
17 | text-shadow: none; /* Prevent inheritance from `body` */
18 | background-color: #fff;
19 | border: .05rem solid #fff;
20 | }
21 |
22 |
23 | /*
24 | * Base structure
25 | */
26 |
27 | html,
28 | body {
29 | height: 100%;
30 | background-color: #333;
31 | }
32 |
33 | body {
34 | display: -ms-flexbox;
35 | display: flex;
36 | color: #fff;
37 | text-shadow: 0 .05rem .1rem rgba(0, 0, 0, .5);
38 | box-shadow: inset 0 0 5rem rgba(0, 0, 0, .5);
39 | }
40 |
41 | .cover-container {
42 | max-width: 42em;
43 | }
44 |
45 |
46 | /*
47 | * Header
48 | */
49 | .masthead {
50 | margin-bottom: 2rem;
51 | }
52 |
53 | .masthead-brand {
54 | margin-bottom: 0;
55 | }
56 |
57 | .nav-masthead .nav-link {
58 | padding: .25rem 0;
59 | font-weight: 700;
60 | color: rgba(255, 255, 255, .5);
61 | background-color: transparent;
62 | border-bottom: .25rem solid transparent;
63 | }
64 |
65 | .nav-masthead .nav-link:hover,
66 | .nav-masthead .nav-link:focus {
67 | border-bottom-color: rgba(255, 255, 255, .25);
68 | }
69 |
70 | .nav-masthead .nav-link + .nav-link {
71 | margin-left: 1rem;
72 | }
73 |
74 | .nav-masthead .active {
75 | color: #fff;
76 | border-bottom-color: #fff;
77 | }
78 |
79 | @media (min-width: 48em) {
80 | .masthead-brand {
81 | float: left;
82 | }
83 |
84 | .nav-masthead {
85 | float: right;
86 | }
87 | }
88 |
89 |
90 | /*
91 | * Cover
92 | */
93 | .cover {
94 | padding: 0 1.5rem;
95 | }
96 |
97 | .cover .btn-lg {
98 | padding: .75rem 1.25rem;
99 | font-weight: 700;
100 | }
101 |
102 | .cover p a {
103 | font-weight: bold;
104 | text-decoration: underline;
105 | }
106 |
107 |
108 | /*
109 | * Footer
110 | */
111 | .mastfoot {
112 | color: rgba(255, 255, 255, .5);
113 | }
114 |
115 | .bd-placeholder-img {
116 | font-size: 1.125rem;
117 | text-anchor: middle;
118 | }
119 |
120 | @media (min-width: 768px) {
121 | .bd-placeholder-img-lg {
122 | font-size: 3.5rem;
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/www/assets/front.js:
--------------------------------------------------------------------------------
1 | console.log('Check https://contributte.org');
2 |
--------------------------------------------------------------------------------
/www/index.php:
--------------------------------------------------------------------------------
1 |