Basic functionality

This commit is contained in:
2022-03-12 16:25:30 +01:00
parent f3beaa64cf
commit acc21b7b24
137 changed files with 12647 additions and 5089 deletions

4
.prettierrc Normal file
View File

@@ -0,0 +1,4 @@
{
"semi": false,
"singleQuote": true
}

View File

@@ -14,6 +14,14 @@ class Bootstrap
$configurator = new Configurator;
$appDir = dirname(__DIR__);
// Provide some parameters
$configurator->addParameters([
'rootDir' => realpath(__DIR__ . '/..'),
'appDir' => __DIR__,
'wwwDir' => realpath(__DIR__ . '/../www'),
]);
$configurator->setDebugMode($_SERVER['HTTP_HOST'] === 'localhost');
$configurator->enableTracy($appDir . '/log');

View File

@@ -1,27 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Presenters;
use Nette;
final class Error4xxPresenter extends Nette\Application\UI\Presenter
{
public function startup(): void
{
parent::startup();
if (!$this->getRequest()->isMethod(Nette\Application\Request::FORWARD)) {
$this->error();
}
}
public function renderDefault(Nette\Application\BadRequestException $exception): void
{
// load template 403.latte or 404.latte or ... 4xx.latte
$file = __DIR__ . "/templates/Error/{$exception->getCode()}.latte";
$this->template->setFile(is_file($file) ? $file : __DIR__ . '/templates/Error/4xx.latte');
}
}

View File

@@ -1,43 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Presenters;
use Nette;
use Nette\Application\Responses;
use Nette\Http;
use Tracy\ILogger;
final class ErrorPresenter implements Nette\Application\IPresenter
{
use Nette\SmartObject;
/** @var ILogger */
private $logger;
public function __construct(ILogger $logger)
{
$this->logger = $logger;
}
public function run(Nette\Application\Request $request): Nette\Application\Response
{
$exception = $request->getParameter('exception');
if ($exception instanceof Nette\Application\BadRequestException) {
[$module, , $sep] = Nette\Application\Helpers::splitName($request->getPresenterName());
return new Responses\ForwardResponse($request->setPresenterName($module . $sep . 'Error4xx'));
}
$this->logger->log($exception, ILogger::EXCEPTION);
return new Responses\CallbackResponse(function (Http\IRequest $httpRequest, Http\IResponse $httpResponse): void {
if (preg_match('#^text/html(?:;|$)#', (string) $httpResponse->getHeader('Content-Type'))) {
require __DIR__ . '/templates/Error/500.phtml';
}
});
}
}

View File

@@ -1,12 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Presenters;
use Nette;
final class HomepagePresenter extends BasePresenter
{
}

View File

@@ -1,13 +1,39 @@
<span title="Toggle Vite" data-action="netteVite">
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
<svg
width="410"
height="404"
viewBox="0 0 410 404"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z"
fill="url(#paint0_linear)"
/>
<path
d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z"
fill="url(#paint1_linear)"
/>
<defs>
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
<linearGradient
id="paint0_linear"
x1="6.00017"
y1="32.9999"
x2="235"
y2="344"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#41D1FF" />
<stop offset="1" stop-color="#BD34FE" />
</linearGradient>
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
<linearGradient
id="paint1_linear"
x1="194.651"
y1="8.81818"
x2="236.076"
y2="292.989"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#FFEA83" />
<stop offset="0.0833333" stop-color="#FFDD35" />
<stop offset="1" stop-color="#FFA800" />
@@ -16,23 +42,25 @@
</svg>
</span>
<style>
#tracy-debug [data-action="netteVite"] {
#tracy-debug [data-action='netteVite'] {
cursor: pointer;
padding: 0 .4em;
margin: 0 -.4em;
padding: 0 0.4em;
margin: 0 -0.4em;
display: block;
}
#tracy-debug [data-action="netteVite"]:hover {
#tracy-debug [data-action='netteVite']:hover {
background: #c3c1b8;
}
</style>
<script>
function getCookie(n, a = `; ${document.cookie}`.match(`;\\s*${n}=([^;]+)`)) {
return a ? JSON.parse(a[1]) : false;
return a ? JSON.parse(a[1]) : false
}
const element = document.querySelector('#tracy-debug [data-action="netteVite"]')
const element = document.querySelector(
'#tracy-debug [data-action="netteVite"]'
)
console.log(getCookie('netteVite'))
@@ -41,9 +69,13 @@
}
element.addEventListener('click', () => {
document.cookie = getCookie('netteVite') ? 'netteVite=false; path=/;' : 'netteVite=true; path=/;'
document.cookie = getCookie('netteVite')
? 'netteVite=false; path=/;'
: 'netteVite=true; path=/;'
getCookie('netteVite') ? (element.style.opacity = '') : (element.style.opacity = '40%')
getCookie('netteVite')
? (element.style.opacity = '')
: (element.style.opacity = '40%')
document.location.reload()
})
</script>

View File

@@ -0,0 +1,32 @@
<?php //declare(strict_types = 1);
namespace App\MyApi\v1\Handlers;
use App\Model\Database\EntityManager;
use Tomaj\NetteApi\Handlers\BaseHandler;
use Tomaj\NetteApi\Response\JsonApiResponse;
use Tomaj\NetteApi\Response\ResponseInterface;
use App\Model\Database\Facade\TermFacade;
class TermsHandler extends Basehandler
{
private $em;
private $termFacade;
public function __construct(EntityManager $em, TermFacade $tf)
{
parent::__construct();
$this->em = $em;
$this->termFacade = $tf;
}
public function handle(array $params): ResponseInterface
{
$translation = $this->em->getTranslationRepository()->findOneBy(['id' => $_GET['translation']]);
$this->termFacade->setDirection($translation->direction);
$q = $this->termFacade->findByStringFull($_GET['string'], $translation->dictionary, '');
$terms = $q->getArrayResult();
return new JsonApiResponse(200, ['status' => 'ok', 'terms' => $terms]);
}
}

View File

@@ -0,0 +1,39 @@
<?php //declare(strict_types = 1);
namespace App\MyApi\v1\Handlers;
use App\Model\Database\EntityManager;
use Tomaj\NetteApi\Handlers\BaseHandler;
use Tomaj\NetteApi\Response\JsonApiResponse;
use Tomaj\NetteApi\Response\ResponseInterface;
use App\Model\Database\Facade\TermFacade;
use Nette\Utils\Strings;
class TranslationsHandler extends Basehandler
{
private $em;
public function __construct(EntityManager $em)
{
parent::__construct();
$this->em = $em;
}
public function handle(array $params): ResponseInterface
{
$trs = $this->em->getTranslationRepository()->findBy([], ['id' => 'ASC', 'direction' => 'ASC']);
$translations = [];
foreach ($trs as $t) {
$lang1 = $t->getLangName1();
$lang2 = $t->getLangName2();
$lang = mb_substr($lang1, 0, -1);
$lang .= "o-" . $lang2;
$slug = Strings::webalize($lang);
$translations[$t->id]["slug"] = $slug;
$translations[$t->id]["lang"] = $lang;
}
return new JsonApiResponse(200, ['status' => 'ok', 'translations' => $translations]);
}
}

View File

@@ -0,0 +1,29 @@
<?php //declare(strict_types = 1);
namespace App\MyApi\v1\Handlers;
use App\Model\Database\EntityManager;
use Tomaj\NetteApi\Handlers\BaseHandler;
use Tomaj\NetteApi\Response\JsonApiResponse;
use Tomaj\NetteApi\Response\ResponseInterface;
class UsersListingHandler extends Basehandler
{
private $em;
public function __construct(EntityManager $em)
{
parent::__construct();
$this->em = $em;
}
public function handle(array $params): ResponseInterface
{
//$users = [];
//foreach ($this->userRepository->all() as $user) {
// $users[] = $user->toArray();
//}
$users = [ 'name' => 'test'];
return new JsonApiResponse(200, ['status' => 'ok', 'users' => $users]);
}
}

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 Executable 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
{
use \Nette\SmartObject;
}

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,29 @@
<?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
*
* @property int $id
*/
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,50 @@
<?php declare(strict_types = 1);
namespace App\Model\Database\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Model\Database\Entity\Attributes\TId;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
/**
* @ORM\Entity(repositoryClass="App\Model\Database\Repository\DictTypeRepository")
* @ORM\HasLifecycleCallbacks
*
* @property int $id
* @property String $shortName
* @property String $fullName
*/
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,94 @@
<?php declare(strict_types = 1);
namespace App\Model\Database\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Model\Database\Entity\Attributes\TId;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
/**
* @ORM\Entity(repositoryClass="App\Model\Database\Repository\DictionaryRepository")
* @ORM\Table(name="`dictionary`")
* @ORM\HasLifecycleCallbacks
*
* @property int $id
* @property String $name
* @property String $fullname
* @property Term $terms
* @property Language $lang1
* @property Language $lang2
*/
class Dictionary extends AbstractEntity
{
use Tid;
public function __construct($name,$fullname)
{
$this->name = $name;
$this->fullName = $fullname;
$this->translations = new ArrayCollection();
}
/**
* @ORM\OneToMany(targetEntity="\App\Model\Database\Entity\Translation", mappedBy="dictionary", cascade={"persist"})
* @var Collection&iterable<Translation>
*/
protected Collection $translations;
public function getTranslations()
{
return $this->translations;
}
/**
* @ORM\OnetoMany(targetEntity="\App\Model\Database\Entity\Term", mappedBy="dictionary")
* @var Collection&iterable<Term>
*/
protected Collection $terms;
/**
* @ORM\ManyToOne(targetEntity="Language", inversedBy="dictionaries1")
* @ORM\JoinColumn(name="lang1_id", referencedColumnName="id")
*/
protected ?Language $lang1;
/**
* @ORM\ManyToOne(targetEntity="Language", inversedBy="dictionaries2")
* @ORM\JoinColumn(name="lang2_id", referencedColumnName="id")
*/
protected ?Language $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,65 @@
<?php declare(strict_types = 1);
namespace App\Model\Database\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Model\Database\Entity\Attributes\TId;
use Doctrine\Common\Collections\Collection;
/**
* @ORM\Entity(repositoryClass="App\Model\Database\Repository\LanguageRepository")
* @ORM\HasLifecycleCallbacks
*
* @property int $id
* @property String $name
* @property String $shortName
* @property Dictionary $dictionaries1
* @property Dictionary $dictionaries2
*
*/
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;
}
/*
* @var Collection&iterable<Dictionary>
* @ORM\OneToMany(targetEntity="Dictionary", mappedBy="language")
*/
protected Collection $dictionaries1;
/*
* @var Collection&iterable<Dictionary>
* @ORM\OneToMany(targetEntity="Dictionary", mappedBy="language")
*/
protected Collection $dictionaries2;
}
?>

View File

@@ -0,0 +1,85 @@
<?php
namespace App\Model\Database\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Model\Database\Entity\Attributes\TId;
/**
* @ORM\Entity(repositoryClass="App\Model\Database\Repository\PronunciationRepository")
* @ORM\HasLifecycleCallbacks
*
* @property int $id
* @property Term $terms
* @property PronunciationType $type
* @property String $ipa
* @property String $filename
*/
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;
/**
* Get the value of filename
*/
public function getFilename()
{
return $this->filename;
}
/**
* Get the value of terms
*/
public function getTerms()
{
return $this->terms;
}
/**
* Get the value of ipa
*/
public function getIpa()
{
return $this->ipa;
}
/**
* Get the value of type
*/
public function getType()
{
return $this->type;
}
}
?>

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Model\Database\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Model\Database\Entity\Attributes\Tid;
/**
* @ORM\Entity(repositoryClass="App\Model\Database\Repository\PronunciationTypeRepository")
* @ORM\HasLifecycleCallbacks
*
* @property int $id
* @property String $name
* @property String $fullName
*/
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,36 @@
<?php
namespace App\Model\Database\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Model\Database\Entity\Attributes\TId;
/**
* @ORM\Entity(repositoryClass="App\Model\Database\Repository\SuffixRepository")
* @ORM\HasLifecycleCallbacks
*
* @property int $id
* @property String $text
*/
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,231 @@
<?php
namespace App\Model\Database\Entity;
use App\Model\Database\Entity\Attributes\TId;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Proxy\Proxy;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="App\Model\Database\Repository\TermRepository")
* @ORM\HasLifecycleCallbacks
* @ORM\Table(indexes={@ORM\Index(columns={"string1"}, flags={"fulltext"}),@ORM\Index(columns={"string2"}, flags={"fulltext"})})
*
* @property int $id
* @property String $string1
* @property String $string2
* @property Dictionary $dictionary
* @property mixed $pronunciations
* @property Suffix $suffix1
* @property Suffix $suffix2
* @property DictType $type
* @property int $order2
* @property String $member
*/
class Term extends AbstractEntity
{
use Tid;
public function __construct(Dictionary $dictionary, $string)
{
$this->dictionary = $dictionary;
$this->string1 = $string;
}
/**
* @ORM\ManyToOne(targetEntity="\App\Model\Database\Entity\Dictionary", inversedBy="terms",cascade={"persist", "remove" })
*/
protected $dictionary;
/**
* @ORM\Column(type="string")
*/
protected $string1;
/**
* @ORM\Column(type="string")
*/
protected $string2;
/**
* @ORM\ManyToOne(targetEntity="Suffix")
*/
protected $suffix1;
/**
* @ORM\ManyToOne(targetEntity="Suffix")
*/
protected $suffix2;
/**
* @ORM\ManyToOne(targetEntity="DictType")
*/
protected $type;
/**
* @ORM\ManyToMany(targetEntity="Pronunciation", inversedBy="terms")
* @ORM\JoinTable(name="terms_pronunciations")
* @var Collection&iterable<Pronunciation>
*/
protected Collection $pronunciations;
/**
* @ORM\Column(type="string",nullable=true)
*/
protected $member;
public function setMember($member)
{
$this->member = $member;
return $this;
}
public function getMember()
{
return $this->member;
}
/**
* @ORM\Column(type="smallint",nullable=true)
*/
protected $order2;
/**
* @ORM\Column(type="json_array",nullable=true)
*/
protected $flags;
public function getFlags()
{
return $this->flags;
}
public function setFlags($flags)
{
$this->flags = $flags;
return $this;
}
public function getOrder2()
{
return $this->order2;
}
public function setOrder2($order2)
{
$this->order2 = $order2;
return $this;
}
/**
* Get the value of string1
*/
public function getString1()
{
return $this->string1;
}
/**
* Set the value of string1
*
* @return self
*/
public function setString1($string1)
{
$this->string1 = $string1;
return $this;
}
/**
* Get the value of string2
*/
public function getString2()
{
return $this->string2;
}
/**
* Set the value of string2
*
* @return self
*/
public function setString2($string2)
{
$this->string2 = $string2;
return $this;
}
/**
* Get the value of suffix1
*/
public function getSuffix1()
{
return $this->suffix1;
}
/**
* Set the value of suffix1
*
* @return self
*/
public function setSuffix1($suffix1)
{
$this->suffix1 = $suffix1;
return $this;
}
/**
* Get the value of suffix2
*/
public function getSuffix2()
{
return $this->suffix2;
}
/**
* Set the value of suffix2
*
* @return self
*/
public function setSuffix2($suffix2)
{
$this->suffix2 = $suffix2;
return $this;
}
/**
* Get the value of type
*/
public function getType()
{
return $this->type;
}
/**
* Set the value of type
*
* @return self
*/
public function setType($type)
{
$this->type = $type;
return $this;
}
/**
* Get the value of pronunciations
*
* @return Collection&iterable<Pronunciation>
*/
public function getPronunciations()
{
return $this->pronunciations;
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace App\Model\Database\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Model\Database\Entity\Attributes\TId;
/**
* @ORM\Entity(repositoryClass="App\Model\Database\Repository\TermFlagRepository")
* @ORM\HasLifecycleCallbacks
*
* @property int $id
* @property WordClass $class
* @property Language $language
* @property String $code
* @property String $description
*/
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,87 @@
<?php declare(strict_types = 1);
namespace App\Model\Database\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Model\Database\Entity\Attributes\TId;
/**
* @ORM\Entity(repositoryClass="App\Model\Database\Repository\TranslationRepository")
* @ORM\Table(name="`translation`")
* @ORM\HasLifecycleCallbacks
*
* @property int $id
* @property Dictionary $dictionary
* @property Language $lang1
* @property Language $lang2
* @property int $direction
*/
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="App\Model\Database\Entity\Dictionary", inversedBy="translations")
*/
protected $dictionary;
public function getDictionary()
{
return $this->dictionary;
}
/**
* @ORM\ManyToOne(targetEntity="App\Model\Database\Entity\Language", inversedBy="translations1")
*/
protected $lang1;
/**
* @ORM\ManyToOne(targetEntity="App\Model\Database\Entity\Language", inversedBy="translations2")
*/
protected $lang2;
/**
* @ORM\Column(type="string")
*/
protected $lang_name1;
public function getLangName1(): String
{
return $this->lang_name1;
}
/**
* @ORM\Column(type="string")
*/
protected $lang_name2;
public function getLangName2(): String
{
return $this->lang_name2;
}
/**
* @ORM\Column(type="smallint")
*/
protected $direction;
public function getDirection() : int
{
return $this->direction;
}
}
?>

View File

@@ -0,0 +1,214 @@
<?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 App\Model\Utils\Strings;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="App\Model\Database\Repository\UserRepository")
* @ORM\Table(name="`user`")
* @ORM\HasLifecycleCallbacks
*
* @property int $id
* @property String $name
* @property String $surname
* @property String $email
* @property String $password
* @property String $role
* @property int $state
*/
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,43 @@
<?php
namespace App\Model\Database\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Model\Database\Entity\Attributes\TId;
/**
* @ORM\Entity(repositoryClass="App\Model\Database\Repository\WordClassRepository")
* @ORM\HasLifecycleCallbacks
*
* @property int $id
* @property String $name
*
*/
class WordClass extends AbstractEntity
{
use Tid;
public function __construct($id,$name)
{
$this->id = $id;
$this->name = $name;
}
/**
* @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,124 @@
<?php declare(strict_types = 1);
namespace App\Model\Database\Facade;
use App\Model\Database\Entity\Dictionary;
use App\Model\Database\Entity\Term;
use App\Model\Database\Entity\Translation;
use App\Model\Database\EntityManager;
class TermFacade
{
/** @var EntityManager */
private $em;
private $direction;
public function __construct(
EntityManager $em
)
{
$this->em = $em;
}
public function setDirection(int $direction)
{
$this->direction = $direction;
}
/**
* @param mixed[] $data
*/
public function findByString(String $term,Dictionary $d,String $clen = null)
{
$query = $this->em->createQueryBuilder('t')->select('t')
->addSelect('s1')
->addSelect('s2')
->from(Term::class,'t')
->leftJoin('t.suffix1','s1')
->leftJoin('t.suffix2','s2');
if ($this->direction == 1) {
$query->where('t.string1 LIKE :str')->setParameter('str', $term."%");
$query->orderBy('t.string1,t.id','ASC');
} else if ($this->direction == 2) {
$query->where('t.string2 LIKE :str')->setParameter('str', $term."%");
$query->orderBy('t.string2','ASC');
$query->addOrderBy('t.order2','ASC');
}
$query->andWhere('t.dictionary = :dict')->setParameter(':dict',$d->id);
if ($clen)
$query->andWhere('t.member LIKE :clen')->setParameter('clen','%'.$clen.'%');
return $query->getQuery();
}
/**
* @param mixed[] $data
*/
public function findByStringQuick(String $string, Dictionary $d, String $clen = null)
{
$query = $this->em->createQueryBuilder('t')->select('DISTINCT t.string1 AS str')->from(Term::class,'t');
if ($this->direction == 1) {
$query->where('t.string1 LIKE :str')->setParameter('str',$string."%");
$query->orderBy('t.string1','ASC');
} else if ($this->direction == 2) {
$query->where('t.string2 LIKE :str')->setParameter('str', $string."%");
$query->orderBy('t.string2','ASC');
}
$query->andWhere('t.dictionary = :dict')->setParameter(':dict',$d->id);
return $query->getQuery();
}
/**
* @param mixed[] $data
*/
public function findByStringFull(String $string, Dictionary $d, String $clen = null, $like = true)
{
$search = $string;
if ($like) $search = $search . "%";
$query = $this->em->createQueryBuilder('t')->select('t')
->addSelect('s1')
->addSelect('s2')
->addSelect('p')
->from(Term::class,'t')
->leftJoin('t.suffix1','s1')
->leftJoin('t.suffix2','s2')
->leftJoin('t.pronunciations','p');
if ($this->direction == 1) {
$query->where('t.string1 LIKE :str')->setParameter('str',$search);
$query->orderBy('t.id','ASC');
$query->addOrderBy('p.id','ASC');
} else if ($this->direction == 2) {
$query->where('t.string2 LIKE :str')->setParameter('str', $search);
$query->orderBy('t.string2','ASC');
$query->addOrderBy('t.order2','ASC');
}
$query->andWhere('t.dictionary = :dict')->setParameter(':dict',$d->id);
if ($clen)
$query->andWhere('t.member LIKE :clen')->setParameter('clen','%'.$clen.'%');
return $query->getQuery();
}
/**
* @param mixed[] $data
*/
public function findByStringSimple(String $string, Dictionary $d, String $clen = null)
{
$query = $this->em->createQueryBuilder('t')->select('t')->from(Term::class,'t');
if ($this->direction == 1) {
$query->where('t.string1 LIKE :str')->setParameter('str',$string."%");
$query->orderBy('t.id','ASC');
} else if ($this->direction == 2) {
$query->where('t.string2 LIKE :str')->setParameter('str', $string."%");
$query->orderBy('t.string2','ASC');
$query->addOrderBy('t.order2','ASC');
}
$query->andWhere('t.dictionary = :dict')->setParameter(':dict',$d->id);
return $query->getQuery();
}
}

View File

@@ -0,0 +1,86 @@
<?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());
}
/**
* Fetches all records and returns an associative array indexed by key
*
* @param array $criteria
* @param string $key
*
* @throws \Exception|QueryException
* @return array
*/
public function findAssoc(array $criteria = [],String $key = NULL)
{
if (!is_array($criteria)) {
$key = $criteria;
$criteria = [];
}
$qb = $this->createQueryBuilder('e')
->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);
}
}
try {
return $qb->getQuery()->getResult();
} catch (\Exception $e) {
throw $this->handleException($e, $qb);
}
}
}

View File

@@ -0,0 +1,18 @@
<?php declare(strict_types = 1);
namespace App\Model\Database\Repository;
use App\Model\Database\Entity\DictType;
/**
* @method DictType|NULL find($id, ?int $lockMode = NULL, ?int $lockVersion = NULL)
* @method DictType|NULL findOneBy(array $criteria, array $orderBy = NULL)
* @method DictType[] findAll()
* @method DictType[] findBy(array $criteria, array $orderBy = NULL, ?int $limit = NULL, ?int $offset = NULL)
* @extends AbstractRepository<DictType>
*/
class DictTypeRepository extends AbstractRepository
{
}

View File

@@ -0,0 +1,18 @@
<?php declare(strict_types = 1);
namespace App\Model\Database\Repository;
use App\Model\Database\Entity\Dictionary;
/**
* @method Dictionary|NULL find($id, ?int $lockMode = NULL, ?int $lockVersion = NULL)
* @method Dictionary|NULL findOneBy(array $criteria, array $orderBy = NULL)
* @method Dictionary[] findAll()
* @method Dictionary[] findBy(array $criteria, array $orderBy = NULL, ?int $limit = NULL, ?int $offset = NULL)
* @extends AbstractRepository<Dictionary>
*/
class DictionaryRepository extends AbstractRepository
{
}

View File

@@ -0,0 +1,16 @@
<?php declare(strict_types = 1);
namespace App\Model\Database\Repository;
use App\Model\Database\Entity\Pronunciation;
/**
* @method Pronunciation|NULL find($id, ?int $lockMode = NULL, ?int $lockVersion = NULL)
* @method Pronunciation|NULL findOneBy(array $criteria, array $orderBy = NULL)
* @method Pronunciation[] findAll()
* @method Pronunciation[] findBy(array $criteria, array $orderBy = NULL, ?int $limit = NULL, ?int $offset = NULL)
* @extends AbstractRepository<Pronunciation>
*/
class PronunciationRepository extends AbstractRepository
{
}

View File

@@ -0,0 +1,18 @@
<?php declare(strict_types = 1);
namespace App\Model\Database\Repository;
use App\Model\Database\Entity\TermFlag;
/**
* @method TermFlag|NULL find($id, ?int $lockMode = NULL, ?int $lockVersion = NULL)
* @method TermFlag|NULL findOneBy(array $criteria, array $orderBy = NULL)
* @method TermFlag[] findAll()
* @method TermFlag[] findBy(array $criteria, array $orderBy = NULL, ?int $limit = NULL, ?int $offset = NULL)
* @extends AbstractRepository<TermFlag>
*/
class TermFlagRepository extends AbstractRepository
{
}

View File

@@ -0,0 +1,18 @@
<?php declare(strict_types = 1);
namespace App\Model\Database\Repository;
use App\Model\Database\Entity\Term;
/**
* @method Term|NULL find($id, ?int $lockMode = NULL, ?int $lockVersion = NULL)
* @method Term|NULL findOneBy(array $criteria, array $orderBy = NULL)
* @method Term[] findAll()
* @method Term[] findBy(array $criteria, array $orderBy = NULL, ?int $limit = NULL, ?int $offset = NULL)
* @extends AbstractRepository<Term>
*/
class TermRepository extends AbstractRepository
{
}

View File

@@ -0,0 +1,57 @@
<?php declare(strict_types = 1);
namespace App\Model\Database\Repository;
use Doctrine\ORM\EntityRepository;
use App\Model\Database\Entity\Dictionary;
class TermsQuery extends EntityRepository
{
private Dictionary $dictionary;
private String $string;
private Int $direction;
private String $clen;
public function __construct(Dictionary $dict,String $string,Int $direction = 1)
{
$this->dictionary = $dict;
$this->string = $string;
$this->direction = $direction;
}
public function withClen(String $clen)
{
if ($clen != null && $clen != '')
$this->clen = $clen;
}
/**
* Fetches all records like $key => $value pairs
*
* @param mixed[] $criteria
* @param mixed[] $orderBy
* @return mixed[]
*/
protected function doCreateQuery()
{
$query = $this->createQueryBuilder('t')->select('t')
->addSelect('s1')
->addSelect('s2')
->leftJoin('t.suffix1','s1')
->leftJoin('t.suffix2','s2');
if ($this->direction == 1) {
$query->where('t.string1 LIKE :str')->setParameter('str',$this->string."%");
$query->orderBy('t.string1,t.id','ASC');
} else if ($this->direction == 2) {
$query->where('t.string2 LIKE :str')->setParameter('str', $this->string."%");
$query->orderBy('t.string2','ASC');
$query->addOrderBy('t.order2','ASC');
}
$query->andWhere('t.dictionary = :dict')->setParameter(':dict',$this->dictionary->id);
if ($this->clen)
$query->andWhere('t.member LIKE :clen')->setParameter('clen','%'.$this->clen.'%');
return $query;
}
}

View File

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

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,61 @@
<?php declare(strict_types = 1);
namespace App\Model\Database;
use App\Model\Database\Entity\User;
use App\Model\Database\Entity\Translation;
use App\Model\Database\Entity\Term;
use App\Model\Database\Entity\TermFlag;
use App\Model\Database\Entity\Pronunciation;
use App\Model\Database\Entity\DictType;
use App\Model\Database\Entity\Dictionary;
use App\Model\Database\Repository\UserRepository;
use App\Model\Database\Repository\TranslationRepository;
use App\Model\Database\Repository\TermRepository;
use App\Model\Database\Repository\TermFlagRepository;
use App\Model\Database\Repository\PronunciationRepository;
use App\Model\Database\Repository\DictTypeRepository;
use App\Model\Database\Repository\DictionaryRepository;
/**
* @mixin EntityManager
*/
trait TRepositories
{
public function getUserRepository(): UserRepository
{
return $this->getRepository(User::class);
}
public function getTranslationRepository(): TranslationRepository
{
return $this->getRepository(Translation::class);
}
public function getTermRepository(): TermRepository
{
return $this->getRepository(Term::class);
}
public function getTermFlagRepository(): TermFlagRepository
{
return $this->getRepository(TermFlag::class);
}
public function getPronunciationRepository(): PronunciationRepository
{
return $this->getRepository(Pronunciation::class);
}
public function getDictTypeRepository(): DictTypeRepository
{
return $this->getRepository(DictType::class);
}
public function getDictionaryRepository(): DictionaryRepository
{
return $this->getRepository(Dictionary::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, []);
}
}

30
app/model/Latte/Filters.php Executable file
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);
}
}

16
app/model/Latte/Macros.php Executable file
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,64 @@
<?php declare(strict_types = 1);
namespace App\Model\Router;
use Nette\Application\Routers\Route;
use Nette\Application\Routers\RouteList;
use Nette\Routing\RouteList as RoutingRouteList;
final class RouterFactory
{
public function create(): RouteList
{
$router = new RouteList();
$this->buildApi($router);
$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;
}
protected function buildApi(RouteList $router): RouteList
{
$router[] = $list = new RouteList('Api');
$list[] = new Route('/api/v<version>/<package>[/<apiAction>][/<params>]', 'Api: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',
]);
}
}

15
app/model/Security/Identity.php Executable file
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);
}
}

10
app/model/Utils/Arrays.php Executable file
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
{
}

13
app/model/Utils/DateTime.php Executable file
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
{
}

10
app/model/Utils/FileSystem.php Executable file
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 Executable 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 Executable 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
{
}

10
app/model/Utils/Strings.php Executable file
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
{
}

10
app/model/Utils/Validators.php Executable file
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,65 @@
{**
* @param string $basePath web base path
* @param array $flashes flash messages
*}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{ifset title}{include title|striptags} | {/ifset}Nette Sandbox</title>
<link rel="stylesheet" href="{$basePath}/css/style.css">
<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<meta name="viewport" content="width=device-width">
<script src="https://code.jquery.com/jquery-1.12.0.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
{block head}{/block}
</head>
<body>
<header>
<hgroup>
<h1>My Dictionary</h1>
<h2>multilanguage dictionary with pronunciations</h2>
</hgroup>
<nav>
<ul>
<li><a href="{link Homepage:default}">Jednoduchý</a></li>
<li><a href="{link Homepage:ipa}">s výslovnosťou</a></li>
<li><a href="{link Homepage:interactive}">Interaktívny</a></li>
<li><a href="{link Homepage:alphabet}">Abeceda</a></li>
</ul>
</nav>
<a href="#" title="Jaro's Solutions homepage"><img src="logo.gif" alt="Jaro's Solutions" /></a>
</header>
<article>
<h1 n:ifset="$title">{$title}</h1>
<div n:foreach="$flashes as $flash" n:class="flash, $flash->type">{$flash->message}</div>
{include content}
</article>
<section>
<h1>Slovníky</h1>
<ul>
{foreach $translations as $tr}
<li><a href="{link Homepage:select $tr["slug"] }">{$tr["lang"]}</a></li>
{/foreach}
</ul>
</section>
<footer>
<p>&copy; 2016 Jaro's Solutions - <a href="#">Sitemap</a> </p>
</footer>
{block scripts}
<script src="https://nette.github.io/resources/js/netteForms.min.js"></script>
<script src="{$basePath}/js/nette.ajax.js"></script>
<script src="{$basePath}/js/main.js"></script>
{/block}
</body>
</html>

View File

@@ -1,8 +1,6 @@
<?php
<?php declare(strict_types = 1);
declare(strict_types=1);
namespace App\Presenters;
namespace App\Modules\Front;
use Nette;
use Vite;
@@ -11,7 +9,7 @@ use Vite;
/**
* Base presenter for all application presenters.
*/
abstract class BasePresenter extends Nette\Application\UI\Presenter
abstract class BaseFrontPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private Vite $vite,

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;
use Nette;
final class HomePresenter extends BaseFrontPresenter
{
}

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}

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