This commit is contained in:
2022-01-13 18:41:03 +01:00
commit 0fb9f639da
159 changed files with 13183 additions and 0 deletions

2
app/.htaccess Normal file
View File

@@ -0,0 +1,2 @@
Order Allow,Deny
Deny from all

40
app/bootstrap.php Normal file
View File

@@ -0,0 +1,40 @@
<?php declare(strict_types = 1);
use Contributte\Bootstrap\ExtraConfigurator;
use Nette\DI\Compiler;
use Tracy\Debugger;
require __DIR__ . '/../vendor/autoload.php';
$configurator = new ExtraConfigurator();
$configurator->setTempDirectory(__DIR__ . '/../var/tmp');
$configurator->onCompile[] = function (ExtraConfigurator $configurator, Compiler $compiler): void {
// Add env variables to config structure
$compiler->addConfig(['parameters' => $configurator->getEnvironmentParameters()]);
};
// According to NETTE_DEBUG env
$configurator->setEnvDebugMode();
// Enable tracy and configure it
$configurator->enableTracy(__DIR__ . '/../var/log');
Debugger::$errorTemplate = __DIR__ . '/resources/tracy/500.phtml';
// Provide some parameters
$configurator->addParameters([
'rootDir' => realpath(__DIR__ . '/..'),
'appDir' => __DIR__,
'wwwDir' => realpath(__DIR__ . '/../www'),
]);
// Load development or production config
if (getenv('NETTE_ENV', true) === 'dev') {
$configurator->addConfig(__DIR__ . '/../config/env/dev.neon');
} else {
$configurator->addConfig(__DIR__ . '/../config/env/prod.neon');
}
$configurator->addConfig(__DIR__ . '/../config/local.neon');
return $configurator->createContainer();

View File

@@ -0,0 +1,25 @@
<?php declare(strict_types = 1);
namespace App\Domain\Http;
use Contributte\Events\Extra\Event\Application\RequestEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Tracy\Debugger;
class RequestLoggerSubscriber implements EventSubscriberInterface
{
/**
* @return mixed[]
*/
public static function getSubscribedEvents(): array
{
return [RequestEvent::class => 'onRequest'];
}
public function onRequest(RequestEvent $event): void
{
Debugger::barDump($event->getRequest());
}
}

View File

@@ -0,0 +1,25 @@
<?php declare(strict_types = 1);
namespace App\Domain\Order\Event;
use Symfony\Contracts\EventDispatcher\Event;
final class OrderCreated extends Event
{
public const NAME = 'order.created';
/** @var string */
private $order;
public function __construct(string $order)
{
$this->order = $order;
}
public function getOrder(): string
{
return $this->order;
}
}

View File

@@ -0,0 +1,42 @@
<?php declare(strict_types = 1);
namespace App\Domain\Order;
use App\Domain\Order\Event\OrderCreated;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Tracy\Debugger;
class OrderLogSubscriber implements EventSubscriberInterface
{
/**
* @return mixed[]
*/
public static function getSubscribedEvents(): array
{
return [
OrderCreated::NAME => [
['onOrderCreatedBefore', 100],
['onOrderCreated', 0],
['onOrderCreatedAfter', -100],
],
];
}
public function onOrderCreatedBefore(OrderCreated $event): void
{
Debugger::barDump('BEFORE');
}
public function onOrderCreated(OrderCreated $event): void
{
Debugger::log($event, 'info');
Debugger::barDump($event);
}
public function onOrderCreatedAfter(OrderCreated $event): void
{
Debugger::barDump('AFTER');
}
}

View File

@@ -0,0 +1,48 @@
<?php declare(strict_types = 1);
namespace App\Domain\User;
use App\Model\Database\Entity\User;
use App\Model\Database\EntityManager;
use App\Model\Security\Passwords;
class CreateUserFacade
{
/** @var EntityManager */
private $em;
public function __construct(
EntityManager $em
)
{
$this->em = $em;
}
/**
* @param mixed[] $data
*/
public function createUser(array $data): User
{
// Create User
$user = new User(
$data['name'],
$data['surname'],
$data['email'],
$data['username'],
Passwords::create()->hash($data['password'] ?? md5(microtime()))
);
// Set role
if (isset($data['role'])) {
$user->setRole($data['role']);
}
// Save user
$this->em->persist($user);
$this->em->flush();
return $user;
}
}

14
app/model/App.php Normal file
View File

@@ -0,0 +1,14 @@
<?php declare(strict_types = 1);
namespace App\Model;
final class App
{
public const DESTINATION_FRONT_HOMEPAGE = ':Front:Home:';
public const DESTINATION_ADMIN_HOMEPAGE = ':Admin:Home:';
public const DESTINATION_SIGN_IN = ':Admin:Sign:in';
public const DESTINATION_AFTER_SIGN_IN = self::DESTINATION_ADMIN_HOMEPAGE;
public const DESTINATION_AFTER_SIGN_OUT = self::DESTINATION_FRONT_HOMEPAGE;
}

View File

@@ -0,0 +1,25 @@
<?php declare(strict_types = 1);
namespace App\Model\Console;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class HelloCommand extends Command
{
protected function configure(): void
{
$this->setName('hello');
$this->setDescription('Hello world!');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->write('Hello world!');
return 0;
}
}

View File

@@ -0,0 +1,8 @@
<?php declare(strict_types = 1);
namespace App\Model\Database\Entity;
abstract class AbstractEntity
{
}

View File

@@ -0,0 +1,33 @@
<?php declare(strict_types = 1);
namespace App\Model\Database\Entity\Attributes;
use App\Model\Utils\DateTime;
use Doctrine\ORM\Mapping as ORM;
trait TCreatedAt
{
/**
* @var DateTime
* @ORM\Column(type="datetime", nullable=FALSE)
*/
protected $createdAt;
public function getCreatedAt(): DateTime
{
return $this->createdAt;
}
/**
* Doctrine annotation
*
* @ORM\PrePersist
* @internal
*/
public function setCreatedAt(): void
{
$this->createdAt = new DateTime();
}
}

View File

@@ -0,0 +1,27 @@
<?php declare(strict_types = 1);
namespace App\Model\Database\Entity\Attributes;
trait TId
{
/**
* @var int
* @ORM\Column(type="integer", nullable=FALSE)
* @ORM\Id
* @ORM\GeneratedValue
*/
private $id;
public function getId(): int
{
return $this->id;
}
public function __clone()
{
$this->id = null;
}
}

View File

@@ -0,0 +1,33 @@
<?php declare(strict_types = 1);
namespace App\Model\Database\Entity\Attributes;
use App\Model\Utils\DateTime;
use Doctrine\ORM\Mapping as ORM;
trait TUpdatedAt
{
/**
* @var DateTime|NULL
* @ORM\Column(type="datetime", nullable=TRUE)
*/
protected $updatedAt;
public function getUpdatedAt(): ?DateTime
{
return $this->updatedAt;
}
/**
* Doctrine annotation
*
* @ORM\PreUpdate
* @internal
*/
public function setUpdatedAt(): void
{
$this->updatedAt = new DateTime();
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace App\Model\Database\Entity;
use App\Model\Database\Entity\Attributes\TCreatedAt;
use App\Model\Database\Entity\Attributes\TId;
use App\Model\Database\Entity\Attributes\TUpdatedAt;
use App\Model\Exception\Logic\InvalidArgumentException;
use App\Model\Security\Identity;
use App\Model\Utils\DateTime;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class DictType extends AbstractEntity
{
use TId;
public function __construct($short_name,$full_name)
{
$this->shortName = $short_name;
$this->fullName = $full_name;
}
/**
* @ORM\Column(type="string")
*/
protected $shortName;
public function getShortName()
{
return $this->shortName;
}
/**
* @ORM\Column(type="string")
*/
protected $fullName;
public function getFullName()
{
return $this->fullName;
}
}
?>

View File

@@ -0,0 +1,76 @@
<?php
namespace App\Model\Database\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Model\Database\Entity\Attributes\TId;
/**
* @ORM\Entity
*/
class Dictionary extends AbstractEntity
{
use Tid;
public function __construct($name,$fullname)
{
$this->name = $name;
$this->fullName = $fullname;
$this->translations = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* @ORM\OneToMany(targetEntity="App\Model\Database\Entity\Translation", mappedBy="dictionary", cascade={"persist"})
* @var Article[]|\Doctrine\Common\Collections\ArrayCollection
*/
protected $translations;
public function getTranslations()
{
return $this->translations;
}
/**
* @ORM\ManyToOne(targetEntity="Language", inversedBy="lang1_dicionaries")
*/
protected $lang1;
/**
* @ORM\ManyToOne(targetEntity="Language", inversedBy="lang2_dicionaries")
*/
protected $lang2;
/**
* @ORM\Column(type="string")
*/
protected $name;
public function getName()
{
return $this->name;
}
/**
* @ORM\Column(type="string")
*/
protected $fullName;
public function setLang1($lang1)
{
$this->lang1 = $lang1;
return $this;
}
public function setLang2($lang2)
{
$this->lang2 = $lang2;
return $this;
}
}
?>

View File

@@ -0,0 +1,54 @@
<?php
namespace App\Model\Database\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Model\Database\Entity\Attributes\TId;
/**
* @ORM\Entity
*/
class Language extends AbstractEntity
{
use Tid;
public function __construct($shortname,$name)
{
$this->shortName = $shortname;
$this->name = $name;
}
/**
* @ORM\Column(type="string",length=2, unique=true, options={"fixed" = true})
*/
protected $shortName;
public function getShortName()
{
return $this->shortName;
}
/**
* @ORM\Column(type="string",length=32)
*/
protected $name;
public function getName()
{
return $this->name;
}
/*
* @ORM\OneToMany(targetEntity="Dictionary", mappedBy="language")
*/
protected $dictionaries1;
/*
* @ORM\OneToMany(targetEntity="Dictionary", mappedBy="language")
*/
protected $dictionaries2;
}
?>

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Model\Database\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Model\Database\Entity\Attributes\TId;
/**
* @ORM\Entity
*/
class Pronunciation extends AbstractEntity
{
use Tid;
public function __construct($type,$ipa,$filename=null)
{
$this->type = $type;
$this->ipa = $ipa;
$this->filename = $filename;
$this->terms = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* @ORM\ManyToMany(targetEntity="Term", mappedBy="pronunciations")
*/
private $terms;
/**
* @ORM\ManyToOne(targetEntity="PronunciationType", inversedBy="pronunciations")
*/
protected $type;
/**
* @ORM\Column(type="string",nullable=true)
*/
protected $ipa;
/**
* @ORM\Column(type="string", nullable=true)
*/
protected $filename;
}
?>

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Model\Database\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Model\Database\Entity\Attributes\Tid;
/**
* @ORM\Entity
*/
class PronunciationType extends AbstractEntity
{
use Tid;
public function __construct($name,$fullName)
{
$this->name = $name;
$this->fullName = $fullName;
}
/**
* @ORM\Column(type="string")
*/
protected $name;
/**
* @ORM\Column(type="string")
*/
protected $fullName;
}
?>

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Model\Database\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Model\Database\Entity\Attributes\TId;
/**
* @ORM\Entity
*/
class Suffix extends AbstractEntity
{
use Tid;
public function __construct($text)
{
$this->text = $text;
}
/**
* @ORM\Column(type="string")
*/
protected $text;
public function getText()
{
return $this->text;
}
}
?>

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Model\Database\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Model\Database\Entity\Attributes\TId;
/**
* @ORM\Entity
* @ORM\Table(indexes={@ORM\Index(columns={"string1"}, flags={"fulltext"}),
@ORM\Index(columns={"string2"}, flags={"fulltext"})})
*/
class Term extends AbstractEntity
{
use Tid;
public function __construct(Dictionary $dictionary,$string)
{
$this->dictionary = $dictionary;
$this->string1 = $string;
}
/**
* @ORM\ManyToOne(targetEntity="Dictionary", inversedBy="fullDict",cascade={"persist", "remove" })
*/
protected $dictionary;
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
}
?>

View File

@@ -0,0 +1,90 @@
<?php
namespace App\Model\Database\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Model\Database\Entity\Attributes\TId;
/**
* @ORM\Entity
*/
class TermFlag extends AbstractEntity
{
use Tid;
public function __construct($id,$class,$language,$code,$description)
{
$this->id = $id;
$this->class = $class;
$this->language = $language;
$this->code = $code;
$this->description = $description;
}
/**
* @ORM\ManyToOne(targetEntity="WordClass", inversedBy="flags")
*/
protected $class;
public function getClass()
{
return $this->class;
}
public function setClass($class)
{
$this->class = $class;
return $this;
}
/**
* @ORM\ManyToOne(targetEntity="Language", inversedBy="flags")
*/
protected $language;
public function getLanguage()
{
return $this->language;
}
public function setLanguage($language)
{
$this->language = $language;
return $this;
}
/**
* @ORM\Column(type="string")
*/
protected $code;
public function getCode()
{
return $this->code;
}
public function setCode($code)
{
$this->code = $code;
return $this;
}
/**
* @ORM\Column(type="string")
*/
protected $description;
public function getDescription()
{
return $this->description;
}
public function setDescription($description)
{
$this->description = $description;
return $this;
}
}
?>

View File

@@ -0,0 +1,77 @@
<?php
namespace App\Model\Database\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Model\Database\Entity\Attributes\TId;
/**
* @ORM\Entity
*/
class Translation extends AbstractEntity
{
use Tid;
public function __construct($dictionary,$lang1,$lang2,$lang_name1,$lang_name2,$direction)
{
$this->dictionary = $dictionary;
$this->lang1 = $lang1;
$this->lang2 = $lang2;
$this->lang_name1 = $lang_name1;
$this->lang_name2 = $lang_name2;
$this->direction = $direction;
}
/**
* @ORM\ManyToOne(targetEntity="Dictionary", inversedBy="translations")
*/
protected $dictionary;
public function getDictionary()
{
return $this->dictionary;
}
/**
* @ORM\ManyToOne(targetEntity="Language", inversedBy="translations1")
*/
protected $lang1;
/**
* @ORM\ManyToOne(targetEntity="Language", inversedBy="translations2")
*/
protected $lang2;
/**
* @ORM\Column(type="string")
*/
protected $lang_name1;
public function getLangName1()
{
return $this->lang_name1;
}
/**
* @ORM\Column(type="string")
*/
protected $lang_name2;
public function getLangName2()
{
return $this->lang_name2;
}
/**
* @ORM\Column(type="smallint")
*/
protected $direction;
public function getDirection()
{
return $this->direction;
}
}
?>

View File

@@ -0,0 +1,205 @@
<?php declare(strict_types = 1);
namespace App\Model\Database\Entity;
use App\Model\Database\Entity\Attributes\TCreatedAt;
use App\Model\Database\Entity\Attributes\TId;
use App\Model\Database\Entity\Attributes\TUpdatedAt;
use App\Model\Exception\Logic\InvalidArgumentException;
use App\Model\Security\Identity;
use App\Model\Utils\DateTime;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="App\Model\Database\Repository\UserRepository")
* @ORM\Table(name="`user`")
* @ORM\HasLifecycleCallbacks
*/
class User extends AbstractEntity
{
public const ROLE_ADMIN = 'admin';
public const ROLE_USER = 'user';
public const STATE_FRESH = 1;
public const STATE_ACTIVATED = 2;
public const STATE_BLOCKED = 3;
public const STATES = [self::STATE_FRESH, self::STATE_BLOCKED, self::STATE_ACTIVATED];
use TId;
use TCreatedAt;
use TUpdatedAt;
/**
* @var string
* @ORM\Column(type="string", length=255, nullable=FALSE, unique=false)
*/
private $name;
/**
* @var string
* @ORM\Column(type="string", length=255, nullable=FALSE, unique=false)
*/
private $surname;
/**
* @var string
* @ORM\Column(type="string", length=255, nullable=FALSE, unique=TRUE)
*/
private $email;
/**
* @var string
* @ORM\Column(type="string", length=255, nullable=FALSE, unique=TRUE)
*/
private $username;
/**
* @var int
* @ORM\Column(type="integer", length=10, nullable=FALSE)
*/
private $state;
/**
* @var string
* @ORM\Column(type="string", length=255, nullable=FALSE)
*/
private $password;
/**
* @var string
* @ORM\Column(type="string", length=255, nullable=FALSE)
*/
private $role;
/**
* @var DateTime|NULL
* @ORM\Column(type="datetime", nullable=TRUE)
*/
private $lastLoggedAt;
public function __construct(string $name, string $surname, string $email, string $username, string $passwordHash)
{
$this->name = $name;
$this->surname = $surname;
$this->email = $email;
$this->username = $username;
$this->password = $passwordHash;
$this->role = self::ROLE_USER;
$this->state = self::STATE_FRESH;
}
public function changeLoggedAt(): void
{
$this->lastLoggedAt = new DateTime();
}
public function getEmail(): string
{
return $this->email;
}
public function getUsername(): string
{
return $this->username;
}
public function changeUsername(string $username): void
{
$this->username = $username;
}
public function getLastLoggedAt(): ?DateTime
{
return $this->lastLoggedAt;
}
public function getRole(): string
{
return $this->role;
}
public function setRole(string $role): void
{
$this->role = $role;
}
public function getPasswordHash(): string
{
return $this->password;
}
public function changePasswordHash(string $password): void
{
$this->password = $password;
}
public function block(): void
{
$this->state = self::STATE_BLOCKED;
}
public function activate(): void
{
$this->state = self::STATE_ACTIVATED;
}
public function isActivated(): bool
{
return $this->state === self::STATE_ACTIVATED;
}
public function getName(): string
{
return $this->name;
}
public function getSurname(): string
{
return $this->surname;
}
public function getFullname(): string
{
return $this->name . ' ' . $this->surname;
}
public function rename(string $name, string $surname): void
{
$this->name = $name;
$this->surname = $surname;
}
public function getState(): int
{
return $this->state;
}
public function setState(int $state): void
{
if (!in_array($state, self::STATES)) {
throw new InvalidArgumentException(sprintf('Unsupported state %s', $state));
}
$this->state = $state;
}
public function getGravatar(): string
{
return 'https://www.gravatar.com/avatar/' . md5($this->email);
}
public function toIdentity(): Identity
{
return new Identity($this->getId(), [$this->role], [
'email' => $this->email,
'name' => $this->name,
'surname' => $this->surname,
'state' => $this->state,
'gravatar' => $this->getGravatar(),
]);
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace App\Model\Database\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Model\Database\Entity\Attributes\TId;
/**
* @ORM\Entity
*/
class WordClass extends AbstractEntity
{
use Tid;
public function __construct($id,$name)
{
$this->id = $id;
$this->name = $name;
}
public function setId($id)
{
$this->id = $id;
return $id;
}
public function getId()
{
return $this->id;
}
/**
* @ORM\Column(type="string")
*/
protected $name;
public function getName()
{
return $this->id;
}
public function setName($name)
{
$this->name = $name;
return $this;
}
}

View File

@@ -0,0 +1,28 @@
<?php declare(strict_types = 1);
namespace App\Model\Database;
use App\Model\Database\Repository\AbstractRepository;
use Doctrine\Persistence\ObjectRepository;
use Nettrine\ORM\EntityManagerDecorator;
class EntityManager extends EntityManagerDecorator
{
use TRepositories;
/**
* @param string $entityName
* @return AbstractRepository<T>|ObjectRepository<T>
* @internal
* @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
* @phpstan-template T of object
* @phpstan-param class-string<T> $entityName
* @phpstan-return ObjectRepository<T>
*/
public function getRepository($entityName): ObjectRepository
{
return parent::getRepository($entityName);
}
}

View File

@@ -0,0 +1,49 @@
<?php declare(strict_types = 1);
namespace App\Model\Database\Repository;
use Doctrine\ORM\EntityRepository;
/**
* @phpstan-template TEntityClass of object
* @phpstan-extends EntityRepository<TEntityClass>
*/
abstract class AbstractRepository extends EntityRepository
{
/**
* Fetches all records like $key => $value pairs
*
* @param mixed[] $criteria
* @param mixed[] $orderBy
* @return mixed[]
*/
public function findPairs(?string $key, string $value, array $criteria = [], array $orderBy = []): array
{
if ($key === null) {
$key = $this->getClassMetadata()->getSingleIdentifierFieldName();
}
$qb = $this->createQueryBuilder('e')
->select(['e.' . $value, 'e.' . $key])
->resetDQLPart('from')
->from($this->getEntityName(), 'e', 'e.' . $key);
foreach ($criteria as $k => $v) {
if (is_array($v)) {
$qb->andWhere(sprintf('e.%s IN(:%s)', $k, $k))->setParameter($k, array_values($v));
} else {
$qb->andWhere(sprintf('e.%s = :%s', $k, $k))->setParameter($k, $v);
}
}
foreach ($orderBy as $column => $order) {
$qb->addOrderBy($column, $order);
}
return array_map(function ($row) {
return reset($row);
}, $qb->getQuery()->getArrayResult());
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App;
use Kdyby;
use Nette;
class DictTypes extends Nette\Object
{
private $em;
private $dicttypes;
public function __construct(Kdyby\Doctrine\EntityManager $em)
{
$this->em = $em;
$this->dicttypes = $em->getRepository(DictType::class);
}
public function find($value)
{
return $this->dicttypes->find($value);
}
public function findBy($criteria = [],$orderBy = [])
{
return $this->dicttypes->findBy($criteria,$orderBy);
}
public function findPairs($criteria,$value,$orderBy,$key)
{
return $this->dicttypes->findPairs($criteria,$value,$orderBy,$key);
}
public function findAssoc($criteria, $key = NULL)
{
return $this->dicttypes->findAssoc($criteria,$key);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App;
use Kdyby;
use Nette;
class Dictionaries extends Nette\Object
{
private $em;
private $dictionaries;
public function __construct(Kdyby\Doctrine\EntityManager $em)
{
$this->em = $em;
$this->dictionaries = $em->getRepository(Dictionary::class);
}
public function findAll($criteria = [],$orderBy = [])
{
return $this->dictionaries->findBy($criteria,$orderBy);
}
public function findPairs($criteria,$value,$orderBy,$key)
{
return $this->dictionaries->findPairs($criteria,$value,$orderBy,$key);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App;
use Kdyby;
use Nette;
class Pronunciations extends Nette\Object
{
private $em;
private $pronunciations;
public function __construct(Kdyby\Doctrine\EntityManager $em)
{
$this->em = $em;
$this->pronunciations = $em->getRepository(Pronunciation::class);
}
public function find($value)
{
return $this->pronunciations->find($value);
}
public function findBy($criteria = [],$orderBy = [])
{
return $this->pronunciations->findBy($criteria,$orderBy);
}
public function findPairs($criteria,$value,$orderBy,$key)
{
return $this->pronunciations->findPairs($criteria,$value,$orderBy,$key);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App;
use Kdyby;
use Nette;
class TermFlags extends Nette\Object
{
private $em;
private $termFlags;
public function __construct(Kdyby\Doctrine\EntityManager $em)
{
$this->em = $em;
$this->termFlags = $em->getRepository(TermFlag::class);
}
public function find($value)
{
return $this->termFlags->find($value);
}
public function findBy($criteria = [],$orderBy = [])
{
return $this->termFlags->findBy($criteria,$orderBy);
}
public function findPairs($criteria,$value,$orderBy,$key)
{
return $this->termFlags->findPairs($criteria,$value,$orderBy,$key);
}
public function findAssoc($criteria,$key)
{
return $this->termFlags->findAssoc($criteria,$key);
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace App;
use Kdyby;
use Nette;
class Terms extends Nette\Object
{
private $em;
private $termsDao;
private $terms;
public function __construct(Kdyby\Doctrine\EntityManager $em)
{
$this->em = $em;
$this->terms = $em->getRepository(Term::class);
$this->termsDao = $em->getDao(Term::class);
}
public function findAll($criteria = [],$orderBy = [])
{
return $this->terms->findBy($criteria,$orderBy);
}
public function findBy($criteria = [],$orderBy = [])
{
return $this->terms->findBy($criteria,$orderBy);
}
public function find($id)
{
return $this->terms->find($id);
}
public function findPairs($criteria,$value,$orderBy,$key)
{
return $this->terms->findPairs($criteria,$value,$orderBy,$key);
}
public function fetch($query)
{
return $this->termsDao->fetch($query);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App;
use Kdyby;
use Nette;
class Translations extends Nette\Object
{
private $em;
private $translations;
public function __construct(Kdyby\Doctrine\EntityManager $em)
{
$this->em = $em;
$this->translations = $em->getRepository(Translation::class);
}
public function find($value)
{
return $this->translations->find($value);
}
public function findBy($criteria = [],$orderBy = [])
{
return $this->translations->findBy($criteria,$orderBy);
}
public function findPairs($criteria,$value,$orderBy,$key)
{
return $this->translations->findPairs($criteria,$value,$orderBy,$key);
}
}

View File

@@ -0,0 +1,22 @@
<?php declare(strict_types = 1);
namespace App\Model\Database\Repository;
use App\Model\Database\Entity\User;
/**
* @method User|NULL find($id, ?int $lockMode = NULL, ?int $lockVersion = NULL)
* @method User|NULL findOneBy(array $criteria, array $orderBy = NULL)
* @method User[] findAll()
* @method User[] findBy(array $criteria, array $orderBy = NULL, ?int $limit = NULL, ?int $offset = NULL)
* @extends AbstractRepository<User>
*/
class UserRepository extends AbstractRepository
{
public function findOneByEmail(string $email): ?User
{
return $this->findOneBy(['email' => $email]);
}
}

View File

@@ -0,0 +1,19 @@
<?php declare(strict_types = 1);
namespace App\Model\Database;
use App\Model\Database\Entity\User;
use App\Model\Database\Repository\UserRepository;
/**
* @mixin EntityManager
*/
trait TRepositories
{
public function getUserRepository(): UserRepository
{
return $this->getRepository(User::class);
}
}

View File

@@ -0,0 +1,10 @@
<?php declare(strict_types = 1);
namespace App\Model\Exception\Logic;
use App\Model\Exception\LogicException;
final class InvalidArgumentException extends LogicException
{
}

View File

@@ -0,0 +1,8 @@
<?php declare(strict_types = 1);
namespace App\Model\Exception;
class LogicException extends \LogicException
{
}

View File

@@ -0,0 +1,10 @@
<?php declare(strict_types = 1);
namespace App\Model\Exception\Runtime;
use Nette\Security\AuthenticationException as NetteAuthenticationException;
final class AuthenticationException extends NetteAuthenticationException
{
}

View File

@@ -0,0 +1,10 @@
<?php declare(strict_types = 1);
namespace App\Model\Exception\Runtime;
use App\Model\Exception\RuntimeException;
final class IOException extends RuntimeException
{
}

View File

@@ -0,0 +1,10 @@
<?php declare(strict_types = 1);
namespace App\Model\Exception\Runtime;
use App\Model\Exception\RuntimeException;
final class InvalidStateException extends RuntimeException
{
}

View File

@@ -0,0 +1,8 @@
<?php declare(strict_types = 1);
namespace App\Model\Exception;
class RuntimeException extends \RuntimeException
{
}

View File

@@ -0,0 +1,35 @@
<?php declare(strict_types = 1);
namespace App\Model\Latte;
use Latte\Engine;
final class FilterExecutor
{
/** @var Engine */
private $latte;
public function __construct(Engine $latte)
{
$this->latte = $latte;
}
/**
* @param mixed[] $args
* @return mixed
*/
public function __call(string $name, array $args)
{
return $this->latte->invokeFilter($name, $args);
}
/**
* @return mixed
*/
public function __get(string $name)
{
return $this->latte->invokeFilter($name, []);
}
}

View File

@@ -0,0 +1,30 @@
<?php declare(strict_types = 1);
namespace App\Model\Latte;
use Nette\Neon\Neon;
use Nette\StaticClass;
use Nette\Utils\Json;
final class Filters
{
use StaticClass;
/**
* @param mixed $value
*/
public static function neon($value): string
{
return Neon::encode($value, Neon::BLOCK);
}
/**
* @param mixed $value
*/
public static function json($value): string
{
return Json::encode($value);
}
}

View File

@@ -0,0 +1,16 @@
<?php declare(strict_types = 1);
namespace App\Model\Latte;
use Latte\Compiler;
use Latte\Macros\MacroSet;
final class Macros extends MacroSet
{
public static function register(Compiler $compiler): void
{
$compiler = new static($compiler);
}
}

View File

@@ -0,0 +1,51 @@
<?php declare(strict_types = 1);
namespace App\Model\Latte;
use App\Model\Security\SecurityUser;
use Nette\Application\UI\Control;
use Nette\Bridges\ApplicationLatte\LatteFactory;
use Nette\Bridges\ApplicationLatte\Template;
use Nette\Bridges\ApplicationLatte\TemplateFactory as NetteTemplateFactory;
use Nette\Caching\IStorage;
use Nette\Http\IRequest;
final class TemplateFactory extends NetteTemplateFactory
{
/** @var LatteFactory */
private $latteFactory;
/** @var SecurityUser */
private $user;
public function __construct(
LatteFactory $latteFactory,
IRequest $httpRequest,
SecurityUser $user,
IStorage $cacheStorage,
string $templateClass = null
)
{
parent::__construct($latteFactory, $httpRequest, $user, $cacheStorage, $templateClass);
$this->latteFactory = $latteFactory;
$this->user = $user;
}
public function createTemplate(Control $control = null, string $class = null): Template
{
/** @var Template $template */
$template = parent::createTemplate($control);
// Remove default $template->user for prevent misused
unset($template->user);
// Assign new variables
$template->_user = $this->user;
$template->_template = $template;
$template->_filters = new FilterExecutor($this->latteFactory->create());
return $template;
}
}

View File

@@ -0,0 +1,20 @@
<?php declare(strict_types = 1);
namespace App\Model\Latte;
use App\Model\Security\SecurityUser;
use App\Modules\Base\BasePresenter;
use App\UI\Control\BaseControl;
use Nette\Bridges\ApplicationLatte\Template;
/**
* @property-read SecurityUser $_user
* @property-read BasePresenter $presenter
* @property-read BaseControl $control
* @property-read string $baseUri
* @property-read string $basePath
* @property-read array $flashes
*/
final class TemplateProperty extends Template
{
}

View File

@@ -0,0 +1,55 @@
<?php declare(strict_types = 1);
namespace App\Model\Router;
use Nette\Application\Routers\Route;
use Nette\Application\Routers\RouteList;
final class RouterFactory
{
public function create(): RouteList
{
$router = new RouteList();
$this->buildMailing($router);
$this->buildPdf($router);
$this->buildAdmin($router);
$this->buildFront($router);
return $router;
}
protected function buildAdmin(RouteList $router): RouteList
{
$router[] = $list = new RouteList('Admin');
$list[] = new Route('admin/<presenter>/<action>[/<id>]', 'Home:default');
return $router;
}
protected function buildFront(RouteList $router): RouteList
{
$router[] = $list = new RouteList('Front');
$list[] = new Route('<presenter>/<action>[/<id>]', 'Home:default');
return $router;
}
protected function buildMailing(RouteList $router): RouteList
{
$router[] = $list = new RouteList('Mailing');
$list[] = new Route('mailing/<presenter>/<action>[/<id>]', 'Home:default');
return $router;
}
protected function buildPdf(RouteList $router): RouteList
{
$router[] = $list = new RouteList('Pdf');
$list[] = new Route('pdf/<presenter>/<action>[/<id>]', 'Home:default');
return $router;
}
}

View File

@@ -0,0 +1,57 @@
<?php declare(strict_types = 1);
namespace App\Model\Security\Authenticator;
use App\Model\Database\Entity\User;
use App\Model\Database\EntityManager;
use App\Model\Exception\Runtime\AuthenticationException;
use App\Model\Security\Passwords;
use Nette\Security\Authenticator;
use Nette\Security\IIdentity;
final class UserAuthenticator implements Authenticator
{
/** @var EntityManager */
private $em;
/** @var Passwords */
private $passwords;
public function __construct(EntityManager $em, Passwords $passwords)
{
$this->em = $em;
$this->passwords = $passwords;
}
/**
* @param string $username
* @param string $password
* @throws AuthenticationException
*/
public function authenticate(string $username, string $password): IIdentity
{
$user = $this->em->getUserRepository()->findOneBy(['email' => $username]);
if (!$user) {
throw new AuthenticationException('The username is incorrect.', self::IDENTITY_NOT_FOUND);
} elseif (!$user->isActivated()) {
throw new AuthenticationException('The user is not active.', self::INVALID_CREDENTIAL);
} elseif (!$this->passwords->verify($password, $user->getPasswordHash())) {
throw new AuthenticationException('The password is incorrect.', self::INVALID_CREDENTIAL);
}
$user->changeLoggedAt();
$this->em->flush();
return $this->createIdentity($user);
}
protected function createIdentity(User $user): IIdentity
{
$this->em->flush();
return $user->toIdentity();
}
}

View File

@@ -0,0 +1,47 @@
<?php declare(strict_types = 1);
namespace App\Model\Security\Authorizator;
use App\Model\Database\Entity\User;
use Nette\Security\Permission;
final class StaticAuthorizator extends Permission
{
/**
* Create ACL
*/
public function __construct()
{
$this->addRoles();
$this->addResources();
$this->addPermissions();
}
/**
* Setup roles
*/
protected function addRoles(): void
{
$this->addRole(User::ROLE_ADMIN);
}
/**
* Setup resources
*/
protected function addResources(): void
{
$this->addResource('Admin:Home');
}
/**
* Setup ACL
*/
protected function addPermissions(): void
{
$this->allow(User::ROLE_ADMIN, [
'Admin:Home',
]);
}
}

View File

@@ -0,0 +1,15 @@
<?php declare(strict_types = 1);
namespace App\Model\Security;
use Nette\Security\SimpleIdentity as NetteIdentity;
class Identity extends NetteIdentity
{
public function getFullname(): string
{
return sprintf('%s %s', $this->data['name'] ?? '', $this->data['surname'] ?? '');
}
}

View File

@@ -0,0 +1,15 @@
<?php declare(strict_types = 1);
namespace App\Model\Security;
use Nette\Security\Passwords as NettePasswords;
final class Passwords extends NettePasswords
{
public static function create(): Passwords
{
return new Passwords();
}
}

View File

@@ -0,0 +1,19 @@
<?php declare(strict_types = 1);
namespace App\Model\Security;
use App\Model\Database\Entity\User;
use Nette\Security\User as NetteUser;
/**
* @method Identity getIdentity()
*/
final class SecurityUser extends NetteUser
{
public function isAdmin(): bool
{
return $this->isInRole(User::ROLE_ADMIN);
}
}

View File

@@ -0,0 +1,10 @@
<?php declare(strict_types = 1);
namespace App\Model\Utils;
use Nette\Utils\Arrays as NetteArrays;
final class Arrays extends NetteArrays
{
}

View File

@@ -0,0 +1,13 @@
<?php declare(strict_types = 1);
namespace App\Model\Utils;
use Nette\Utils\DateTime as ContributteDateTime;
/**
* @method DateTime modifyClone(string $modify = '')
*/
final class DateTime extends ContributteDateTime
{
}

View File

@@ -0,0 +1,10 @@
<?php declare(strict_types = 1);
namespace App\Model\Utils;
use Contributte\Utils\FileSystem as ContributteFileSystem;
final class FileSystem extends ContributteFileSystem
{
}

10
app/model/Utils/Html.php Normal file
View File

@@ -0,0 +1,10 @@
<?php declare(strict_types = 1);
namespace App\Model\Utils;
use Nette\Utils\Html as NetteHtml;
final class Html extends NetteHtml
{
}

10
app/model/Utils/Image.php Normal file
View File

@@ -0,0 +1,10 @@
<?php declare(strict_types = 1);
namespace App\Model\Utils;
use Nette\Utils\Image as NetteImage;
class Image extends NetteImage
{
}

View File

@@ -0,0 +1,10 @@
<?php declare(strict_types = 1);
namespace App\Model\Utils;
use Contributte\Utils\Strings as ContributteStrings;
final class Strings extends ContributteStrings
{
}

View File

@@ -0,0 +1,10 @@
<?php declare(strict_types = 1);
namespace App\Model\Utils;
use Contributte\Utils\Validators as ContributteValidators;
final class Validators extends ContributteValidators
{
}

View File

@@ -0,0 +1,26 @@
<?php declare(strict_types = 1);
namespace App\Modules\Admin;
use App\Model\App;
use App\Modules\Base\SecuredPresenter;
use Nette\Application\UI\ComponentReflection;
abstract class BaseAdminPresenter extends SecuredPresenter
{
/**
* @param ComponentReflection|mixed $element
* @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
*/
public function checkRequirements($element): void
{
parent::checkRequirements($element);
if (!$this->user->isAllowed('Admin:Home')) {
$this->flashError('You cannot access this with user role');
$this->redirect(App::DESTINATION_FRONT_HOMEPAGE);
}
}
}

View File

@@ -0,0 +1,31 @@
<?php declare(strict_types = 1);
namespace App\Modules\Admin\Home;
use App\Domain\Order\Event\OrderCreated;
use App\Modules\Admin\BaseAdminPresenter;
use Nette\Application\UI\Form;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
final class HomePresenter extends BaseAdminPresenter
{
/** @var EventDispatcherInterface @inject */
public $dispatcher;
protected function createComponentOrderForm(): Form
{
$form = new Form();
$form->addText('order', 'Order name')
->setRequired(true);
$form->addSubmit('send', 'OK');
$form->onSuccess[] = function (Form $form): void {
$this->dispatcher->dispatch(new OrderCreated($form->values->order), OrderCreated::NAME);
};
return $form;
}
}

View File

@@ -0,0 +1,8 @@
{block #content}
<h1>SECRET PAGE!</h1>
<a n:href="Sign:out" class="btn btn-danger">Logout</a>
<hr>
<h2>Orders</h2>
{control orderForm}

View File

@@ -0,0 +1,75 @@
<?php declare(strict_types = 1);
namespace App\Modules\Admin\Sign;
use App\Model\App;
use App\Model\Exception\Runtime\AuthenticationException;
use App\Modules\Admin\BaseAdminPresenter;
use App\UI\Form\BaseForm;
use App\UI\Form\FormFactory;
use Nette\Application\UI\ComponentReflection;
final class SignPresenter extends BaseAdminPresenter
{
/** @var string @persistent */
public $backlink;
/** @var FormFactory @inject */
public $formFactory;
/**
* @param ComponentReflection|mixed $element
* @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
*/
public function checkRequirements($element): void
{
}
public function actionIn(): void
{
if ($this->user->isLoggedIn()) {
$this->redirect(App::DESTINATION_AFTER_SIGN_IN);
}
}
public function actionOut(): void
{
if ($this->user->isLoggedIn()) {
$this->user->logout();
$this->flashSuccess('_front.sign.out.success');
}
$this->redirect(App::DESTINATION_AFTER_SIGN_OUT);
}
protected function createComponentLoginForm(): BaseForm
{
$form = $this->formFactory->forBackend();
$form->addEmail('email')
->setRequired(true);
$form->addPassword('password')
->setRequired(true);
$form->addCheckbox('remember')
->setDefaultValue(true);
$form->addSubmit('submit');
$form->onSuccess[] = [$this, 'processLoginForm'];
return $form;
}
public function processLoginForm(BaseForm $form): void
{
try {
$this->user->setExpiration($form->values->remember ? '14 days' : '20 minutes');
$this->user->login($form->values->email, $form->values->password);
} catch (AuthenticationException $e) {
$form->addError('Invalid username or password');
return;
}
$this->redirect(App::DESTINATION_AFTER_SIGN_IN);
}
}

View File

@@ -0,0 +1,29 @@
{block #content}
<form n:name="loginForm" class="form-signin">
<div class="text-center mb-4">
<img class="mb-4" src="https://avatars0.githubusercontent.com/u/99965?s=200&v=4" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal">Webapp Skeleton Admin</h1>
</div>
<div n:foreach="$form->errors as $error" class="alert alert-danger" role="alert">
{$error}
</div>
<div class="form-label-group">
<input type="email" n:name="email" class="form-control" placeholder="Email address" required autofocus>
<label n:name="email">Email address</label>
</div>
<div class="form-label-group">
<input type="password" n:name="password" class="form-control" placeholder="Password" required>
<label n:name="password">Password</label>
</div>
<div class="checkbox mb-3">
<label>
<input n:name="remember" type="checkbox"> Remember me
</label>
</div>
<button n:name="submit" class="btn btn-lg btn-primary btn-block">Sign in</button>
<p class="mt-5 mb-3 text-muted text-center">&copy; {=date('Y')}</p>
</form>

View File

@@ -0,0 +1,7 @@
{layout '../../Base/templates/@layout.latte'}
{block #head}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.2.1/css/bootstrap.min.css" defer />
<link rel="stylesheet" href="{$basePath}/assets/admin.css" defer />
<script src="{$basePath}/assets/admin.js" defer></script>
{/block}

View File

@@ -0,0 +1,44 @@
<?php declare(strict_types = 1);
namespace App\Modules\Base;
use App\Model\Exception\Runtime\InvalidStateException;
use Nette\Application\BadRequestException;
use Nette\Application\Request;
use Nette\Application\UI\ComponentReflection;
abstract class BaseError4xxPresenter extends SecuredPresenter
{
/**
* Common presenter method
*/
public function startup(): void
{
parent::startup();
if ($this->getRequest() !== null && $this->getRequest()->isMethod(Request::FORWARD)) {
return;
}
$this->error();
}
public function renderDefault(BadRequestException $exception): void
{
$rf1 = new ComponentReflection(static::class);
$fileName = $rf1->getFileName();
// Validate if class is not in PHP core
if ($fileName === false) {
throw new InvalidStateException('Class is defined in the PHP core or in a PHP extension');
}
$dir = dirname($fileName);
// Load template 403.latte or 404.latte or ... 4xx.latte
$file = $dir . '/templates/' . $exception->getCode() . '.latte';
$this->template->setFile(is_file($file) ? $file : $dir . '/templates/4xx.latte');
}
}

View File

@@ -0,0 +1,57 @@
<?php declare(strict_types = 1);
namespace App\Modules\Base;
use Nette\Application\BadRequestException;
use Nette\Application\Helpers;
use Nette\Application\IResponse as AppResponse;
use Nette\Application\Request;
use Nette\Application\Responses\CallbackResponse;
use Nette\Application\Responses\ForwardResponse;
use Nette\Http\IRequest;
use Nette\Http\IResponse;
use Psr\Log\LogLevel;
use Throwable;
use Tracy\Debugger;
use Tracy\ILogger;
abstract class BaseErrorPresenter extends SecuredPresenter
{
/**
* @return ForwardResponse|CallbackResponse
*/
public function run(Request $request): AppResponse
{
$e = $request->getParameter('exception');
if ($e instanceof Throwable) {
$code = $e->getCode();
$level = ($code >= 400 && $code <= 499) ? LogLevel::WARNING : LogLevel::ERROR;
Debugger::log(sprintf(
'Code %s: %s in %s:%s',
$code,
$e->getMessage(),
$e->getFile(),
$e->getLine()
), $level);
Debugger::log($e, ILogger::EXCEPTION);
}
if ($e instanceof BadRequestException) {
[$module, , $sep] = Helpers::splitName($request->getPresenterName());
return new ForwardResponse($request->setPresenterName($module . $sep . 'Error4xx'));
}
return new CallbackResponse(function (IRequest $httpRequest, IResponse $httpResponse): void {
$header = $httpResponse->getHeader('Content-Type');
if ($header !== null && preg_match('#^text/html(?:;|$)#', $header)) {
require __DIR__ . '/templates/500.phtml';
}
});
}
}

View File

@@ -0,0 +1,23 @@
<?php declare(strict_types = 1);
namespace App\Modules\Base;
use App\Model\Latte\TemplateProperty;
use App\Model\Security\SecurityUser;
use App\UI\Control\TFlashMessage;
use App\UI\Control\TModuleUtils;
use Contributte\Application\UI\Presenter\StructuredTemplates;
use Nette\Application\UI\Presenter;
/**
* @property-read TemplateProperty $template
* @property-read SecurityUser $user
*/
abstract class BasePresenter extends Presenter
{
use StructuredTemplates;
use TFlashMessage;
use TModuleUtils;
}

View File

@@ -0,0 +1,30 @@
<?php declare(strict_types = 1);
namespace App\Modules\Base;
use App\Model\App;
use Nette\Application\UI\ComponentReflection;
use Nette\Security\IUserStorage;
abstract class SecuredPresenter extends BasePresenter
{
/**
* @param ComponentReflection|mixed $element
* @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
*/
public function checkRequirements($element): void
{
if (!$this->user->isLoggedIn()) {
if ($this->user->getLogoutReason() === IUserStorage::INACTIVITY) {
$this->flashInfo('You have been logged out for inactivity');
}
$this->redirect(
App::DESTINATION_SIGN_IN,
['backlink' => $this->storeRequest()]
);
}
}
}

View File

@@ -0,0 +1,8 @@
<?php declare(strict_types = 1);
namespace App\Modules\Base;
abstract class UnsecuredPresenter extends BasePresenter
{
}

View File

@@ -0,0 +1,27 @@
<!DOCTYPE html><!-- "' --></textarea></script></style></pre></xmp></a></audio></button></canvas></datalist></details></dialog></iframe></listing></meter></noembed></noframes></noscript></optgroup></option></progress></rp></select></table></template></title></video>
<meta charset="utf-8">
<meta name="robots" content="noindex">
<title>Server Error</title>
<style>
#nette-error { all: initial; position: absolute; top: 0; left: 0; right: 0; height: 70vh; min-height: 400px; display: flex; align-items: center; justify-content: center; z-index: 1000 }
#nette-error div { all: initial; max-width: 550px; background: white; color: #333; display: block }
#nette-error h1 { all: initial; font: bold 50px/1.1 sans-serif; display: block; margin: 40px }
#nette-error p { all: initial; font: 20px/1.4 sans-serif; margin: 40px; display: block }
#nette-error small { color: gray }
</style>
<div id=nette-error>
<div>
<h1>Server Error</h1>
<p>We're sorry! The server encountered an internal error and
was unable to complete your request. Please try again later.</p>
<p><small>error 500</small></p>
</div>
</div>
<script>
document.body.insertBefore(document.getElementById('nette-error'), document.body.firstChild);
</script>

View File

@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="shortcut icon" href="{$basePath}/favicon.ico">
<!-- Seo -->
<title>{block #title|stripHtml|trim}Webapp Skeleton{/} | Contributte</title>
<!-- Meta -->
<meta name="description" n:ifset="#description" content="{include #description}">
<meta name="keywords" n:ifset="#keywords" content="{include #keywords}">
<meta name="robots" content="index,follow">
<meta name="googlebot" content="snippet,archive">
<meta name="author" content="f3l1x">
{block #head}{/}
</head>
<body>
{block #main}
<div class="container">
{include #content}
</div>
{/}
</body>
</html>

View File

@@ -0,0 +1,10 @@
<?php declare(strict_types = 1);
namespace App\Modules\Front;
use App\Modules\Base\UnsecuredPresenter;
abstract class BaseFrontPresenter extends UnsecuredPresenter
{
}

View File

@@ -0,0 +1,10 @@
<?php declare(strict_types = 1);
namespace App\Modules\Front\Error;
use App\Modules\Base\BaseErrorPresenter;
final class ErrorPresenter extends BaseErrorPresenter
{
}

View File

@@ -0,0 +1,10 @@
<?php declare(strict_types = 1);
namespace App\Modules\Front\Error4xx;
use App\Modules\Base\BaseError4xxPresenter;
final class Error4xxPresenter extends BaseError4xxPresenter
{
}

View File

@@ -0,0 +1,7 @@
{block #content}
<h1 n:block=title>Access Denied</h1>
<p>You do not have permission to view this page. Please try contact the web
site administrator if you believe you should be able to view this page.</p>
<p><small>error 403</small></p>

View File

@@ -0,0 +1,8 @@
{block #content}
<h1 n:block=title>Page Not Found</h1>
<p>The page you requested could not be found. It is possible that the address is
incorrect, or that the page no longer exists. Please use a search engine to find
what you are looking for.</p>
<p><small>error 404</small></p>

View File

@@ -0,0 +1,6 @@
{block #content}
<h1 n:block=title>Method Not Allowed</h1>
<p>The requested method is not allowed for the URL.</p>
<p><small>error 405</small></p>

View File

@@ -0,0 +1,6 @@
{block #content}
<h1 n:block=title>Page Not Found</h1>
<p>The page you requested has been taken off the site. We apologize for the inconvenience.</p>
<p><small>error 410</small></p>

View File

@@ -0,0 +1,4 @@
{block #content}
<h1 n:block=title>Oops...</h1>
<p>Your browser sent a request that this server could not understand or process.</p>

View File

@@ -0,0 +1,10 @@
<?php declare(strict_types = 1);
namespace App\Modules\Front\Home;
use App\Modules\Front\BaseFrontPresenter;
final class HomePresenter extends BaseFrontPresenter
{
}

View File

@@ -0,0 +1,2 @@
{block #content}
Welcome

View File

@@ -0,0 +1,38 @@
{layout '../../Base/templates/@layout.latte'}
{block #head}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.min.css" defer/>
<link rel="stylesheet" href="{$basePath}/assets/front.css" defer/>
<script src="{$basePath}/assets/front.js" defer></script>
{/}
{block #main}
<div class="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column">
<header class="masthead mb-auto">
<div class="inner">
<h3 class="masthead-brand">Webapp</h3>
<nav class="nav nav-masthead justify-content-center">
<a n:class="$presenter->isLinkCurrent(':Front:Home:') ? active, nav-link" n:href=":Front:Home:">Home</a>
<a n:class="$presenter->isLinkCurrent(':Admin:Home:') ? active, nav-link" n:href=":Admin:Home:">Admin</a>
<a n:class="$presenter->isLinkCurrent(':Pdf:Home:') ? active, nav-link" n:href=":Pdf:Home:">PDF example</a>
</nav>
</div>
</header>
<main role="main" class="inner cover">
<h1 class="cover-heading">Webapp skeleton.</h1>
<p class="lead">This is example project based on <a href="https://nette.org">Nette Framework</a></p>
<p class="lead">And also many <a href="https://contributte.org">Contributte + Nettrine</a> packages.</p>
<div class="lead">
<a href="https://github.com/contributte/webapp-skeleton" class="btn btn-lg btn-secondary">Learn more in docs</a>
</div>
</main>
<footer class="mastfoot mt-auto">
<div class="inner">
<p>Cover template for <a href="https://getbootstrap.com/">Bootstrap</a>, by
<a href="https://twitter.com/mdo">@mdo</a>.</p>
</div>
</footer>
</div>
{/}

View File

@@ -0,0 +1,35 @@
<?php declare(strict_types = 1);
namespace App\Modules\Mailing\Home;
use Contributte\Mailing\IMailBuilderFactory;
use Nette\Application\UI\Presenter;
class HomePresenter extends Presenter
{
/** @var IMailBuilderFactory */
private $mailBuilderFactory;
public function __construct(IMailBuilderFactory $mailBuilderFactory)
{
parent::__construct();
$this->mailBuilderFactory = $mailBuilderFactory;
}
public function actionDefault(): void
{
$mail = $this->mailBuilderFactory->create();
$mail->setSubject('Example');
$mail->addTo('foo@example.com');
$mail->setTemplateFile(__DIR__ . '/templates/Emails/email.latte');
$mail->setParameters([
'title' => 'Title',
'content' => 'Lorem ipsum dolor sit amet',
]);
$mail->send();
}
}

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>{ifset title}{include title|stripHtml} | {/ifset}Nette Web</title>
</head>
<body>
<div n:foreach="$flashes as $flash" n:class="flash, $flash->type">{$flash->message}</div>
{include content}
{block scripts}
<script src="https://nette.github.io/resources/js/netteForms.min.js"></script>
{/block}
</body>
</html>

View File

@@ -0,0 +1,9 @@
{layout $_config->layout}
{block #header}
{$title}
{/block}
{block #content}
{$content}
{/block}

View File

@@ -0,0 +1,38 @@
{* This is the welcome page, you can delete it *}
{block content}
<div id="banner">
<h1 n:block=title>Congratulations!</h1>
</div>
<div id="content">
<h2>You have successfully created your <a href="https://nette.org">Nette</a> Web project.</h2>
<p><img src="data:image/png;base64,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" alt="">
If you are exploring Nette for the first time, you should read the
<a href="https://doc.nette.org/quickstart">Quick Start</a>, <a href="https://doc.nette.org">documentation</a>,
<a href="https://pla.nette.org">tutorials</a> and <a href="https://forum.nette.org">forum</a>.</p>
<h2>We hope you enjoy Nette!</h2>
</div>
<style>
html { font: normal 18px/1.3 Georgia, "New York CE", utopia, serif; color: #666; -webkit-text-stroke: 1px rgba(0,0,0,0); overflow-y: scroll; }
body { background: #3484d2; color: #333; margin: 2em auto; padding: 0 .5em; max-width: 600px; min-width: 320px; }
a { color: #006aeb; padding: 3px 1px; }
a:hover, a:active, a:focus { background-color: #006aeb; text-decoration: none; color: white; }
#banner { border-radius: 12px 12px 0 0; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAB5CAMAAADPursXAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAGBQTFRFD1CRDkqFDTlmDkF1D06NDT1tDTNZDk2KEFWaDTZgDkiCDTtpDT5wDkZ/DTBVEFacEFOWD1KUDTRcDTFWDkV9DkR7DkN4DkByDTVeDC9TDThjDTxrDkeADkuIDTRbDC9SbsUaggAAAEdJREFUeNqkwYURgAAQA7DH3d3335LSKyxAYpf9vWCpnYbf01qcOdFVXc14w4BznNTjkQfsscAdU3b4wIh9fDVYc4zV8xZgAAYaCMI6vPgLAAAAAElFTkSuQmCC); }
h1 { font: inherit; color: white; font-size: 50px; line-height: 121px; margin: 0; padding-left: 4%; background: url(https://files.nette.org/images/logo-nette@2.png) no-repeat 95%; background-size: 130px auto; text-shadow: 1px 1px 0 rgba(0, 0, 0, .9); }
@media (max-width: 600px) {
h1 { background: none; font-size: 40px; }
}
#content { background: white; border: 1px solid #eff4f7; border-radius: 0 0 12px 12px; padding: 10px 4%; overflow: hidden; }
h2 { font: inherit; padding: 1.2em 0; margin: 0; }
img { border: none; float: right; margin: 0 0 1em 3em; }
</style>

View File

@@ -0,0 +1,46 @@
<?php declare(strict_types = 1);
namespace App\Modules\Pdf\Home;
use App\Modules\Base\BasePresenter;
use Contributte\PdfResponse\PdfResponse;
use Nette\Bridges\ApplicationLatte\Template;
class HomePresenter extends BasePresenter
{
/** @inject */
public PdfResponse $pdfResponse;
private function createPdf(): PdfResponse
{
/** @var Template $template */
$template = $this->createTemplate();
$template->setFile(__DIR__ . '/../../../resources/pdf/example.latte');
$template->title = 'Contributte PDF example';
$this->pdfResponse->setTemplate($template->renderToString());
$this->pdfResponse->documentTitle = 'Contributte PDF example'; // creates filename 2012-06-30-my-super-title.pdf
$this->pdfResponse->pageFormat = 'A4-L'; // wide format
$this->pdfResponse->getMPDF()->SetFooter('|Contributte PDF|'); // footer
return $this->pdfResponse;
}
public function actionViewPdf(): void
{
$pdf = $this->createPdf();
$pdf->setSaveMode(PdfResponse::INLINE);
$this->sendResponse($pdf);
}
public function actionDownloadPdf(): void
{
$pdf = $this->createPdf();
$pdf->setSaveMode(PdfResponse::DOWNLOAD);
$this->sendResponse($pdf);
}
}

View File

@@ -0,0 +1,34 @@
{block #content}
<div id="banner">
<h1 n:block=title>Contributte PDF example</h1>
</div>
<div id="content">
<h2>Welcome in example of using contributte/pdf.</h2>
<a n:href="Home:viewPdf" target="_blank">View generated PDF</a>
<br><br>
<a n:href="Home:downloadPdf" target="_blank">Download generated PDF</a>
<p></p>
</div>
<style>
html { font: normal 18px/1.3 Georgia, "New York CE", utopia, serif; color: #666; -webkit-text-stroke: 1px rgba(0,0,0,0); overflow-y: scroll; }
body { background: #3484d2; color: #333; margin: 2em auto; padding: 0 .5em; max-width: 800px; min-width: 320px; }
a { color: #006aeb; padding: 3px 1px; }
a:hover, a:active, a:focus { background-color: #006aeb; text-decoration: none; color: white; }
#banner { border-radius: 12px 12px 0 0; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAB5CAMAAADPursXAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAGBQTFRFD1CRDkqFDTlmDkF1D06NDT1tDTNZDk2KEFWaDTZgDkiCDTtpDT5wDkZ/DTBVEFacEFOWD1KUDTRcDTFWDkV9DkR7DkN4DkByDTVeDC9TDThjDTxrDkeADkuIDTRbDC9SbsUaggAAAEdJREFUeNqkwYURgAAQA7DH3d3335LSKyxAYpf9vWCpnYbf01qcOdFVXc14w4BznNTjkQfsscAdU3b4wIh9fDVYc4zV8xZgAAYaCMI6vPgLAAAAAElFTkSuQmCC); }
h1 { font: inherit; color: white; font-size: 50px; line-height: 121px; margin: 0; padding-left: 4%; background: url(https://files.nette.org/images/logo-nette@2.png) no-repeat 95%; background-size: 130px auto; text-shadow: 1px 1px 0 rgba(0, 0, 0, .9); }
@media (max-width: 600px) {
h1 { background: none; font-size: 40px; }
}
#content { background: white; border: 1px solid #eff4f7; border-radius: 0 0 12px 12px; padding: 10px 4%; overflow: hidden; }
h2 { font: inherit; padding: 1.2em 0; margin: 0; }
img { border: none; float: right; margin: 0 0 1em 3em; }
</style>

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>{ifset title}{include title|stripHtml} | {/ifset}Nette Web</title>
</head>
<body>
<div n:foreach="$flashes as $flash" n:class="flash, $flash->type">{$flash->message}</div>
{include content}
{block scripts}
<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script>
{/block}
</body>
</html>

View File

@@ -0,0 +1,9 @@
{layout $_defaults->layout}
{block #title}
Webapp Skeleton
{/block}
{block #copyright}
Webapp Skeleton
{/block}

View File

@@ -0,0 +1,10 @@
{layout $_config->layout}
{block #header}
Automatické přihlášení
{/block}
{block #content}
<h2>Přihlášení</h2>
<a href="{link Admin:SignIn:, do => auth, token => $token}" target="_blank">Přihlásit se</a>
{/block}

View File

@@ -0,0 +1,171 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>{$title}</title>
</head>
<body>
<img src="https://heatbadger.now.sh/github/readme/contributte/pdf/">
<h2>Lorem ipsum</h2>
<p>
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Neque porro quisquam est, qui dolorem ipsum quia
dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et
dolore magnam aliquam quaerat voluptatem. Sed elit dui, pellentesque a, faucibus vel, interdum nec, diam.
Etiam posuere lacus quis dolor. Aliquam ante. Sed vel lectus. Donec odio tempus molestie, porttitor ut,
iaculis quis, sem. Aliquam erat volutpat. Fusce wisi. Cum sociis natoque penatibus et magnis dis parturient
montes, nascetur ridiculus mus. Nullam eget nisl. Etiam bibendum elit eget erat.
</p>
<h1 id="readme">ReadMe</h1>
<h2 id="usage">Usage</h2>
<h3 id="how-to-prepare-pdf-from-template">How to prepare PDF from template</h3>
<pre><code class="lang-php"><span class="hljs-comment">// in a Presenter</span>
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span
class="hljs-title">actionPdf</span><span class="hljs-params">()</span>
</span>{
$template = <span class="hljs-keyword">$this</span>-&gt;createTemplate();
$template-&gt;setFile(<span class="hljs-keyword">__DIR__</span> . <span class="hljs-string">"/path/to/template.latte"</span>);
$template-&gt;someValue = <span class="hljs-number">123</span>;
<span class="hljs-comment">// Tip: In template to make a new page use &lt;pagebreak&gt;</span>
$pdf = <span class="hljs-keyword">new</span> \Contributte\PdfResponse\PdfResponse($template);
<span class="hljs-comment">// optional</span>
$pdf-&gt;documentTitle = date(<span class="hljs-string">"Y-m-d"</span>) . <span class="hljs-string">" My super title"</span>; <span
class="hljs-comment">// creates filename 2012-06-30-my-super-title.pdf</span>
$pdf-&gt;pageFormat = <span class="hljs-string">"A4-L"</span>; <span class="hljs-comment">// wide format</span>
$pdf-&gt;getMPDF()-&gt;setFooter(<span class="hljs-string">"|© www.mysite.com|"</span>); <span class="hljs-comment">// footer</span>
<span class="hljs-comment">// do something with $pdf</span>
<span class="hljs-keyword">$this</span>-&gt;sendResponse($pdf);
}
</code></pre>
<h3 id="save-file-to-server">Save file to server</h3>
<pre><code class="lang-php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span
class="hljs-keyword">function</span> <span class="hljs-title">actionPdf</span><span
class="hljs-params">()</span>
</span>{
$template = <span class="hljs-keyword">$this</span>-&gt;createTemplate();
$template-&gt;setFile(<span class="hljs-keyword">__DIR__</span> . <span class="hljs-string">"/path/to/template.latte"</span>);
$pdf = <span class="hljs-keyword">new</span> \Contributte\PdfResponse\PdfResponse($template);
$pdf-&gt;save(<span class="hljs-keyword">__DIR__</span> . <span
class="hljs-string">"/path/to/directory"</span>); <span class="hljs-comment">// as a filename $this-&gt;documentTitle will be used</span>
$pdf-&gt;save(<span class="hljs-keyword">__DIR__</span> . <span
class="hljs-string">"/path/to/directory"</span>, <span class="hljs-string">"filename"</span>); <span
class="hljs-comment">// OR use a custom name</span>
}
</code></pre>
<h3 id="attach-file-to-an-email">Attach file to an email</h3>
<pre><code class="lang-php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span
class="hljs-keyword">function</span> <span class="hljs-title">actionPdf</span><span
class="hljs-params">()</span>
</span>{
$template = <span class="hljs-keyword">$this</span>-&gt;createTemplate();
$template-&gt;setFile(<span class="hljs-keyword">__DIR__</span> . <span class="hljs-string">"/path/to/template.latte"</span>);
$pdf = <span class="hljs-keyword">new</span> \Contributte\PdfResponse\PdfResponse($template);
$savedFile = $pdf-&gt;save(<span class="hljs-keyword">__DIR__</span> . <span class="hljs-string">"/path/to/directory"</span>);
$mail = <span class="hljs-keyword">new</span> Nette\Mail\Message;
$mail-&gt;addTo(<span class="hljs-string">"john@doe.com"</span>);
$mail-&gt;addAttachment($savedFile);
$mailer = <span class="hljs-keyword">new</span> SendmailMailer();
$mailer-&gt;send($mail);
}
</code></pre>
<h3 id="force-file-to-download">Force file to download</h3>
<pre><code class="lang-php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span
class="hljs-keyword">function</span> <span class="hljs-title">actionPdf</span><span
class="hljs-params">()</span>
</span>{
$template = <span class="hljs-keyword">$this</span>-&gt;createTemplate();
$template-&gt;setFile(<span class="hljs-keyword">__DIR__</span> . <span class="hljs-string">"/path/to/template.latte"</span>);
$pdf = <span class="hljs-keyword">new</span> \Contributte\PdfResponse\PdfResponse($template);
$pdf-&gt;setSaveMode(PdfResponse::DOWNLOAD); <span class="hljs-comment">//default behavior</span>
<span class="hljs-keyword">$this</span>-&gt;sendResponse($pdf);
}
</code></pre>
<h3 id="force-file-to-display-in-a-browser">Force file to display in a browser</h3>
<pre><code class="lang-php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span
class="hljs-keyword">function</span> <span class="hljs-title">actionPdf</span><span
class="hljs-params">()</span>
</span>{
$template = <span class="hljs-keyword">$this</span>-&gt;createTemplate();
$template-&gt;setFile(<span class="hljs-keyword">__DIR__</span> . <span class="hljs-string">"/path/to/template.latte"</span>);
$pdf = <span class="hljs-keyword">new</span> \Contributte\PdfResponse\PdfResponse($template);
$pdf-&gt;setSaveMode(PdfResponse::INLINE);
<span class="hljs-keyword">$this</span>-&gt;sendResponse($pdf);
}
</code></pre>
<h3 id="set-a-pdf-background-easily">Set a pdf background easily</h3>
<pre><code class="lang-php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span
class="hljs-keyword">function</span> <span class="hljs-title">actionPdf</span><span
class="hljs-params">()</span>
</span>{
$pdf = <span class="hljs-keyword">new</span> \Contributte\PdfResponse\PdfResponse(<span
class="hljs-string">''</span>);
$pdf-&gt;setBackgroundTemplate(<span class="hljs-keyword">__DIR__</span> . <span class="hljs-string">"/path/to/an/existing/file.pdf"</span>);
<span class="hljs-comment">// to write into an existing document use the following statements</span>
$mpdf = $pdf-&gt;getMPDF();
$mpdf-&gt;WriteFixedPosHTML(<span class="hljs-string">'hello world'</span>, <span
class="hljs-number">1</span>, <span class="hljs-number">10</span>, <span class="hljs-number">10</span>, <span
class="hljs-number">10</span>);
<span class="hljs-comment">// to write to another page</span>
$mpdf-&gt;AddPage();
<span class="hljs-comment">// to move to exact page, use</span>
$mpdf-&gt;page = <span class="hljs-number">3</span>; <span class="hljs-comment">// = move to 3rd page</span>
<span class="hljs-keyword">$this</span>-&gt;sendResponse($pdf);
}
</code></pre>
<h3 id="create-pdf-with-latte-only">Create pdf with latte only</h3>
<pre><code class="lang-php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span
class="hljs-keyword">function</span> <span class="hljs-title">actionPdf</span><span
class="hljs-params">()</span>
</span>{
$latte = <span class="hljs-keyword">new</span> Latte\Engine;
$latte-&gt;setTempDirectory(<span class="hljs-string">'/path/to/cache'</span>);
$latte-&gt;addFilter(<span class="hljs-string">'money'</span>, <span class="hljs-function"><span
class="hljs-keyword">function</span><span class="hljs-params">($val)</span> </span>{ <span
class="hljs-keyword">return</span> ...; }); <span
class="hljs-comment">// formerly registerHelper()</span>
$latte-&gt;onCompile[] = <span class="hljs-function"><span class="hljs-keyword">function</span><span
class="hljs-params">($latte)</span> </span>{
$latte-&gt;addMacro(...); <span class="hljs-comment">// when you want add some own macros, see http://goo.gl/d5A1u2</span>
};
$template = $latte-&gt;renderToString(<span class="hljs-keyword">__DIR__</span> . <span class="hljs-string">"/path/to/template.latte"</span>);
$pdf = <span class="hljs-keyword">new</span> \Contributte\PdfResponse\PdfResponse($template);
<span class="hljs-keyword">$this</span>-&gt;sendResponse($pdf);
}
</code></pre>
<h3 id="configuration-of-custom-temp-dir-for-mpdf-in-pdfresponse">
Configuration of custom temp dir for mPDF in PdfResponse
</h3>
<pre><code class="lang-yml">services:
-<span class="ruby"></span> factory: Contributte\PdfResponse\PdfResponse
setup:
-<span class="ruby"> $mpdfConfig([<span class="hljs-symbol">tempDir:</span> %tempDir<span
class="hljs-string">%/mpdf])</span></span>
</code></pre>
<h2 id="see-also">See also</h2>
<ul>
<li><a href="https://forum.nette.org/cs/3726-addon-pdfresponse-pdfresponse">Nette forum</a> (czech)</li>
<li><a href="https://componette.com/joseki/pdfresponse/">Componette</a></li>
</ul>
<hr>
<p>Thanks for testing, reporting and contributing.</p>
</body>
</html>

View File

@@ -0,0 +1,4 @@
{
"code": 500,
"error": "Internal server error"
}

View File

@@ -0,0 +1,250 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Server Error</title>
<style>
/**/
:root {
--main-color: #eaeaea;
--stroke-color: black;
}
/**/
body {
background: var(--main-color);
}
h1 {
margin: 100px auto 0 auto;
color: var(--stroke-color);
font-family: 'Encode Sans Semi Condensed', Verdana, sans-serif;
font-size: 10rem;
line-height: 10rem;
font-weight: 200;
text-align: center;
}
h2 {
margin: 20px auto 30px auto;
font-family: 'Encode Sans Semi Condensed', Verdana, sans-serif;
font-size: 1.5rem;
font-weight: 200;
text-align: center;
}
h1, h2 {
-webkit-transition: opacity 0.5s linear, margin-top 0.5s linear; /* Safari */
transition: opacity 0.5s linear, margin-top 0.5s linear;
}
.loading h1, .loading h2 {
margin-top: 0px;
opacity: 0;
}
.gears {
position: relative;
margin: 0 auto;
width: auto;
height: 0;
}
.gear {
position: relative;
z-index: 0;
width: 120px;
height: 120px;
margin: 0 auto;
border-radius: 50%;
background: var(--stroke-color);
}
.gear:before {
position: absolute;
left: 5px;
top: 5px;
right: 5px;
bottom: 5px;
z-index: 2;
content: "";
border-radius: 50%;
background: var(--main-color);
}
.gear:after {
position: absolute;
left: 25px;
top: 25px;
z-index: 3;
content: "";
width: 70px;
height: 70px;
border-radius: 50%;
border: 5px solid var(--stroke-color);
box-sizing: border-box;
background: var(--main-color);
}
.gear.one {
left: -130px;
}
.gear.two {
top: -75px;
}
.gear.three {
top: -235px;
left: 130px;
}
.gear .bar {
position: absolute;
left: -15px;
top: 50%;
z-index: 0;
width: 150px;
height: 30px;
margin-top: -15px;
border-radius: 5px;
background: var(--stroke-color);
}
.gear .bar:before {
position: absolute;
left: 5px;
top: 5px;
right: 5px;
bottom: 5px;
z-index: 1;
content: "";
border-radius: 2px;
background: var(--main-color);
}
.gear .bar:nth-child(2) {
transform: rotate(60deg);
-webkit-transform: rotate(60deg);
}
.gear .bar:nth-child(3) {
transform: rotate(120deg);
-webkit-transform: rotate(120deg);
}
@-webkit-keyframes clockwise {
0% {
-webkit-transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
}
}
@-webkit-keyframes anticlockwise {
0% {
-webkit-transform: rotate(360deg);
}
100% {
-webkit-transform: rotate(0deg);
}
}
@-webkit-keyframes clockwiseError {
0% {
-webkit-transform: rotate(0deg);
}
20% {
-webkit-transform: rotate(30deg);
}
40% {
-webkit-transform: rotate(25deg);
}
60% {
-webkit-transform: rotate(30deg);
}
100% {
-webkit-transform: rotate(0deg);
}
}
@-webkit-keyframes anticlockwiseErrorStop {
0% {
-webkit-transform: rotate(0deg);
}
20% {
-webkit-transform: rotate(-30deg);
}
60% {
-webkit-transform: rotate(-30deg);
}
100% {
-webkit-transform: rotate(0deg);
}
}
@-webkit-keyframes anticlockwiseError {
0% {
-webkit-transform: rotate(0deg);
}
20% {
-webkit-transform: rotate(-30deg);
}
40% {
-webkit-transform: rotate(-25deg);
}
60% {
-webkit-transform: rotate(-30deg);
}
100% {
-webkit-transform: rotate(0deg);
}
}
.gear.one {
-webkit-animation: anticlockwiseErrorStop 2s linear infinite;
}
.gear.two {
-webkit-animation: anticlockwiseError 2s linear infinite;
}
.gear.three {
-webkit-animation: clockwiseError 2s linear infinite;
}
.loading .gear.one, .loading .gear.three {
-webkit-animation: clockwise 3s linear infinite;
}
.loading .gear.two {
-webkit-animation: anticlockwise 3s linear infinite;
}
</style>
</head>
<body>
<h1>500</h1>
<h2>Unexpected Error <b>:(</b></h2>
<div class="gears">
<div class="gear one">
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
</div>
<div class="gear two">
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
</div>
<div class="gear three">
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1 @@
Internal server error

View File

@@ -0,0 +1,16 @@
<?php declare(strict_types = 1);
namespace App\UI\Control;
use App\Model\Latte\TemplateProperty;
use App\Modules\Base\BasePresenter;
use Nette\Application\UI\Control;
/**
* @property-read TemplateProperty $template
* @property-read BasePresenter $presenter
*/
abstract class BaseControl extends Control
{
}

View File

@@ -0,0 +1,49 @@
<?php declare(strict_types = 1);
namespace App\UI\Control;
use App\Modules\Base\BasePresenter;
use stdClass;
/**
* @mixin BasePresenter
*/
trait TFlashMessage
{
/**
* @internal
* @param string $message
* @param string $type
* @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
*/
public function flashMessage($message, $type = 'info'): stdClass
{
if ($this->isAjax()) {
$this->redrawControl('flashes');
}
return parent::flashMessage($message, $type);
}
public function flashInfo(string $message): stdClass
{
return $this->flashMessage($message, 'info');
}
public function flashSuccess(string $message): stdClass
{
return $this->flashMessage($message, 'success');
}
public function flashWarning(string $message): stdClass
{
return $this->flashMessage($message, 'warning');
}
public function flashError(string $message): stdClass
{
return $this->flashMessage($message, 'danger');
}
}

View File

@@ -0,0 +1,63 @@
<?php declare(strict_types = 1);
namespace App\UI\Control;
use App\Model\Exception\Runtime\InvalidStateException;
use App\Modules\Base\BasePresenter;
/**
* @mixin BasePresenter
*/
trait TModuleUtils
{
/**
* Gets module name
*/
public function getModuleName(): string
{
$name = $this->getName();
// Validate presenter has a proper name
if ($name === null) {
throw new InvalidStateException('Presenter doesn\'t have a name');
}
$parts = explode(':', $name);
return current($parts);
}
/**
* Is current module active?
*
* @param string $module Module name
*/
public function isModuleCurrent(string $module): bool
{
return strpos($this->getAction(true), $module) !== false;
}
/**
* Gets template dir
*/
public function getTemplateDir(): string
{
$fileName = $this->getReflection()->getFileName();
// Validate if class is not in PHP core
if ($fileName === false) {
throw new InvalidStateException('Class is defined in the PHP core or in a PHP extension');
}
$realpath = realpath(dirname($fileName) . '/../templates');
// Validate if file exists
if ($realpath === false) {
throw new InvalidStateException('File does not exist');
}
return $realpath;
}
}

View File

@@ -0,0 +1,19 @@
<?php declare(strict_types = 1);
namespace App\UI\Control;
use App\Modules\Base\BasePresenter;
use Nette\Localization\ITranslator;
/**
* @mixin BasePresenter
*/
trait TTranslate
{
protected function _(string $message): string // phpcs:ignore
{
return $this->getContext()->getByType(ITranslator::class)->translate($message);
}
}

31
app/ui/Form/BaseForm.php Normal file
View File

@@ -0,0 +1,31 @@
<?php declare(strict_types = 1);
namespace App\UI\Form;
use Nette\Application\UI\Form;
use Nette\Forms\Controls\TextInput;
class BaseForm extends Form
{
public function addFloat(string $name, ?string $label = null): TextInput
{
$input = self::addText($name, $label);
$input->addCondition(self::FILLED)
->addRule(self::MAX_LENGTH, null, 255)
->addRule(self::FLOAT);
return $input;
}
public function addNumeric(string $name, ?string $label = null): TextInput
{
$input = self::addText($name, $label);
$input->addCondition(self::FILLED)
->addRule(self::MAX_LENGTH, null, 255)
->addRule(self::NUMERIC);
return $input;
}
}

View File

@@ -0,0 +1,23 @@
<?php declare(strict_types = 1);
namespace App\UI\Form;
final class FormFactory
{
private function create(): BaseForm
{
return new BaseForm();
}
public function forFrontend(): BaseForm
{
return $this->create();
}
public function forBackend(): BaseForm
{
return $this->create();
}
}