#2 Практическое задание №2 -- Добавляем ещё одно поле в модель статьи

Что надо сделать

  1. Добавить возможность в админке скрывать временно некоторые статьи сайта -- т.е. в админке показываются все статьи, а на главной только активные.
  2. В админке в списке статей указать какая статья активна, а какая нет.

Это же задание на видео: https://youtu.be/esOk5t_wYMk

Возможные шаги

Делать задачу можно в таком порядке:

  1. Добавить поле/колонку active в таблицу статей и в модель Article -- с возможным значение 0 или 1 (по умолчанию 1 -- т.е. активна) -- т.е. подготовить SQL, который модифицирует таблицу
  2. На форму редактирования добавляем checkbox -- галочка стоит если статья сохранятся как отображаемая (см. подробности про передачу значения по умолчанию из checkbox)
  3. Правим процесс выборки данных в модели Article (подробности на видео)
  4. Изменяем работу вью главной страницы -- показываем только активные статьи
  5. В админке показываем все статьи (в списке), но в дополнительной колонке (правьте view) указываем активна ли статья.

Подсказки

(смотрите только после самостоятельной попытки решить!)

Как проверять -- перед отправкой решения

Прежде чем присылать код на проверку, убедитесь что статьи скрываются/показываются после сохранения их в админке:

вы должны скрыть статью, посмотреть скрылась ли она с главной, а потом снова сделать её активной в админке -- она должна появиться.

Остальные уроки: http://fkn.ktu10.com/?q=node/9428

sid's picture

Как вносить дополнительную колонку active в нашу cms, с помощью терминала или как то, по другому?

vedro-compota's picture

да с помощью терминала, но код который вы использовали пришлите вместе со ссылкой на решение -- например его можно добавить в какой-то файл (.sql) в репозиторий.

_____________
матфак вгу и остальная классика =)

vedro-compota's picture

ALTER TABLE `articles` ADD `active` 
TINYINT(1) NOT NULL AFTER `content`;

-- во-первых всегда пишите COMMENT чтобы потом было понятно что это за поле.
2) Также указывайте значение по умолчанию DEFAULT
-- в этом случае это важно, в т.ч. и потому, что все старые статьи надо было сделать либо активными либо неактивными.

По php:

вижу что реализован CRUD -- теперь статья обновляется
и выводится по новому в админке (судя по коду есть правка в таблице).
Но дайте ссылку на строку, где вы модифицируете выборку на главной,
чтобы не показывать там "неактивные" статьи.

_____________
матфак вгу и остальная классика =)

sid's picture

создав колонку таким образом это будет ошибка?

ALTER TABLE articles 
ADD active SMALLINT(1);
vedro-compota's picture

создав колонку таким образом это будет ошибка?

Также подсвечивайте синтаксис SQL-запросов (относится к оформлению).

_____________
матфак вгу и остальная классика =)

sid's picture

ALTER TABLE 'articles'
ADD COLUMN 'active' 
TINYINT(1) NOT NULL 
DEFAULT '0' 
COMMENT 'По умолчанию 0 запрет на просмотр для пользователя иначе1 никаких ограничений';
vedro-compota's picture

DEFAULT '0'
COMMENT 'По умолчанию 0 никаких ограничений если 1 запрет

-- лучше наоборот. ведь 0 это типа false, т.е. если active = 0, то логично что она "неактивна" = "запрет на вывод для пользователей"

_____________
матфак вгу и остальная классика =)

JinJim's picture

В Article.php строки 166-172.
Если присутствует параметр categoryId, то в запрос добавляется WHERE с указанием категории, которой нужно вывести.
Если этого параметра нет - происходит поиск по регулярному выражению в имени скрипа и, если в нем содержится admin.php, то добавляется пустая строка, то есть условие не добавляется, выводятся все статьи.
В противном случае - добавляется условие "active = 1", то есть когда выводятся статьи без категории и не админом, то выводятся только активные статьи.

 if($categoryId) {
 +            $categoryClause = "WHERE categoryId = :categoryId";                 
 +        } elseif(preg_match("/admin.php/", $_SERVER['REQUEST_URI'])) {
 +            $categoryClause = '';
 +        } else {
 +            $categoryClause = 'WHERE active = 1';
 +        }

Остановился на этом варианте, но я понимаю, что:

Можно по разному проверять на админа - например проверкой авторизованного пользователя в сессии;

Можно при просмотре статей определенной категории выводить только активные статьи (так даже правильнее, наверно, будет; могу поправить - просто к одному условию добавить еще одно: " AND active = 1" ).

Надеюсь смог донести свою мысль)

sid's picture

есть класс article в него была добавленна bool переменная со значением по умолчанию

public $active = true;

как с помощью флага можно править функцию getList();
чтобы она проверяла поле active на true/false и в случае елси она равна true
то эта сатья попадает в список отображаемых, иначе она не должна
выводится.

конструктор данного класса:

public function __construct($data=[])
{
      if (isset($data['id'])) {
          $this->id = (int) $data['id'];
      }
      
      if (isset($data['publicationDate'])) {
          $this->publicationDate = (string)$data['publicationDate'];     
      }


      if (isset($data['title'])) {
          $this->title = $data['title'];        
      }
      
      if (isset($data['categoryId'])) {
          $this->categoryId = (int) $data['categoryId'];      
      }
      
      if (isset($data['summary'])) {
          $this->summary = $data['summary'];         
      }
      
      if (isset($data['content'])) {
          $this->content = $data['content'];  
      }
      
      if (isset($data['active'])) {
          $this->active = $data['active'];  
      }
}

и сама функция getList():

public static function getList($numRows=1000000, 
            $categoryId=null, $order="publicationDate DESC") 
{
    $connection = new PDO(DB_DSN, DB_USERNAME, DB_PASSWORD);
    $categoryClause = $categoryId ? "WHERE categoryId = :categoryId" : "";
    $querySql = "SELECT SQL_CALC_FOUND_ROWS *, UNIX_TIMESTAMP(publicationDate) 
                AS publicationDate
                FROM articles $categoryClause
                ORDER BY  $order  LIMIT :numRows";
        
    $st = $connection->prepare($querySql);
    $st->bindValue(":numRows", $numRows, PDO::PARAM_INT);
        
    if ($categoryId) {
        $st->bindValue( ":categoryId", $categoryId, PDO::PARAM_INT);
    }

    $st->execute();        
    $list = [];

    while ($row = $st->fetch()) {
          article = new Article($row);
          $list[] = $article;
    }

    $querySql = "SELECT FOUND_ROWS() AS totalRows";
    $totalRows = $connection->query($querySql)->fetch();
    $connection = null;

    return (array( "results" => $list, "totalRows" => $totalRows[0]));
}
vedro-compota's picture

По поводу getList -- см https://youtu.be/CjR7ErmMVuc и продолжение подсказки https://youtu.be/Uug_dn85qbM

_____________
матфак вгу и остальная классика =)

JinJim's picture

Спасибо за подробный разбор!
Вопрос такой:
Абсолютно согласен, что необходим четвертый параметр. Как то так:

public static function getList($numRows=1000000, 
            $categoryId=null, $order="publicationDate DESC", $useActiveValue = false)

в этом случае нам придется передавать все 4 параметра при каждом вызове функции в index.php - и в функции homepage и в функции archive. Может имеет смысл сделать этот параметр первым - тогда и передавать нужно меньше параметров (как вариант - вторым или третьим - ведь $order вообще нигде не передается).

vedro-compota's picture

в этом случае нам придется передавать все 4 параметра при каждом вызове функции в index.php - и в функции homepage и в функции archive. Может имеет смысл сделать этот параметр первым - тогда и передавать нужно меньше параметров (как вариант - вторым или третьим - ведь $order вообще нигде не передается).

собственно ваше предложение вполне допустимо (на вкус автора), но помните вот что: сохраняя старый порядок вы гарантируете, что вам не ужно проверять ВЕСЬ КОД проекта на вызовы функции (то есть просто уменьшаете. трудоёмкость)

Если вы видите, что удобнее другой порядок и поправить код всего проекта (! -- а значит это только автопоиск по имени функции/метода) не сложно -- можно использовать ещё и другой.

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

Но в целом это всё на ваше усмотрение.

_____________
матфак вгу и остальная классика =)

sid's picture

Прошу проверить здесь

sid's picture
dimmkan's picture

Ссылка на коммит:
Ссылка

Firons's picture

Ошибка на сайте firstCMS

Скрипт абсолютно корректно работает, автономно от сайта. Запросы проходят на ура. Но на сайте выдает ошибку.

[!] Произошла ОШИБКА
Текст ошибки:
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '= 1' at line 1

<?php

//    Функция для пересборки запроса
    function buildWhereConditionForGetLists(...$arr) {      
        $accum = array(); //
        $switch = 0;
        foreach ($arr as $val) {
            if (!empty($val)) {
                $accum[] = $val;
                $switch += 1;
            }
        }
        if ($switch > 1) {
            return "WHERE ". implode(" AND ", $accum);
        } elseif ($switch == 1) {
            return "WHERE ". implode($accum);
        } else {
            return "";
        }  
    }
    
    
    $categoryId = 1;
    $active = 1;
    $order = "publicationDate DESC";
    $numRows = 5;
    $conn = new PDO("mysql:host=localhost;dbname=cms;charset=utf8;", "myuser", 12345);
    $fromPart = "FROM articles";
    $categoryClause = $categoryId ? "categoryId = :categoryId" : "";
    $activeClause = $active ? "active = :active" : "";
    $assembledRequest = buildWhereConditionForGetLists($categoryClause, $activeClause);
    $sql = "SELECT *, UNIX_TIMESTAMP(publicationDate) 
            AS publicationDate
            $fromPart $assembledRequest
            ORDER BY  $order  LIMIT :numRows";
    $st = $conn->prepare($sql);      
    $st->bindValue(":numRows", $numRows, PDO::PARAM_INT);
    
    if ($categoryId)
        $st->bindValue( ":categoryId", $categoryId, PDO::PARAM_INT);

    if ($active)
        $st->bindValue( ":active", $active, PDO::PARAM_INT);
    
    echo $sql . "<br>". "<br>";
    $st->execute(); // выполняем запрос к базе данных

    while ($result = $st->fetch(PDO::FETCH_ASSOC)) {
        print_r($result);
        echo "<br>". "<br>";
    }

SELECT *, UNIX_TIMESTAMP(publicationDate) AS publicationDate FROM articles WHERE categoryId = :categoryId AND active = :active ORDER BY publicationDate DESC LIMIT :numRows

Array ( [id] => 7 [publicationDate] => 1687802400 [categoryId] => 1 [title] => New Clause [summary] => New Clause New Clause [content] => New Clause New Clause New Clause New Clause [active] => 1 )

Array ( [id] => 3 [publicationDate] => 1497895200 [categoryId] => 1 [title] => Х. Колумб [summary] => Это итальянский мореплаватель, в 1492 году открывший для европейцев Америку, благодаря снаряжению экспедиций католическими королями. [content] => Колумб первым из достоверно известных путешественников пересёк Атлантический океан в субтропической и тропической полосе северного полушария и первым из европейцев ходил в Карибском море и Саргассово море [2]. Он открыл и положил начало исследованию Южной и Центральной Америки, включая их континентальные части и близлежащие архипелаги — Большие Антильские (Куба, Гаити, Ямайка и Пуэрто-Рико), Малые Антильские (от Доминики до Виргинских островов, а также Тринидад) и Багамские острова. Первооткрывателем Америки Колумба можно назвать с оговорками, ведь ещё в Средние века на территории Северной Америки бывали европейцы в лице исландских викингов (см. Винланд). Но, поскольку за пределами Скандинавии сведений об этих походах не было, именно экспедиции Колумба впервые сделали сведения о землях на западе всеобщим достоянием и положили начало колонизации Америки европейцами. Всего Колумб совершил 4 плавания к Америке: Первое плавание (3 августа 1492 — 15 марта 1493). Второе плавание (25 сентября 1493 — 11 июня 1496). Третье плавание (30 мая 1498 — 25 ноября 1500). Четвёртое плавание (9 мая 1502 — 7 ноября 1504). [active] => 1 )

Array ( [id] => 4 [publicationDate] => 1497808800 [categoryId] => 1 [title] => В. Янсзон и А.Тасман [summary] => Голландский мореплаватель и губернатор Виллем Янсзон стал первым европейцем, увидевшим побережье Австралии. [content] => Янсзон отправился в своё третье плавание из Нидерландов к Ост-Индии 18 декабря 1603 года в качестве капитана Duyfken, одного из двенадцати судов большого флота Стивена ван дер Хагена (англ.)русск..[113] Уже в Ост-Индии Янсзон получил приказ отправиться на поиски новых торговых возможностей, в том числе в «к большой земле Новой Гвинеи и другим восточным и южным землям.» 18 ноября 1605 года Duyfken вышел из Бантама к западному берегу Новой Гвинеи. Янсзон пересёк восточную часть Арафурского моря, и, не увидев Торресов пролив, вошёл в залив Карпентария. 26 февраля 1606 года он высадился у реки Пеннефазер (англ.)русск. на западном берегу полуострова Кейп-Йорк в Квинсленде, рядом с современным городом Уэйпа. Это была первая задокументированная высадка европейцев на австралийский континент. Янсзон нанёс на карту около 320 км побережья, полагая, что это южное продолжение Новой Гвинеи. В 1615 году Якоб Лемер и Виллем Корнелис Схаутен, обойдя мыс Горн, доказали, что Огненная Земля является островом и не может быть северной частью неизвестного южного континента. В 1642—1644 годах Абель Тасман, также голландский исследователь и купец на службе VOC, обошёл вокруг Новой Голландии, доказав, что Австралия не является частью мифического южного континента. Он стал первым европейцем, достигшим острова Земля Ван-Димена (сегодня Тасмания) и Новой Зеландии, а также в 1643 году наблюдал острова Фиджи. Тасман, его капитан Вискер и купец Гилсманс также нанесли на карту отдельные участки Австралии, Новой Зеландии и тихоокеанских островов. [active] => 1 )