Чат Telegram
Группа ВКонтакте
Новый комментарий

Kirill.K 17.10.2018 в 20:35

ArticlesController/edit:

if(!$this->user->isAdmin()) {
            throw new Forbidden('Для редактирования статьи нужно обладать правами администратора');
        }

Templates/articles/view:

<?php if(isset($_COOKIE['token']) and $user->isAdmin()): ?>
    <p><a href="http://myproject.loc/articles/<?= $article->getId() ?>/edit">Редактировать статью</a></p>
<? endif; ?>
ivashkevich 19.10.2018 в 00:23
isset($_COOKIE['token'])

Этого достаточно, чтобы знать, что пользователь залогинен? А если в этой куке ерунда какая-нибудь бессмысленная записана? Тогда $user будет null, и при попытке вызвать у него метод произойдет ошибка.

Kirill.K 19.10.2018 в 19:10

Хм... тогда, наверное, так:

<?php if($_COOKIE['token'] === $user->getId() . ':' . $user->getAuthToken() and $user->isAdmin()): ?>
    <p><a href="http://myproject.loc/articles/<?= $article->getId() ?>/edit">Редактировать статью</a></p>
<? endif; ?>

Громоздко, конечно, как то получается...

ivashkevich 19.10.2018 в 21:58

Да нет, все проще. Если пользователь не авторизован, то в $user будет null.
Достаточно проверить:

if ($user !== null && $user->isAdmin())
OneMoreTime 26.05.2020 в 16:18
if ($user !== null && $user->isAdmin())

Наверное перед этим стоит делать проверку юзера на isset?

ivashkevich 27.05.2020 в 17:57

У нас $user всегда в шаблоне есть же.

ilyaOrlov 02.12.2018 в 21:54

Проверил свой код несколько раз. Всё так, как вы написали. Но при редактировании, если передавать какое-то поле пустым, то вылетает ошибка о том, что $article->getId() становится null в шаблоне edit.php. Решил проблему передачей вместе с ошибкой еще и массива $article.

$this->view->renderHtml('articles/edit.php', ['error' => $e->getMessage(), 'article' => $article]);

Тогда все работает. В чем проблема, не подскажите?

ivashkevich 02.12.2018 в 22:58

Была ошибка, исправил. Спасибо. В шаблоне использовалась $article, я почему-то подумал, что если произойдет ошибка, то в шаблоне мы статью не будем выводить.

Metey 27.07.2019 в 19:27

При попытке обновления статьи НЕ админом – бросайте исключение ForbiddenException, как в прошлом уроке.

 public function edit(int $articleId): void
    { 
        $article = Article::getById($articleId);
        if ($article === null) {
            throw new NotFoundException();
        }
        if ($this->user === null) {
            throw new UnauthorizedException();
        }
        if (!$this->user->isAdmin()) {
            throw new ForbiddenException('Для доступа к данной странице необходимы права администратора!');
        }
    ......

Добавьте ссылку на странице показа статьи с текстом «Редактировать», которая будет вести на страницу редактирования этой статьи.
Сделайте так, чтобы эта ссылка показывалась только если пользователь залогинен и он админ.

<h1><?= $article->getName() ?></h1>
    <p><?= $article->getText() ?></p>
    <p>Автор: <?= $article->getAuthor()->getNickname() ?></p>

    <?if ($user!==null && $user->isAdmin()):?>
    <p><a href="/articles/<?= $article->getId() ?>/edit">Редактировать</a></p>
    <?endif?>
ivashkevich 28.07.2019 в 13:38

Отлично

Iliusha99 03.08.2019 в 17:57

Добавил метод в модель Юзера:

public function allowEdit(): bool
    {
        if ($this === null || $this->getRole() !== 'admin') {
            return false;
        }
        return true;
    }

В ArticleControllers при попытке переходить по ссылке ред. :

if (!$this->user->allowEdit()) {
            throw new AccessForbidden();
        }

И в шаблоне в зависимости от значения $user->allowEdit() решаем показать <<Редактировать>> или нет :

<?php if($user->allowEdit()):?> <p><a href="<?= $article->getId() ?>/edit">Редактировать</a></p> <?php endif; ?>
ivashkevich 03.08.2019 в 20:39

$this === null - такого не бывает. Если есть $this, значит есть объект. null там быть не может

ivashkevich 03.08.2019 в 20:42
public function allowEdit(): bool
    {
        if ($this === null || $this->getRole() !== 'admin') {
            return false;
        }
        return true;
    }

Сокращается до

public function allowEdit(): bool
    {
        return $this->getRole() === 'admin';
    }

А вообще, метод для проверки возможности редактирования должен быть у статьи, а не у пользователя. В этот метод аргументом должен передаваться объект пользователя, права которого нужно проверить.

Iliusha99 03.08.2019 в 20:44

ок, поправлю

ivashkevich 03.08.2019 в 20:44

В шаблон лучше передавать уже готовую переменную, которая будет говорить о том, разрешено ли редактирование статьи, например, isEditable. Эта переменная должна формироваться в контроллере и передаваться во view.

Moskva 07.08.2019 в 18:24

1.ArticlesConroller.php

...
if(!$this->user->IsAdmin()){
            throw new ForbiddenException();
        }
...

2-3.view.php

...
<? if(!empty($user) && ($user->isAdmin())): ?>
    <hr>
    <div style="text-align: right;"><a href="/articles/<?= $article->getId()?>/edit">Редактировать</a><div>
<? endif; ?>
...
ivashkevich 07.08.2019 в 18:57

if(!$this->user->IsAdmin()) - имена методов пишутся с маленькой буквы!

Moskva 08.08.2019 в 20:17

Не уследил, спасибо.

artemship 06.09.2019 в 18:21

articles/view.php

<?php if ($isEditable): ?>
    <p><a href="/articles/<?= $article->getId() ?>/edit">Редактировать</a></p>
<?php endif; ?>

ArticlesController

    public function edit(int $articleId): void
    {
    ...
        if (!$this->user->isAdmin()) {
            throw new ForbiddenException('Статьи могут редактировать только администраторы');
        }
    ...

    public function view(int $articleId): void
    {
    ...
        if ($this->user === null) {
            $isEditable = false;
        } else {
            $isEditable = ($this->user->isAdmin());
        }

        $this->view->renderHtml('articles/view.php', [
            'article' => $article,
            'isEditable' => $isEditable
        ]);
    ...

Тут вопрос: можно ли в экшене view проверку заменить на

        if ($this->user !== null) {
            $isEditable = ($this->user->isAdmin());
        }

В таком случае в методе $this->view->renderHtml высвечивается предупреждение, что переменная $isEditable "might have not been defined". При этом ошибок в рендере шаблона нет. Как лучше поступить?

ivashkevich 08.09.2019 в 09:37
$isEditable = ($this->user->isAdmin())

Для чего скобки?

Ответ на твой вопрос - нет, нельзя. Потому что если пользователь будет не залогинен, возникнет ошибка.

andreskrip 15.02.2020 в 14:15

Спасибо за урок!

ArticlesController::edit

if (!$this->user->isAdmin()) {
            throw new ForbiddenException();
        }

templates/articles/view.php

<?php if ($user !== null && $user->isAdmin()): ?>
    <a href="/articles/<?= $article->getId() ?>/edit">Редактировать</a>
<?php endif; ?>

Вопрос не по уроку: немного неудобно было при отладке проверки на авторизацию постоянно заново заходить в одиночную статью, т.к. после входа редиректит на главную. Каким образом можно сделать редирект на ту страницу, с которой ты перешел на страницу авторизации? В гугле были предложения джавасриптом

header("location:javascript://history.go(-1)");

или через глобальную переменную (во-первых оно меня вернуло на страницу авторизации, вместо нужной страницы, а во-вторых пишут что это нехорошая практика в принципе)

header('Location: ' . $_SERVER['HTTP_REFERER']);
exit();
ivashkevich 18.02.2020 в 15:54

Я бы использовал второй вариант - при переходе на страницу логина устанавливал бы куку с реферером и после логина переходил по ней. Хорошая практика или нет - решать вам) Если проблем она вам не создает, значит не такая уж и плохая.

andreskrip 18.02.2020 в 15:56

спасибо большое за совет)

ivashkevich 18.02.2020 в 18:24

Пожалуйста)

OneMoreTime 23.03.2020 в 10:59

В контоллере статей есть повторяющиеся проверки в разных методах:

if ($this->user === null){
            throw new UnAuthorizedException();
        }
        if (!$this->user->isAdmin()){
            throw new ForbiddenException('You don\'t have Admin rights');
        }

в модели статьи так же:

if (empty($fields['name'])){
            throw new InvalidArgumentException('Name field cannot be empty');
        }
        if (empty($fields['text'])){
            throw new InvalidArgumentException('Text field cannot be empty');
        }

Если такие небольшие повторяющиеся участки встречаются всего два-три раза, стоит их выделять в отдельные общие методы? А то вроде получается масло масляное - повторяющийся код убираем, но делаем нагромождение методов на каждый чих. Или все таки лучше любую повторяемость обобщать отдельно , чтобы потом было легче и быстрее при необходимости модифицировать код?

ivashkevich 23.03.2020 в 11:58

Такие небольшие кусочки можно вынести, но только если это даст реальное упрощение кода. Ну и есть "правило трёх" - если требуется такой же код в еще одном месте - возьми и скопируй, но если уже в трёх местах - вынеси в отдельный компонент (класс, метод, функцию и пр.)

Dimitry 08.04.2020 в 07:48
//ArticlesController
public function view(int $articleId)
    {
        $article = Article::getById($articleId);

        if ($article === null) {
            throw new NotFoundException();
        }

        $this->view->renderHtml('articles/view.php', [
            'article' => $article,
            'admin'   =>$this->user->isAdmin()
        ]);
    }

    public function edit(int $articleId)
    {
        $article = Article::getById($articleId);

        if ($article === null) {
            throw new NotFoundException();
        }

        if ($this->user === null) {
            throw new UnauthorizedException();
        }
        if (!$this->user->isAdmin()){
            throw new Forbidden();
        }

        if (!empty($_POST)) {
            try {
                $article->updateFromArray($_POST);
            } catch (InvalidArgumentException $e) {
                $this->view->renderHtml('articles/edit.php', ['error' => $e->getMessage(), 'article' => $article]);
                return;
            }

            header('Location: /articles/' . $article->getId(), true, 302);
            exit();
        }

        $this->view->renderHtml('articles/edit.php', ['article' => $article]);
    }

//view.php
<?php include __DIR__ . '/../header.php'; ?>
    <h1><?= $article->getName() ?></h1>
    <p><?= $article->getText() ?></p>
    <p>Автор: <?= $article->getAuthor()->getNickname() ?></p>
<?
    if($admin){
        echo '<a href="http://myproject.loc/articles/'.$article->getId().'/edit">Редактировать</a>';
    }
?>
<?php include __DIR__ . '/../footer.php'; ?>

Домашка

ivashkevich 08.04.2020 в 08:29

В шаблоне правильнее тоже использовать имя isAdmin, потому что это не сама сущность админа, а только флажок.

Dimitry 08.04.2020 в 07:56

У меня вопрос: Когда происходит обработка исключений, то почему-то отображаются ссылки для входа и регистрации, хотя пользователь уже зарегистрирован и выполнен вход, при переключении на страницу главная все в порядке - пользователь авторизован, как это исправить?

ivashkevich 08.04.2020 в 08:30

А в какой момент прокидывается пользователь в шаблон?

Ed 17.04.2020 в 20:52
  1. Задание (ArticleController)

    if($article === null) {
      throw new NotFoundException();
    }
    
    try {
      if($this->user === null) {
        throw new UnautorizedException();
      }
      if($this->user->getRole() !== 'admin') {
        throw new ForbiddenException();
      }
    } catch(ForbiddenException | UnautorizedException $e) {
      header('Location: /');
      exit();
    }

    Там же:

    public function view(int $articleId)
    {
    $article = Article::getById($articleId);
    
    if($article === null) {
      throw new NotFoundException();
    }
    
    $author = User::getById($article->getAuthor()->getId());
    $autorizedUserId = $this->user->getId();
    $articleAccess = false;
    
    if($autorizedUserId === $author->getId()) {
      $articleAccess = true;
    }
    
    $this->view->renderHtml('articles\view.php',
      ['article' => $article,
       'access' => $articleAccess
      ]);
    }

    templates/articles/View

    <?php if($access):  ?>
    <a href="/articles/<?=$article->getId()?>/edit"> Редактировать </a>
    <?php endif; ?>

    Сделал так, чтобы только автор статьи мог редактировать её, а не любой admin)

ivashkevich 18.04.2020 в 06:05
} catch(ForbiddenException | UnautorizedException $e) {
  header('Location: /');
  exit();
}

Что это за обработка исключений такая? Нужно завести соответствующие шаблоны и показывать информацию об ошибках пользователю.

Также нет смысла бросать исключения и ловить их в одном и том же слое приложения. Можно же сразу написать

  if($this->user === null) {
    header('Location: /');
    exit();
  }

Исключения удобны тем, что можно бросать их в одном слое (например, модели) а ловить в другом (контроллере). Или в вашем случае, бросили в контроллере, а поймали во фронт-контроллере.

babls2332@gmail.com 19.04.2020 в 11:23

ArticlesController.php

$userController->isAdmin($this->user);

User.php

public function isAdmin(User $user) {
        if ($user->role !== 'admin') {
            throw new Forbidden('Недостаточно прав');
        } else {
            return true;
        }
    }

articles/view.php

<? if ((!empty($user)) && ($user->isAdmin($user))) { ?>
    <a href="/articles/<?= $article->getId() ?>/edit">Редактировать</a>
<? } ?>
ivashkevich 19.04.2020 в 15:51
     public function isAdmin(User $user) {
        if ($user->role !== 'admin') {
            throw new Forbidden('Недостаточно прав');
        } else {
            return true;
        }
    }

Это не исключительная ситуация, и исключение здесь бросать не к месту. Метод называется isAdmin, значит он должен вернуть булево значение true/false, в зависимости от того, является ли пользователь админом или нет. Если не является - возвращайте false, ничего исключительного здесь нет.

В то же время, для того чтобы бросать исключения, когда какое-то условие не выполняется, применяются методы с префиксом ensure...
Например:

public function ensureIsAdmin(): void
{
    if (!$user->isAdmin()) {
        throw ...
    }
}

Обратите внимание, что такие методы ничего не возвращают, лишь бросают исключение, когда что-то не так.

Ensure можно перевести как "убедись что..." и далее пишем, в чем нужно убедиться. Если не убедились - это уже исключительная ситуация.

babls2332@gmail.com 19.04.2020 в 16:57

В прошлом уроке ничего не сказали по-поводу этого метода, потому так и оставил

Alexann 22.04.2020 в 21:52

ArticlesController.php:

...
public function edit(int $articleId): void
    {
        $article = Article::getById($articleId);

        if ($article === null) {
            throw new NotFoundException('Не найдена такая статья');
        }
        if ($this->user === null) {
            throw new UnauthorizedException();
        }
        if (!$this->user->isAdmin()) {
            throw new ForbiddenException('Для редактирования статьи необходимы права администратора');
        }
...

view.php:

<?php include __DIR__ . '/../header.php';
if ($user!==null && $user->isAdmin()) :?>
    <a href="/articles/<?= $article->getId() ?>/edit">Редактировать</a>
<? endif; ?>
<h1><?= $article->getName() ?></h1>
<p><?= $article->getText() ?></p>
<p>Автор: <?= $article->getAuthor()->getNickname() ?></p>
<?php include __DIR__ . '/../footer.php'; ?>
ivashkevich 23.04.2020 в 10:36

Отлично!

Dmitry.Dudin 28.04.2020 в 14:29

view.php

<?php if($user!==null && $user->isAdmin()): ?>
<a href="/articles/<?= $article->getId() ?>/edit">Редактировать статью</a>
    <?php endif; ?>

ArticlesController.php

        if (!$this->user->isAdmin()) {
            throw new Forbidden('Ошибка 403: Для добавления статьи необходимо обладать правами администратора');
        }

Я на жену столько времени не трачу, сколько потратил на эту задачу. Спасибо тебе, Артём!)

На самом деле задача простейшая, но эти мелочи...)

ivashkevich 28.04.2020 в 19:25

:D

Fill Patron 10.05.2020 в 11:46

src\MyProject\Controllers\ArticlesController.php

...
    public function edit(int $articleId): void
    {
        $article = Article::getById($articleId);
        $user = $this->user;

        if ($article === null) {
            throw new NotFoundException('Статьи не существует');
        }

        if ($user === null) {
            throw new UnauthorizedException();
        }

        if (!$user->isAdmin()) {
            throw new ForbiddenException('Недостаточно прав, редактировать статьи можно только админам');
        }

        if (!empty($_POST)) {
            try {
                $article->updateFromArray($_POST);
            } catch (InvalidArgumentException $e) {
                $this->view->renderHtml('articles/edit.php', ['error' => $e->getMessage(), 'article' => $article]);
                return;
            }

            header('Location: /articles/' . $article->getId(), true, 302);
            exit();
        }
...

templates\articles\view.php

<?php include __DIR__ . '/../header.php'; ?>
    <h1><?= $article->getName() ?></h1>
    <p><?= $article->getText() ?></p>
    <p>Автор: <?= $article->getAuthor()->getNickName() ?></p>
<?php
if (!empty($user)) {
    if ($user->isAdmin()) {
        echo '<a href="' . $article->getId() . '/edit">Редактировать</a>';
    }
}
?>
<?php include __DIR__ . '/../footer.php'; ?>
ivashkevich 10.05.2020 в 18:29

Отлично

dima1 20.05.2020 в 19:43

src/MyProject/Controllers/ArticlesController.php view()

if (!empty($this->user) && $this->user->isAdmin()) {
            $beEdited = true;
        } else {
            $beEdited = false;
        }

        $this->view->renderHtml('articles/view.php', [
            'article' => $article,
            'beEdited' => $beEdited,
        ]);

src/MyProject/Controllers/ArticlesController.php edit()

if (!$this->user->isAdmin()) {
    throw new ForbiddenException('У Вас не достаточно прав, чтобы редактировать статью');
}

templates/articles/view.php

<?php require_once __DIR__ . '/../header.php'; ?>
    <h2><?= $article->getName() ?></h2>
    <div><?= $article->getText() ?></div>
    <p>Автор: <?= $article->getAuthor()->getNickname() ?></p>
    <?= $beEdited ? '<p><a href="/articles/' . $article->getId() . '/edit">Редактировать статью</a></p>' : '' ?>
<?php require_once __DIR__ . '/../footer.php'; ?>

Работает все!

ivashkevich 20.05.2020 в 19:52

А если зайти на страницу просмотра неавторизванным юзером?

dima1 20.05.2020 в 21:44

Изменил.
Вопрос:
В выражении if (!empty($this->user) && $this->user->isAdmin()) если первое условие не выполняется, то второе проверяется?

ivashkevich 21.05.2020 в 07:13

Нет, в PHP && и || это ленивые операторы. Выполняются слева направо. Если левый операнд оператора && имеет значение false, дальше проверять не имеет смысла. С || наоборот, будет выполнять пока не встретит первый true.

dima1 21.05.2020 в 09:01

Ясно, спасибо.

titelivus 21.05.2020 в 16:04

При попытке обновления статьи НЕ админом – бросайте исключение ForbiddenException, как в прошлом уроке.

ArticlesController.php

...
    public function edit(int $articleId)
    {
        $article = Article::getById($articleId);

        if ($article === null) {
            throw new NotFoundException();
        }

        if ($this->user === null) {
            throw new UnauthorizedException();
        }

        if (!$this->user->isAdmin()) {
            throw new ForbiddenException();
        }
...

Добавьте ссылку на странице показа статьи с текстом «Редактировать», которая будет вести на страницу редактирования этой статьи.

Сделайте так, чтобы эта ссылка показывалась только если пользователь залогинен и он админ.

templates\articles\view.php

<?php include __DIR__ . '/../header.php'; ?>
    <h1><?= $article->getName() ?></h1>
    <p><?= $article->getText() ?></p>
    <p>Автор: <?= $article->getAuthor()->getNickname() ?></p>
    <?php if ($user !== null && $user->isAdmin()): ?>
        <a href="/articles/<?= $article->getId() ?>/edit">Редактировать</a>
    <?php endif; ?>
<?php include __DIR__ . '/../footer.php'; ?>
ivashkevich 22.05.2020 в 08:05

Супер!

studentDev 05.06.2020 в 15:41
//При попытке обновления статьи НЕ админом – бросайте исключение ForbiddenException, как в прошлом уроке.

public function edit(int $articleId): void
        {
            $article = Article::getById($articleId);

            if($this->user == null) {
                throw new Forbidden('Forbidden', 403);
            }

            if($this->user->getRole() !== 'admin') {
                throw new Forbidden('Forbidden', 403);
            }

            if(!$this->user->isAdmin()) {
                throw new Forbidden('Forbidden', 403);
            }
}
--------------------------------
//2. Добавьте ссылку на странице показа статьи с текстом «Редактировать», которая будет вести на страницу редактирования этой статьи.
&
//3. Сделайте так, чтобы эта ссылка показывалась только если пользователь залогинен и он админ.
//ArticlesController
 public function view(int $articleId)
        {

            $article = Article::getById($articleId);

            if(!empty($user) && $user->isAdmin()) {
                $role = $this->user->isAdmin();
            }

            if($article === null) {
                $this->view->renderHtml('errors/404.php', [], 404);
                return;
            }

            $this->view->renderHtml('articles/view.php', ['article' => $article, 'articleId' => $article->getId(), 'role' => $role]);
        }

///templates/articles/view.php
<?php include __DIR__ . '/../header.php'; ?>
    <h1><?= $article->getName() ?></h1>
    <p><?= $article->getText() ?></p>
    <p>Автор статьи: <?= $article->getAuthor()->getNickname() ?></p>

<?php if($role === 'admin'): ?>
    <p><a href="/articles/<?=$articleId?>/edit">Редактировать</a></p>
<?php endif; ?>

<?php include __DIR__ . '/../footer.php'; ?>

Update! +

ivashkevich 06.06.2020 в 08:33
            if(!$this->user) {
                $role = null;
            } else {
                $role = $this->user->getRole();
            }

Лучше проверять в шаблоне

if (!empty($user) && $user->isAdmin()):
tsaruk4356@gmail.com Patron 08.07.2020 в 13:57

ArticlesController

if (!$this->user->isAdmin()) {
            throw new Forbidden('Для редактирования статьи необходимо обладать правами администратора');
        }

view.php

<?php include __DIR__ . '/../header.php';
if ($user !== null && $user->isAdmin()): ?>
<a href="/articles/<?= $article->getId() ?>/edit">Редактировать</a>
<?php endif; ?>
<h1><?= $article->getName() ?></h1>
<p><?= $article->getText() ?></p>
<p>Автор: <?= $article->getAuthor()->getNickname() ?></p>
<?php include __DIR__ . '/../footer.php'; ?>
ivashkevich 08.07.2020 в 14:34

Отлично!

IePyton Patron 05.08.2020 в 11:59

домашка
1 пункт
ArticlesController.php

use MyProject\Exceptions\ForbiddenException;
...
    public function edit(int $articleId): void
    {
        $article = Article::getById($articleId);

        if ($article === null) {
            throw new NotFoundException();
        }

        if (!$this->user->isAdmin()) {
            throw new ForbiddenException();
        }

index.php

catch (\MyProject\Exceptions\ForbiddenException $e) {
    $view = new \MyProject\View\View(__DIR__ . '/../templates/errors');
    $view->renderHtml('403.php', ['error'=>$e->getMessage()], 403);
}

2 пункт и 3 пункт

view.php

<?php include __DIR__ . '/../header.php'; ?>
    <h1><?= $article->getName() ?></h1>
    <p><?= $article->getText() ?></p>
    <p>Автор статьи: <?= $article->getAuthor()->getNickname() ?></p>

<?php if ($user->isAdmin()): ?>
    <p><a href="/phplearn.my/www/articles/<?= $article->getId() ?>/edit">Редактировать статью</p>
<?php endif; ?>

<?php include __DIR__ . '/../footer.php'; ?>
ivashkevich 05.08.2020 в 13:32

Огонь!

VitaliyB Patron 31.08.2020 в 08:54
  1. При попытке обновления статьи НЕ админом – бросайте исключение ForbiddenException, как в прошлом уроке.
public function edit(int $articleId): void
...
 if (!$this->user->isAdmin()) {
            throw new Forbidden('Ошибка доступа. Нужны права администратора.');
        }
...

Добавьте ссылку на странице показа статьи с текстом «Редактировать», которая будет вести на страницу редактирования этой статьи.
Сделайте так, чтобы эта ссылка показывалась только если пользователь залогинен и он админ.

view.php

...
<?php if ($user !==null && $user-> isAdmin()):?>
    <p> <a href="/articles/<?= $article->getId()?>/edit">Редактировать</a> <a href="/articles/<?= $article->getId()?>/delete">  Удалить</a> </p>
<?endif;?>
...
ivashkevich 03.09.2020 в 17:14

Супер!

Логические задачи с собеседований