Наследование шаблонов в PHP без использования сторонних библиотек. Соединение с базой данных

Многих интересует вопрос, как перенести сайт и БД на новый хостинг или с тестовой локальной машины на рабочий сервер? Внимательно читаем инструкцию.

Для переноса сайта с одного хостинга на другой, или с тестовой локальной машины на рабочий сервер выполните следующие действия:
1. Копируем содержимое всех файлов XOOPS, включая xoops_lib и xoops_data. Лучше архивировать прямо на сервере (локальной машине), легче будет скачать/заливать;
2. Создаём дамп базы данных на старом/локальном сервере;
2.1. Заходим в phpMyAdmin и выбираем нужную базу данных;
2.2. Нажимаем на вкладку "Экспорт" [рис. 1]

Рис. 1

2.3. Выбираем способ экспорта "Быстрый - отображать минимум настроек" нажимаем ok и скачиваем дамп [рис. 2]. В старых версиях нужно указать "Сохранить как файл".

Рис. 2

3. Создаём на новом сервере базу данных для сайта. При создании указываем кодировку utf8. [рис. 3]

Рис. 3

Замечане: Мы привели пример создания базы данных через phpMyAdmin, для этого у вас должны быть привелегии на создание баз данных. Более подробно об создании баз данных читайте в инструкциях к Вашему хостингу.

3.1. Заходим в созданную базу данных и выбираем вкладку "Операции". Здесь мы должны указать кодировку сравнения [рис. 4 ]. Этот шаг можно пропустить, если вы указали кодировку при создании базы данных.

Рис. 4

4. Импортируем созданный на втором шаге дамп базы данных на новый хостиг.
4.1. Заходим в phpMyAdmin и выбираем созданную базу данных.
4.2. Переходим во вкладку "Импорт", выбираем дамп базы данных с локального компьютера, кодировка файла utf-8, и нажимаем ok. [рис. 5]

Рис. 5

5. Закачиваем на новый сервер архив с файлами XOOPS и распаковываем его.
6. Следующим дирректириям устанавливаем права на запись (UNIX 777):
uploads/
uploads/avatars/
uploads/images/
uploads/ranks/
uploads/smilies/
xoops_lib/modules/protector/configs/
xoops_data/caches
xoops_data/caches/xoops_cache
xoops_data/caches/smarty_cache
xoops_data/caches/smarty_compile
xoops_data/configs
xoops_data/data
7. Редактируем конфигурационные файлы;
7.1. Открываем файл /mainfile.php и редактируем следующие строки:
7.1.1. Полный путь до папки XOOPS на новом сервере: define("XOOPS_ROOT_PATH", "C:/xampp/htdocs/xoops");
7.1.2. Полный путь до папки xoops_lib на новом сервере (из соображений безопасности её необходимо вынести в не корня сайта): define("XOOPS_PATH", "C:/xampp/htdocs/xoops/xoops_lib");
7.1.3. Полный путь до папки xoops_data на новом сервере (из соображений безопасности её необходимо вынести в не корня сайта): define("XOOPS_VAR_PATH", "C:/xampp/htdocs/xoops/xoops_data");
7.1.4. HTTP адрес сайта: define("XOOPS_URL", "http://сайт");
7.2. Открываем файл /xoops_data/data/secure.php и редактируем следующие строки:
7.2.1. Хост сервера базы данных (оточняйте у хостинг провайдера): define("XOOPS_DB_HOST", "localhost");
7.2.2. Пользователь созданной базы данных: define("XOOPS_DB_USER", "xoops_user");
7.2.3. Пароль созданной базы данных: define("XOOPS_DB_PASS", "xoops_pass");
7.2.4. Имя созданной базы данных: define("XOOPS_DB_NAME", "xoops_2.5.5_new");
8. На файлы /mainfile.php и /xoops_data/data/secure.php ставьте права только для чтения (UNIX 444)
9. Открываем сайт в браузере, в нашем случае это

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

В нашей папке cms создаем каталог classes . В папке classes создаем новый файл с именем Article.php и копируем в него следующий код:

id = (int) $data["id"]; if (isset($data["publicationDate"])) $this->publicationDate = (int) $data["publicationDate"]; if (isset($data["title"])) $this->title = preg_replace ("/[^\.\,\-\_\"\"\@\?\!\:\$ a-zA-Z0-9()]/", "", $data["title"]); if (isset($data["summary"])) $this->summary = preg_replace ("/[^\.\,\-\_\"\"\@\?\!\:\$ a-zA-Z0-9()]/", "", $data["summary"]); if (isset($data["content"])) $this->content = $data["content"]; } /** * Устанавливаем свойств с помощью значений формы редактирования записи в заданном массиве * * @param assoc Значения записи формы */ public function storeFormValues ($params) { // Сохраняем все параметры $this->__construct($params); // Разбираем и сохраняем дату публикации if (isset($params["publicationDate"])) { $publicationDate = explode ("-", $params["publicationDate"]); if (count($publicationDate) == 3) { list ($y, $m, $d) = $publicationDate; $this->publicationDate = mktime (0, 0, 0, $m, $d, $y); } } } /** * Возвращаем объект статьи соответствующий заданному ID статьи * * @param int ID статьи * @return Article|false Объект статьи или false, если запись не найдена или возникли проблемы */ public static function getById($id) { $conn = new PDO(DB_DSN, DB_USERNAME, DB_PASSWORD); $sql = "SELECT *, UNIX_TIMESTAMP(publicationDate) AS publicationDate FROM articles WHERE id = :id"; $st = $conn->prepare($sql); $st->bindValue(":id", $id, PDO::PARAM_INT); $st->execute(); $row = $st->fetch(); $conn = null; if ($row) return new Article($row); } /** * Возвращает все (или диапазон) объектов статей в базе данных * * @param int Optional Количество строк (по умолчанию все) * @param string Optional Столбец по которому производится сортировка статей (по умолчанию "publicationDate DESC") * @return Array|false Двух элементный массив: results => массив, список объектов статей; totalRows => общее количество статей */ public static function getList($numRows=1000000, $order="publicationDate DESC") { $conn = new PDO(DB_DSN, DB_USERNAME, DB_PASSWORD); $sql = "SELECT SQL_CALC_FOUND_ROWS *, UNIX_TIMESTAMP(publicationDate) AS publicationDate FROM articles ORDER BY " . mysql_escape_string($order) . " LIMIT:numRows"; $st = $conn->prepare($sql); $st->bindValue(":numRows", $numRows, PDO::PARAM_INT); $st->execute(); $list = array(); while ($row = $st->fetch()) { $article = new Article($row); $list = $article; } // Получаем общее количество статей, которые соответствуют критерию $sql = "SELECT FOUND_ROWS() AS totalRows"; $totalRows = $conn->query($sql)->fetch(); $conn = null; return (array ("results" => $list, "totalRows" => $totalRows)); } /** * Вставляем текущий объект статьи в базу данных, устанавливаем его свойства. */ public function insert() { // Есть у объекта статьи ID? if (!is_null($this->id)) trigger_error ("Article::insert(): Attempt to insert an Article object that already has its ID property set (to $this->id).", E_USER_ERROR); // Вставляем статью $conn = new PDO(DB_DSN, DB_USERNAME, DB_PASSWORD); $sql = "INSERT INTO articles (publicationDate, title, summary, content) VALUES (FROM_UNIXTIME(:publicationDate), :title, :summary, :content)"; $st = $conn->prepare ($sql); $st->bindValue(":publicationDate", $this->publicationDate, PDO::PARAM_INT); $st->bindValue(":title", $this->title, PDO::PARAM_STR); $st->bindValue(":summary", $this->summary, PDO::PARAM_STR); $st->bindValue(":content", $this->content, PDO::PARAM_STR); $st->execute(); $this->id = $conn->lastInsertId(); $conn = null; } /** * Обновляем текущий объект статьи в базе данных */ public function update() { // Есть ли у объекта статьи ID? if (is_null($this->id)) trigger_error ("Article::update(): Attempt to update an Article object that does not have its ID property set.", E_USER_ERROR); // Обновляем статью $conn = new PDO(DB_DSN, DB_USERNAME, DB_PASSWORD); $sql = "UPDATE articles SET publicationDate=FROM_UNIXTIME(:publicationDate), title=:title, summary=:summary, content=:content WHERE id = :id"; $st = $conn->prepare ($sql); $st->bindValue(":publicationDate", $this->publicationDate, PDO::PARAM_INT); $st->bindValue(":title", $this->title, PDO::PARAM_STR); $st->bindValue(":summary", $this->summary, PDO::PARAM_STR); $st->bindValue(":content", $this->content, PDO::PARAM_STR); $st->bindValue(":id", $this->id, PDO::PARAM_INT); $st->execute(); $conn = null; } /** * Удаляем текущий объект статьи из базы данных */ public function delete() { // Есть ли у объекта статьи ID? if (is_null($this->id)) trigger_error ("Article::delete(): Attempt to delete an Article object that does not have its ID property set.", E_USER_ERROR); // Удаляем статью $conn = new PDO(DB_DSN, DB_USERNAME, DB_PASSWORD); $st = $conn->prepare ("DELETE FROM articles WHERE id = :id LIMIT 1"); $st->bindValue(":id", $this->id, PDO::PARAM_INT); $st->execute(); $conn = null; } } ?>

Файл получается достаточно длинным, но код очень простой. Разберем его подробно:

1. Определение класса и его свойства

Сначала определим класс Article:

Class Article {

Все, что следует за данным строками до закрывающей фигурной скобки в конце файла содержит код нашего класса Article .

После определения класса мы объявляем свойства класса: $id , $publicationDate и так далее. Каждый объект Article , который мы создаем, будет хранить данные в объявленных свойствах. Обратите внимание, что имена свойств соответствуют именам полей в таблице articles .

Технически, такой тип класса, который содержит свойства соответствующие непосредственно полям базы данных и методы для хранения и получения записей, соответствует шаблону объектно-ориентированного проектирования, известному как active record .

2. Конструктор

Затем мы создаем методы класса. Это функции, которые привязаны к классу и к объекту, создаваемому из класса. Наш основной код вызывает методы для манипулирования данными в объекте Article .

Первый метод, __construct() , является конструктором. Это специальный метод, который автоматически вызывается системой PHP каждый раз, когда создается новый объект Article . Наш конструктор получает необязательный массив $data , в котором содержатся данные для свойств нового объекта. Затем мы присваиваем данные свойствам в теле конструктора. Таким образом, получается удобный способ для создания и инициализации объекта в одно действие.

$this->propertyName означает: "Свойство объекта this с именем " $propertyName ".

Обратите внимание, что метод фильтрует данные, прежде чем присвоить их свойствам. Свойства id и publicationDate приводятся к типу int с помощью (int) , так данные значения должны быть типа int . Свойства title и summary фильтруются с помощью регулярных выражений, так как в них допускает наличие символов из определенного набора. С точки зрения безопасности фильтрация данных ввода - отличная практика. Пропускаем только допустимые значения и символы.

Однако, мы не фильтруем свойство content . Почему? Вероятно, администратор захочет использовать более широкий диапазон символов в содержании статьи - например, разметку HTML. Если мы ограничим диапазон доступных символов в содержании, то снизим полезность нашей системы для администратора.

Обычно, такие места могут оказаться дырой в системе безопасности, так как пользователь может вставить вредный код JavaScript или материалы с ошибками в содержание статьи. Однако, так как мы полагаем. что единственной персоной, которой доступно редактирование содержание, будет администратор системы, располагающий безграничным доверием, то вопрос с уязвимостью содержания остается за рамками нашего внимания. Если вы имеете дело с генерируем пользователями содержанием, например, комментариями или записями форума, то следует быть более осторожным и допускать только "безопасный" код HTML к использованию. Отличным инструментом для решения таких задач является HTML Purifier , который анализирует ввод кода HTML и удаляет все потенциальные угрозы.

Безопасность кода PHP выходит за рамки наших уроков. Вам следует посвятить определенное время для изучения данного вопроса .

3. storeFormValues()

Следующий метод storeFormValues() похож на конструктор в том, что он сохраняет полученный массив данных в свойствах объекта. Основное отличие заключается в том, что storeFormValues() может обрабатывать данные в формате, который используется в формах New Article (Новая статья) и Edit Article (Редактировать статью) (мы создадим их позже). В частности, он может обрабатывать дату публикации в формате YYYY-MM-DD , конвертировать ее в формат времени UNIX, который отлично подходит для хранения в объекте.

Формат времени UNIX представляет собой целое значение, которое содержит количество секунд от полуночи 1 января 1970 до искомой даты. Датой в таком формате легко оперировать, и ее удобно хранить.

Назначение данного метода - облегчить реализацию скрипта для хранения дат, вводимых в формах.

Все члены (то есть свойства и методы) нашего класса Article имеют ключевое слово public перед определением, что означает доступность кода вне класса. Также можно создавать частные члены (директива private) (их можно использовать только в классе) и защищенные члены (директива protected) (которые можно использовать в классе и его подклассах).

4. getById()

Теперь перейдем к методам, реализующим доступ к базе данных MySQL. Первый из них - getById() . Он принимает в качестве аргумента ID статьи ($id) и возвращает запись с указанным ID из таблицы articles , сохраняя данные в новом объекте Article .

Обычно, когда вы вызываете метод, сначала нужно создать объект, а затем вызвать метод, принадлежащий объекту. Но, так как getById() возвращает новый объект Article , будет полезно вызывать его напрямую, а не через существующий объект. Иначе придется создавать новый объект-заглушку каждый раз, когда нужно вызвать метод и получить статью.

Для разрешения вызова метода без объекта мы добавляем декларацию static к определению метода. Таким образом разрешается вызов метода непосредственно без определения объекта.

Public static function getById($id) {

Метод использует PDO для соединения с базой данных, получает запись статьи с помощью запроса SQL SELECT и сохраняет данные в новом объекте Article , который возвращается в вызывающий код. PDO — PHP Data Objects —объектно-ориентированная библиотека, встроенная в PHP, которая облегчает связь скриптов PHP с базами данных.

Разберем метод подробнее:

    Соединение с базой данных

    $conn = new PDO(DB_DSN, DB_USERNAME, DB_PASSWORD);

    Здесь выполняется соединение с базой данных MySQL с помощью данных из файла config.php . Дескриптор соединения сохраняется в переменной $conn . Данный дескриптор используется в остальном коде для обмена данных с базой.

    Получаем запись статьи

    Выражение SELECT возвращает все поля (*) из записи в таблице articles , которые соответствуют заданному полю id . Значение поля publicationDate возвращается в формате времени UNIX, вместо формата для дат MySQL, что упрощает процесс сохранения в нашем объекте.

    Вместо того, чтобы помещать наш параметр $id непосредственно в строку SELECT , что увеличивает риск нарушения системы безопасности, мы используем:id . Такой параметр известен как placeholder (указатель места размещения) . Далее мы вызываем метод PDO для привязывания значение $id к указателю места размещения.

    Сразу после сохранения выражения SELECT в строке, мы подготавливаем его с помощью функции $conn->prepare() , сохраняя полученный дескриптор в переменной $st .

    Подготовка выражения используется для работы со многими базами данных. Она позволяет выполнять запросы быстрее и безопаснее.

    Затем мы привязываем значение переменной $id (ID нужной статьи) к указателю места размещения:id с помощью вызова метода bindValue() .

    И вызываем метод execute() для выполнения запроса. После чего используем метод fetch() для перемещения полученной записи в ассоциированный массив с именами полей и соответствующими значениями, который хранится в переменной $row .

    Закрываем соединение

    Так как нам больше не нужно соединение, мы закрываем его, присваивая значение null переменной $conn . Закрывать соединение с базой данных как можно быстрее является хорошей практикой для освобождения памяти на сервере.

    Возвращаем объект Article

    If ($row) return new Article($row); }

    Последним действием в нашем методе является создание объекта Article , который будет содержать запись из базы данных, и возвращение его вызывающему коду. Сначала проверяем наличие данных в переменной $row после вызова метода fetch() . Если данные есть, создаем новый объект Article передавая переменную ему $row . Будет вызван конструктор класса, который наполнит объект данными из массива $row . Затем возвращаем готовый объект и работа метода завершена.

5. getList()

Следующий метод getList() во многом похож на метод getById() . Основное отличие заключается в том, что метод getList() возвращает несколько статей сразу. Его используют, когда нужно вывести список статей для пользователя или администратора.

getList() принимает 2 аргумента:

$numRows Максимальное количество получаемых статей. По умолчанию установлено значение 1,000,000 (то есть, практически все статьи). Данный параметр позволяет нам получать только первые 5 статей для главной страницы. $order Порядок сортировки получаемых статей. По умолчанию используется параметр "publicationDate DESC" , который означает "сортировка по дате публикации, новые статьи первые".

Большая часть кода метода похожа на код метода getById() . Посмотрим на несколько строк:

$sql = "SELECT SQL_CALC_FOUND_ROWS *, UNIX_TIMESTAMP(publicationDate) AS publicationDate FROM articles ORDER BY " . mysql_escape_string($order) . " LIMIT:numRows";

Здесь запрос немного сложнее. Обратите внимание, что здесь не используется выражение WHERE , так как мы хотим получить все статьи, а не одну с заданным ID.

Также добавлено выражение ORDER BY для сортировки возвращаемых записей в определенном порядке. Используется выражение LIMIT с параметром $numRows (как указатель места размещения) для ограничения количества получаемых записей.

Специальное значение MySQL SQL_CALC_FOUND_ROWS указывает базе данных, что нужно вернуть действительное количество возвращаемых записей. Такая информация полезна для информирования пользователя и организации других функций, например, постраничного вывода списка.

Вместо передачи значения переменной $order в запрос через указатель места размещения, мы передаем его прямо в строку запроса, вызывая функцию mysql_escape_string() , чтобы отбросить любые специальные символы (для безопасности). Если использовать указатель места размещения, то PDO поместит кавычки (") вокруг строки (например, ORDER BY "publicationDate DESC"), что является ошибкой синтаксиса.

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

Так как мы возвращаем несколько строк, нужно создать массив $list для размещения соответствующих объектов Article . Затем используем цикл while для получения следующей строки через fetch() , создаем новый объект Article , сохраняем строку в объекте и добавляем объект к массиву $list . Когда строк не останется, метод fetch() вернет false , и цикл остановится.

// Теперь получаем общее число статей, которые соответствуют критерию $sql = "SELECT FOUND_ROWS() AS totalRows"; $totalRows = $conn->query($sql)->fetch(); $conn = null; return (array ("results" => $list, "totalRows" => $totalRows));

В завершении мы выполняем запрос, который использует функцию MySQL FOUND_ROWS() для получения количества возвращаемых строк, вычисленного в предыдущей команде SQL_CALC_FOUND_ROWS . В этот раз используем метод PDO query() , который позволяет быстро выполнить запрос, если нет указателей места замещения. Мы вызываем метод fetch() для получения результата. Затем возвращаем оба значения - список объектов Article ($list) и общее количество строк - как ассоциированный массив.

6. insert()

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

insert() добавляет новую статью в таблицу articles , используя значения из текущего объекта Article:

  • Сначала метод проверяет, что объект не имеет установленного свойства $id . Если у объекта есть ID, то, вероятно, статья уже имеется в базе данных и ее добавлять не нужно.
  • Затем метод выполняет запрос SQL INSERT для вставки записи в таблицу articles , используя указатели места замещения для передачи значений свойств в базу данных. Обратите внимание, что мы используем функцию MySQL FROM_UNIXTIME() для конвертации даты публикации в формат MySQL.
  • После выполнения запроса, метод возвращает ID новой статьи с помощью функции PDO lastInsertId() и сохраняет значение в свойстве $id . Мы установили в таблице articles для поля id свойство auto_increment , поэтому MySQL генерирует уникальное значение ID для каждой новой записи.

Обратите внимание, что мы используем PDO::PARAM_INT при привязке целых значений к указателям места замещения, и PDO::PARAM_STR при привязке строк. Таким образом, PDO может правильно обрабатывать значения.

7. update ()

Данный метод похож на метод insert() , за исключением того, что здесь происходит обновление записи в базе данных вместо создания новой записи.

Сначала проверяем наличие ID у объекта, так как обновить можно только запись с известным ID. Затем используем выражение SQL UPDATE для обновления полей записи. Обратите внимание на передачу ID объекта в выражение UPDATE , так как мы знаем, какую запись надо обновить.

8. delete ()

Метод delete() использует выражение SQL DELETE для удаления из таблицы articles статьи, которая хранится в объекте. Для идентификации записи задействуем свойство $id объекта. Для безопасности мы добавили LIMIT 1 к запросу, чтобы ограничиться удалением только одной записи.

В следующем уроке мы создадим скрипты для клиентской и серверной части нашего приложения.

Начиная с третьей версии wordpress предоставляет в наше распоряжение такой любопытный инструмент как произвольные типы записей (они же пользовательские, они же таксономии). Да, автор в курсе, что об этом написано уже немало строк в том числе и на русском языке, однако, столкнувшись с таксономиями в реальности, не всегда можно найти подробные примеры их использования (конечно, не считая скудного описания на официальном сайте wordpress). А посему… ну, как обычно, пишу, чтобы не забыть, ибо старые проекты, содержащие весь код, сохраняю редко.

Как создать?

Вообще, (вы удивитесь) wordpress уже содержит несколько примеров типов записей. Таковыми являются:

  • пост (post -> single.php)
  • страница (page -> page.php)
  • вложения они же медиафайлы (attachments)
  • редакции (revisions)
  • элементы меню (nav_menu_item)

Когда вы создаёте свой тип записи, он, как правило, базируется на каком-то уже существующем и наследует поведение встроенного типа. В примере создадим новый тип записи «Новости», взяв за основу обычный пост.

Подобные вещи пригодятся на сайтах, где нужна более разнообразная структура, чем предлагается по-умолчанию.

/* ** Custom types ** Icons for Type: ** https://developer.wordpress.org/resource/dashicons/ */ // News function create_news () { register_post_type ("news" , array ( "labels" => array ( "name" => __ ( "News" ), "singular_name" => __ ( "News" ), "add_new" => __ ( "Add news" ), "add_new_item" => __ ( "Add news item" ), "edit" => __ ( "Edit news" ), "edit_item" => __ ( "Edit news item" ), "new_item" => __ ( "Single news" ), "all_items" => __ ( "All news" ), "view" => __ ( "View news" ), "view_item" => __ ( "View single news" ), "search_items" => __ ( "Search news" ), "not_found" => __ ( "News not found" ), ), "public" => true , // show in admin panel? "menu_position" => 5 , "supports" => array ( "title" , "editor" , "thumbnail" , "excerpt" , "custom-fields" ), "taxonomies" => array ( "" ), "has_archive" => true , "capability_type" => "post" , "menu_icon" => "dashicons-admin-site" , "rewrite" => array ("slug" => "news" ), )); } add_action ( "init" , "create_news" );

Что сделали? Зарегистрировали новый тип записи с именем news. В массиве он принимает возможные значения, определяющие то, как будут выводиться названия тех или иных действий при редактировании новостей в административной панели. При этом использовали названия, учитывающие локализацию. Если вы делаете шаблон, не предназначенный для «мультиязычной» аудитории, можно ограничиться наименованиями на русском.

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

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

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

Ну, и наконец пути к нашим новостям будут иметь адрес вида mydomain.com/news/my-first-news . Отлично. Инициализируем новый тип записи при помощи add_action и топаем в админку: обновлять роутинг (сохранить изменения), иначе wordpress не сможет найти страницы с news .

Кстати, google требует, чтобы новости оканчивались не красивым названием, а ID. Поэтому иногда нужно знать как переопределить стандартные пути к ним. Сделать это можно в functions.php вот таким хитрым образом:

/* Change links for news post type */ function news_permalink ($permalink , $post = 0 ){ if ( $post -> post_type == "news" ){ return $permalink . $post -> ID . "/" ; } else { return $permalink ; } } add_filter ("post_type_link" , "news_permalink" , 1 , 3 ); function news_rewrite_init (){ add_rewrite_rule ( "news/.+?/(+)?$" , "index.php?post_type=news&p=$matches" , "top" ); } add_action ( "init" , "news_rewrite_init" );

Как добавить дополнительную информацию?

Иногда помимо добавления самого типа записи требуется расширить его представление в административной панели. К примеру, надо вывести информацию об оригинальном источнике новости. В таком случае придётся обратиться к неким мистическим сущностям wordpress, название которых слабо переводится на русский, (и потому нет желания его коверкать, придумывая неологизмы) это metaboxes.

Что всё это значит:

  • links_meta -> идентификатор
  • Source -> название блока
  • links_meta_callback -> обратный вызов
  • news -> к какому типу записи применить
  • advanced/normal -> в каком месте вывести
  • default/height -> приоритет в контексте других блоков

Какие поля будет включать блок? Обратимся к функции обратного вызова.

Original news link:

" ); wp_nonce_field ( basename (__FILE__ ), "links_nonce" ); $links_stored_meta = get_post_meta ( $post -> ID ); ?>


" />

Проиллюстрируем сказанное:

Наконец, научим metabox запоминать ту информацию, которая была записана в поле.

/** * Saves the custom meta input */ function links_meta_save ( $post_id ) { // Checks save status $is_autosave = wp_is_post_autosave ( $post_id ); $is_revision = wp_is_post_revision ( $post_id ); $is_valid_nonce = ( isset ( $_POST [ "links_nonce" ] ) && wp_verify_nonce ( $_POST [ "links_nonce" ], basename ( __FILE__ ) ) ) ? "true" : "false" ; // Exits script depending on save status if ( $is_autosave || $is_revision || ! $is_valid_nonce ) { return ; } // Checks for input and sanitizes/saves if needed if ( isset ( $_POST [ "links-news-original" ] ) ) { update_post_meta ( $post_id , "links-news-original" , sanitize_text_field ( $_POST [ "links-news-original" ] ) ); } } add_action ( "save_post" , "links_meta_save" );

Как вывести на странице?

Есть два варианта: сделать шаблон по правилам wordpress или же вывести на странице с шаблоном собственным.

Для шаблона, который будет выводить каждую отдельную запись, создадим страницу с именем single-news.php и примерно таким содержанием:

// Retrieves the stored value from the database $meta_url = get_post_meta ( get_the_ID (), "links-news-original" , true ); ?>

Может так случиться, что для особого типа записи надо будет выводить свой шаблон. Зачем? А там другое оформление или даже разметка. Тогда записываем ещё одну вещь: проверим тип записи, и если он совпадает с нужным, пусть движок использует страницу, отличную от стандартной. Сделаем это для страницы архива, которая не воспринимает указание шаблона а-ля archive-news .

is_search && $post_type == "news" ) { return locate_template ("archive-news" ); } return $template ; } add_filter ("template_include" , "template_news" );

Записи в таком шаблоне выводятся как обычно, без дополнительных запросов с wp_query.

Каждую страницу которая заканчивается на / можно открыть и через /index.php. Думаю, каждый более-менее уважаемый вебмастер это знает и вдаваться в подробности не стоит. Мы думаем, что это не сильная проблема как для людей, так и для поисковых роботов. Но все же, что-бы было на 100% правильно, давайте сделаем так, что если страница открывается через слэш, то ее нельзя было открыть через /index.php или /index.html. Естественно, везде должен отадаваться 301 редирект. чтобы ранее проставленные ссылки не теряли свой вес.


Результат работы скрипта

Как забирать окончание /index.php в Битриксе

Битрикс самая популярная коммерческая CMS, да и мы ее используем, поэтому давайте рассмотрим пример на ней. Многие вебмастера пытаются решить подобную задачу через php, записав код в init.php. но тут возникает 2 ошибки:

  • Редирект не будет работать при композите, потому что композит не грузит ядро битрикса;
  • Большая нагрузка на сервер, т.к. запускается Apache для редиректа.

Вообщем, правильно делать такой редирект исключительно через.htaccess файл. Откроем файл.htaccess, который лежит в корне битрикса и просто добавим 2 строчки кода. RewriteBase / нужно дописать после RewriteEngine On, а в конец всех правил добавим RewriteRule ^(.*)index\.php$ $1 . Естественно, все в конструкции должно лежать. Код:

Options +FollowSymLinks RewriteEngine On #Добавил RewriteBase / RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-l RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !/bitrix/urlrewrite.php$ RewriteRule ^(.*)$ /bitrix/urlrewrite.php [L] RewriteRule .* - #Добавил RewriteRule ^(.*)index\.php$ $1

Забирать окончание /index.php через.htaccess

Если у вас не битрикс, то в корне сайта в.htaccess файле (если его нет, то создайте) нужно добавить такой код:

RewriteEngine On RewriteBase / RewriteRule ^(.*)index\.php$ $1

Надеюсь вы решили вашу задачу!

And those on Linux Systems can use LAMP or MAMP for Mac OS users. Another way to have all this is to have a virtual web server. You can install one yourself using VMware or Oracle VM VirtualBox. For those who are advanced in web servers stuffs, I believe Vagrant will be the best for you.

At the end of the day, what matters is for you to have a running server that has php and a database that you can connect to.

Since PHP 5.5.0, the mysql extension is deprecated I will be using PDO. So if you don"t have PDO extension activated you can do it now. Or if could also use Mysqli instead. I have decided to use PDO to make the tutorial more universal because in the first version of it, many people had problems with mysql and were encounters a lot of errors.

Structure of the site

Here I will be creating a very simple website that will display a list of our recent news posts ordered by date.

For each post we"ll be displaying its title, short description, the date of publication and the author"s name. The title will be a URL that links us to another page that will display the full content of the news(See image bellow).

On the page displays the full artile, we"ll also list other articles so that the user can easily read another news without going page to the home page. We will also provide a link to go back to the home page.

Database model and structure of tables

Create a new database and name it news or something else. We will at this level have only one table in our database. The table will contain all our news. I named the table info_news ; you can name anything you want what matters is the structures.

So create your database and create following table in it.

  • Table structure for table info_news
CREATE TABLE IF NOT EXISTS "info_news" ("news_id" int(11) NOT NULL AUTO_INCREMENT, "news_title" varchar(255) NOT NULL, "news_short_description" text NOT NULL, "news_full_content" text NOT NULL, "news_author" varchar(120) NOT NULL, "news_published_on" int(46) NOT NULL, PRIMARY KEY ("news_id")) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

This is the table"s model:

You may be wondering how I got the structure of this table. The helped me understand different information I need on each news article

Files & folders structure

For this project I will be using two main files: index.php and read-news.php .

Since I want to make the project as simple as possible just to show you the concept, I won"t be using a or adopt an design pattern.

But, I want to keep my code clean; so I will be using a different file that will contain my database connection (dbconnect.php ) and another file(functions.php ) that will contain some set of functions we will create and use them in the project.

At the end, our folder structure should be like follow:

  • News/
    • config/
      • dbconnect.php
    • includes/
      • functions.php
    • design/
      • style.css
  • index.php
  • read-news.php

Check out my own folder bellow:

Files content (codes)

To start, we"ll require the db.connect.php in functions.php because we will the database instance in our functions. Then in index.php and read-news.php we"ll require the functions.php because we will need those functions in them.

  • File config/dbconnect.php

setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); return $pdo; } catch (PDOException $e){ $e->getMessage(); } }

  • File includes/functions.php

This file contains all function we will need in the project. The number of functions may increase as the project grows.

prepare(" SELECT news_id, news_title, news_short_description, news_author, news_published_on FROM info_news ORDER BY news_published_on DESC "); return $request->execute() ? $request->fetchAll() : false; } function getAnArticle($id_article, $conn) { $request = $conn->prepare(" SELECT news_id, news_title, news_full_content, news_author, news_published_on FROM info_news WHERE news_id = ? "); return $request->execute(array($id_article)) ? $request->fetchAll() : false; } function getOtherArticles($differ_id, $conn) { $request = $conn->prepare(" SELECT news_id, news_title, news_short_description, news_full_content, news_author, news_published_on FROM info_news WHERE news_id != ? "); return $request->execute(array($differ_id)) ? $request->fetchAll() : false; }

  • File index.php without PHP

Welcome to news channel

Latest news

First news title here

Second news title here

This news short description will be displayed at this particular place. This news short description will be displayed at this particular place.

published on Jan, 12th 2015 by zooboole

Thirst news title here

This news short description will be displayed at this particular place. This news short description will be displayed at this particular place.

published on Jan, 12th 2015 by zooboole

Fourth news title here

This news short description will be displayed at this particular place. This news short description will be displayed at this particular place.?= date("Y") ?> - all rights reserved.

Note: Each news has a specific URL that links it to the read-news.php page like this:

News title

where x is a number

The x represent the unique id of that particular article. So the read-news.php?newsid=x tells the read-news.php page to display a news that has the id x .

Now in this file we want the news to be fetched and displayed from the database dynamically. Let call the function fetchNews() . To do that let"s replace every thing in

...

by the following:

$article) :?>

news_id ?>">news_title) ?>

news_short_description) ?>

published on news_published_on) ?> by news_author) ?>

  • File read-news.php

Welcome to news channel

Latest news

Welcome to the demo news site. We never stop until you are aware.

return to home page
0) { // Fecth news $article = getAnArticle($id_article, $dbh); $article = $article; }else{ $article = false; echo "Wrong article!"; } $other_articles = getOtherArticles($id_article, $dbh); ?>

news_title) ?>

published on news_published_on) ?> by news_author) ?>
news_full_content) ?>

Other articles

$article) :?>

news_id ?>">news_title) ?>

news_short_description) ?>

published on news_published_on) ?> by news_author) ?> - all rights reserved.

  • The file design/style.css

Html, body { font-family: verdana; font-size: 16px; font-size: 100%; font-size: 1em; height: 100%; width: 100%; margin: 0; padding: 0; background-color: #4DDEDF; } * { box-sizing: border-box; } a{ text-decoration: none; color: #4DDED0; } .welcome { width: 800px; margin: 2em auto; padding: 10px 30px; background-color: #ffffff; } .welcome a { display: inline-block; width: 200px; border: 2px solid #0DDED0; padding: 0.5em; text-align: center; } .welcome h1 { margin: 0; color: #555; } .news-box { width: 800px; margin: 0.5em auto; padding: 30px; background-color: #ffffff; } .news-box h2 { font-size: 1.3em; padding: 0; margin-bottom: 0; color: #e45; } .news-box p { font-size: 12px; padding: 0; margin-bottom: 0.3em; color: #555; } .news-box span { font-size: 10px; color: #aaa; } .footer { font-size: 10px; color: #333; text-align: center; width: 800px; margin: 2em auto; padding: 10px 30px; }

Room for improvement

Indeed, this is a very basic way of making a news website. It doesn"t have any professional aspect like serious news sites do. But, we should know that this is a great basement to start with. The point here is mostly to show you how to retrieve data from a database and display it.

So, to make this tutorial complete, these are some functionalities one could add:

  • an admin panel to manage news (add, edit, delete,etc),
  • categorize your news,
  • inhence the design,
  • add comments system under each article we are reading,
  • news scheduling

There is lot one can do to make such system complete. It"s left with you to decide of what to add or how you can use it for other goals.

Conclusion

Voila. We are at the end of this little tutorial. We have created a simple news system that displays a list of articles and when we click on an article"s title we are taken to a reading page that uses the article"s id to retrieve dynamically the whole content of that article.

It"s a very simple system, but with it you can improve your skills in PHP/MYSQL applications. It was also an opportunity to introduce a bit how to use PDO.

So, if you have any question or you are meeting some errors, just comment down here. Check out the , and you can also file of the source code with comments



Есть вопросы?

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: