Add Project-Pro Blog module with initial SQL setup, CSS styles, and template files
- Created SQL installation scripts for categories and posts tables. - Added uninstall scripts to drop the created tables. - Introduced CSS styles for blog layout, including responsive design for posts and categories. - Implemented PHP redirection in index files to prevent direct access. - Developed Smarty templates for blog category tree, post list, and individual post details. - Ensured proper caching headers in PHP files to enhance performance.
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
/**
|
||||
* Project-Pro Blog — Admin Categories Controller
|
||||
*
|
||||
* @author Project-Pro <https://www.project-pro.pl>
|
||||
* @copyright 2024 Project-Pro
|
||||
*/
|
||||
|
||||
if (!defined('_PS_VERSION_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once _PS_MODULE_DIR_ . 'projectproblog/classes/BlogCategory.php';
|
||||
|
||||
class AdminProjectproBlogCategoriesController extends ModuleAdminController
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->bootstrap = true;
|
||||
$this->table = 'projectproblog_category';
|
||||
$this->className = 'BlogCategory';
|
||||
$this->lang = true;
|
||||
$this->identifier = 'id_category';
|
||||
|
||||
$this->_defaultOrderBy = 'position';
|
||||
$this->_defaultOrderWay = 'ASC';
|
||||
|
||||
parent::__construct();
|
||||
|
||||
// JOIN musi być po parent::__construct() — wtedy $this->context jest już dostępny
|
||||
$this->_join =
|
||||
'LEFT JOIN `' . _DB_PREFIX_ . 'projectproblog_category_lang` parent_lang
|
||||
ON (a.`id_parent` = parent_lang.`id_category`
|
||||
AND parent_lang.`id_lang` = ' . (int) $this->context->language->id . ')';
|
||||
|
||||
$this->_select = 'parent_lang.`name` AS parent_name';
|
||||
|
||||
$this->addRowAction('edit');
|
||||
$this->addRowAction('delete');
|
||||
|
||||
/* -------------------------------------------------------- */
|
||||
/* Lista */
|
||||
/* -------------------------------------------------------- */
|
||||
$this->fields_list = [
|
||||
'id_category' => [
|
||||
'title' => $this->l('ID'),
|
||||
'align' => 'center',
|
||||
'class' => 'fixed-width-xs',
|
||||
],
|
||||
'name' => [
|
||||
'title' => $this->l('Nazwa'),
|
||||
'filter_key' => 'b!name',
|
||||
],
|
||||
'parent_name' => [
|
||||
'title' => $this->l('Kategoria nadrzędna'),
|
||||
'search' => false,
|
||||
],
|
||||
'position' => [
|
||||
'title' => $this->l('Pozycja'),
|
||||
'align' => 'center',
|
||||
'class' => 'fixed-width-xs',
|
||||
],
|
||||
'active' => [
|
||||
'title' => $this->l('Aktywna'),
|
||||
'active' => 'status',
|
||||
'type' => 'bool',
|
||||
'align' => 'center',
|
||||
'class' => 'fixed-width-sm',
|
||||
],
|
||||
];
|
||||
|
||||
/* -------------------------------------------------------- */
|
||||
/* Formularz */
|
||||
/* -------------------------------------------------------- */
|
||||
$this->fields_form = [
|
||||
'legend' => [
|
||||
'title' => $this->l('Kategoria bloga'),
|
||||
'icon' => 'icon-folder',
|
||||
],
|
||||
'input' => [
|
||||
[
|
||||
'type' => 'select',
|
||||
'label' => $this->l('Kategoria nadrzędna'),
|
||||
'name' => 'id_parent',
|
||||
'options' => [
|
||||
'query' => BlogCategory::getCategoriesForSelect(
|
||||
$this->context->language->id,
|
||||
(int) Tools::getValue('id_category')
|
||||
),
|
||||
'id' => 'id_category',
|
||||
'name' => 'name',
|
||||
],
|
||||
'hint' => $this->l('Zostaw "brak" jeśli to kategoria główna.'),
|
||||
],
|
||||
[
|
||||
'type' => 'switch',
|
||||
'label' => $this->l('Aktywna'),
|
||||
'name' => 'active',
|
||||
'required' => false,
|
||||
'is_bool' => true,
|
||||
'values' => [
|
||||
['id' => 'active_on', 'value' => 1, 'label' => $this->l('Tak')],
|
||||
['id' => 'active_off', 'value' => 0, 'label' => $this->l('Nie')],
|
||||
],
|
||||
],
|
||||
[
|
||||
'type' => 'text',
|
||||
'label' => $this->l('Pozycja'),
|
||||
'name' => 'position',
|
||||
'class' => 'fixed-width-xs',
|
||||
'hint' => $this->l('Kolejność na liście (mniejsza liczba = wyżej).'),
|
||||
],
|
||||
[
|
||||
'type' => 'text',
|
||||
'label' => $this->l('Nazwa'),
|
||||
'name' => 'name',
|
||||
'lang' => true,
|
||||
'required' => true,
|
||||
'hint' => $this->l('Nazwa wyświetlana na stronie.'),
|
||||
],
|
||||
[
|
||||
'type' => 'text',
|
||||
'label' => $this->l('Przyjazny URL (slug)'),
|
||||
'name' => 'link_rewrite',
|
||||
'lang' => true,
|
||||
'required' => true,
|
||||
'hint' => $this->l('Tylko małe litery, cyfry i myślniki. Generowany automatycznie z nazwy.'),
|
||||
],
|
||||
[
|
||||
'type' => 'textarea',
|
||||
'label' => $this->l('Opis'),
|
||||
'name' => 'description',
|
||||
'lang' => true,
|
||||
'rows' => 5,
|
||||
],
|
||||
[
|
||||
'type' => 'text',
|
||||
'label' => $this->l('Meta title'),
|
||||
'name' => 'meta_title',
|
||||
'lang' => true,
|
||||
],
|
||||
[
|
||||
'type' => 'text',
|
||||
'label' => $this->l('Meta keywords'),
|
||||
'name' => 'meta_keywords',
|
||||
'lang' => true,
|
||||
'hint' => $this->l('Słowa kluczowe oddzielone przecinkami.'),
|
||||
],
|
||||
[
|
||||
'type' => 'textarea',
|
||||
'label' => $this->l('Meta description'),
|
||||
'name' => 'meta_description',
|
||||
'lang' => true,
|
||||
'rows' => 3,
|
||||
],
|
||||
],
|
||||
'submit' => [
|
||||
'title' => $this->l('Zapisz'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Zapis — auto-generowanie slug z nazwy */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
public function processSave()
|
||||
{
|
||||
$languages = Language::getLanguages(false);
|
||||
|
||||
foreach ($languages as $lang) {
|
||||
$idLang = (int) $lang['id_lang'];
|
||||
$name = Tools::getValue('name_' . $idLang);
|
||||
$slug = Tools::getValue('link_rewrite_' . $idLang);
|
||||
|
||||
// jeśli slug pusty — wygeneruj z nazwy
|
||||
if (empty(trim((string) $slug)) && !empty(trim((string) $name))) {
|
||||
$_POST['link_rewrite_' . $idLang] = Tools::link_rewrite($name);
|
||||
}
|
||||
}
|
||||
|
||||
return parent::processSave();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Toggle active (kliknięcie boolki na liście) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
public function processStatus()
|
||||
{
|
||||
$cat = new BlogCategory((int) Tools::getValue($this->identifier));
|
||||
if (!Validate::isLoadedObject($cat)) {
|
||||
$this->errors[] = $this->l('Nie znaleziono kategorii.');
|
||||
return false;
|
||||
}
|
||||
|
||||
$cat->active = !(bool) $cat->active;
|
||||
|
||||
return (bool) $cat->update();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
<?php
|
||||
/**
|
||||
* Project-Pro Blog — Admin Posts Controller
|
||||
*
|
||||
* @author Project-Pro <https://www.project-pro.pl>
|
||||
* @copyright 2024 Project-Pro
|
||||
*/
|
||||
|
||||
if (!defined('_PS_VERSION_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once _PS_MODULE_DIR_ . 'projectproblog/classes/BlogPost.php';
|
||||
require_once _PS_MODULE_DIR_ . 'projectproblog/classes/BlogCategory.php';
|
||||
|
||||
class AdminProjectproBlogPostsController extends ModuleAdminController
|
||||
{
|
||||
/** Katalog przechowywania miniaturek */
|
||||
const IMG_DIR = 'projectproblog/views/img/posts/';
|
||||
|
||||
/** Flaga zapobiegająca podwójnemu wstrzyknięciu pól do formularza */
|
||||
private $formEnriched = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->bootstrap = true;
|
||||
$this->table = 'projectproblog_post';
|
||||
$this->className = 'BlogPost';
|
||||
$this->lang = true;
|
||||
$this->identifier = 'id_post';
|
||||
|
||||
$this->_defaultOrderBy = 'date_add';
|
||||
$this->_defaultOrderWay = 'DESC';
|
||||
|
||||
parent::__construct();
|
||||
|
||||
$this->addRowAction('edit');
|
||||
$this->addRowAction('delete');
|
||||
|
||||
/* -------------------------------------------------------- */
|
||||
/* Lista */
|
||||
/* -------------------------------------------------------- */
|
||||
$this->fields_list = [
|
||||
'id_post' => [
|
||||
'title' => $this->l('ID'),
|
||||
'align' => 'center',
|
||||
'class' => 'fixed-width-xs',
|
||||
],
|
||||
'title' => [
|
||||
'title' => $this->l('Tytuł'),
|
||||
'filter_key' => 'b!title',
|
||||
],
|
||||
'date_add' => [
|
||||
'title' => $this->l('Data dodania'),
|
||||
'type' => 'datetime',
|
||||
'align' => 'center',
|
||||
],
|
||||
'active' => [
|
||||
'title' => $this->l('Aktywny'),
|
||||
'active' => 'status',
|
||||
'type' => 'bool',
|
||||
'align' => 'center',
|
||||
'class' => 'fixed-width-sm',
|
||||
],
|
||||
];
|
||||
|
||||
/* -------------------------------------------------------- */
|
||||
/* Formularz — pola statyczne */
|
||||
/* (miniaturka i kategorie dołączane dynamicznie */
|
||||
/* w renderForm() żeby obsłużyć podgląd i zaznaczenia) */
|
||||
/* -------------------------------------------------------- */
|
||||
$this->fields_form = [
|
||||
'legend' => [
|
||||
'title' => $this->l('Wpis bloga'),
|
||||
'icon' => 'icon-edit',
|
||||
],
|
||||
'input' => [
|
||||
[
|
||||
'type' => 'switch',
|
||||
'label' => $this->l('Aktywny'),
|
||||
'name' => 'active',
|
||||
'required' => false,
|
||||
'is_bool' => true,
|
||||
'values' => [
|
||||
['id' => 'active_on', 'value' => 1, 'label' => $this->l('Tak')],
|
||||
['id' => 'active_off', 'value' => 0, 'label' => $this->l('Nie')],
|
||||
],
|
||||
],
|
||||
[
|
||||
'type' => 'text',
|
||||
'label' => $this->l('Tytuł'),
|
||||
'name' => 'title',
|
||||
'lang' => true,
|
||||
'required' => true,
|
||||
'col' => 6,
|
||||
],
|
||||
[
|
||||
'type' => 'text',
|
||||
'label' => $this->l('Przyjazny URL (slug)'),
|
||||
'name' => 'link_rewrite',
|
||||
'lang' => true,
|
||||
'required' => true,
|
||||
'col' => 6,
|
||||
'hint' => $this->l('Tylko małe litery, cyfry i myślniki. Generowany automatycznie z tytułu.'),
|
||||
],
|
||||
[
|
||||
'type' => 'textarea',
|
||||
'label' => $this->l('Wstęp'),
|
||||
'name' => 'intro',
|
||||
'lang' => true,
|
||||
'rows' => 5,
|
||||
'autoload_rte' => true,
|
||||
'hint' => $this->l('Krótki opis wyświetlany na liście wpisów.'),
|
||||
],
|
||||
[
|
||||
'type' => 'textarea',
|
||||
'label' => $this->l('Treść'),
|
||||
'name' => 'content',
|
||||
'lang' => true,
|
||||
'rows' => 20,
|
||||
'autoload_rte' => true,
|
||||
],
|
||||
[
|
||||
'type' => 'text',
|
||||
'label' => $this->l('Meta title'),
|
||||
'name' => 'meta_title',
|
||||
'lang' => true,
|
||||
'col' => 6,
|
||||
],
|
||||
[
|
||||
'type' => 'text',
|
||||
'label' => $this->l('Meta keywords'),
|
||||
'name' => 'meta_keywords',
|
||||
'lang' => true,
|
||||
'col' => 6,
|
||||
'hint' => $this->l('Słowa kluczowe oddzielone przecinkami.'),
|
||||
],
|
||||
[
|
||||
'type' => 'textarea',
|
||||
'label' => $this->l('Meta description'),
|
||||
'name' => 'meta_description',
|
||||
'lang' => true,
|
||||
'rows' => 3,
|
||||
],
|
||||
// miniaturka i kategorie → renderForm()
|
||||
],
|
||||
'submit' => [
|
||||
'title' => $this->l('Zapisz'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Formularz — dynamiczne pola: miniaturka + kategorie */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
public function renderForm()
|
||||
{
|
||||
// Ochrona przed podwójnym wstrzyknięciem (np. przy re-renderze po błędzie walidacji)
|
||||
if ($this->formEnriched) {
|
||||
return parent::renderForm();
|
||||
}
|
||||
$this->formEnriched = true;
|
||||
|
||||
$idPost = (int) Tools::getValue('id_post');
|
||||
$existingThumbnail = null;
|
||||
$selectedCategories = [];
|
||||
|
||||
if ($idPost) {
|
||||
$post = new BlogPost($idPost);
|
||||
if (Validate::isLoadedObject($post)) {
|
||||
$existingThumbnail = $post->thumbnail;
|
||||
$selectedCategories = array_map('intval', BlogPost::getCategories($idPost));
|
||||
}
|
||||
}
|
||||
|
||||
// --- pole miniaturki ---
|
||||
$thumbnailHtml = '<div class="form-group">';
|
||||
$thumbnailHtml .= '<label class="control-label col-lg-3">' . $this->l('Miniaturka') . '</label>';
|
||||
$thumbnailHtml .= '<div class="col-lg-9">';
|
||||
|
||||
if ($existingThumbnail) {
|
||||
$url = Context::getContext()->link->getBaseLink()
|
||||
. 'modules/projectproblog/views/img/posts/'
|
||||
. $existingThumbnail;
|
||||
$thumbnailHtml .= '<div style="margin-bottom:10px;">'
|
||||
. '<img src="' . $url . '" style="max-height:150px;max-width:300px;border:1px solid #ddd;padding:4px;" />'
|
||||
. '</div>';
|
||||
}
|
||||
|
||||
$thumbnailHtml .= '<input type="file" name="thumbnail" id="thumbnail_upload" '
|
||||
. 'accept="image/jpeg,image/png,image/webp,image/gif" class="form-control-file">';
|
||||
$thumbnailHtml .= '<p class="help-block">' . $this->l('Dozwolone formaty: jpg, png, webp, gif.') . '</p>';
|
||||
$thumbnailHtml .= '</div></div>';
|
||||
|
||||
// --- pole kategorii ---
|
||||
$allCategories = Db::getInstance()->executeS(
|
||||
'SELECT c.`id_category`, cl.`name`
|
||||
FROM `' . _DB_PREFIX_ . 'projectproblog_category` c
|
||||
LEFT JOIN `' . _DB_PREFIX_ . 'projectproblog_category_lang` cl
|
||||
ON c.`id_category` = cl.`id_category`
|
||||
AND cl.`id_lang` = ' . (int) $this->context->language->id . '
|
||||
WHERE c.`active` = 1
|
||||
ORDER BY cl.`name` ASC'
|
||||
);
|
||||
|
||||
$categoriesHtml = '<div class="form-group">';
|
||||
$categoriesHtml .= '<label class="control-label col-lg-3">'
|
||||
. $this->l('Kategorie')
|
||||
. ' <span class="help-box" data-toggle="tooltip" title="'
|
||||
. $this->l('Wpis może być przypisany do wielu kategorii.') . '"></span>'
|
||||
. '</label>';
|
||||
$categoriesHtml .= '<div class="col-lg-9">';
|
||||
|
||||
if (!empty($allCategories)) {
|
||||
$categoriesHtml .= '<div style="max-height:200px;overflow-y:auto;border:1px solid #ddd;padding:10px;background:#fff;">';
|
||||
foreach ($allCategories as $cat) {
|
||||
$checked = in_array((int) $cat['id_category'], $selectedCategories) ? ' checked' : '';
|
||||
$categoriesHtml .= '<div class="checkbox" style="margin:4px 0;">'
|
||||
. '<label>'
|
||||
. '<input type="checkbox" name="id_category[]" value="' . (int) $cat['id_category'] . '"' . $checked . '> '
|
||||
. htmlspecialchars($cat['name'], ENT_QUOTES, 'UTF-8')
|
||||
. '</label>'
|
||||
. '</div>';
|
||||
}
|
||||
$categoriesHtml .= '</div>';
|
||||
} else {
|
||||
$categoriesHtml .= '<p class="text-muted">'
|
||||
. $this->l('Brak aktywnych kategorii. Dodaj kategorie w zakładce "Kategorie bloga".')
|
||||
. '</p>';
|
||||
}
|
||||
|
||||
$categoriesHtml .= '</div></div>';
|
||||
|
||||
// --- wstrzykiwanie do formularza ---
|
||||
$this->fields_form['input'][] = [
|
||||
'type' => 'html',
|
||||
'label' => '',
|
||||
'name' => 'thumbnail_field',
|
||||
'html_content' => $thumbnailHtml,
|
||||
];
|
||||
|
||||
$this->fields_form['input'][] = [
|
||||
'type' => 'html',
|
||||
'label' => '',
|
||||
'name' => 'categories_field',
|
||||
'html_content' => $categoriesHtml,
|
||||
];
|
||||
|
||||
return parent::renderForm();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Zapis */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
public function processSave()
|
||||
{
|
||||
$idPost = (int) Tools::getValue('id_post');
|
||||
$imgDir = _PS_MODULE_DIR_ . self::IMG_DIR;
|
||||
|
||||
// --- obsługa miniaturki ---
|
||||
if (!empty($_FILES['thumbnail']['name']) && $_FILES['thumbnail']['error'] === UPLOAD_ERR_OK) {
|
||||
|
||||
if (!is_dir($imgDir)) {
|
||||
@mkdir($imgDir, 0755, true);
|
||||
}
|
||||
|
||||
$ext = strtolower(pathinfo($_FILES['thumbnail']['name'], PATHINFO_EXTENSION));
|
||||
$allowed = ['jpg', 'jpeg', 'png', 'webp', 'gif'];
|
||||
|
||||
if (!in_array($ext, $allowed)) {
|
||||
$this->errors[] = $this->l('Niedozwolony format pliku. Dozwolone: jpg, png, webp, gif.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (@getimagesize($_FILES['thumbnail']['tmp_name']) === false) {
|
||||
$this->errors[] = $this->l('Przesłany plik nie jest prawidłowym obrazem.');
|
||||
return false;
|
||||
}
|
||||
|
||||
$filename = md5(uniqid('ppb', true)) . '.' . $ext;
|
||||
|
||||
if (!move_uploaded_file($_FILES['thumbnail']['tmp_name'], $imgDir . $filename)) {
|
||||
$this->errors[] = $this->l('Błąd podczas przesyłania pliku. Sprawdź uprawnienia katalogu.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// usuń starą miniaturkę przy edycji
|
||||
if ($idPost) {
|
||||
$old = new BlogPost($idPost);
|
||||
if (Validate::isLoadedObject($old) && $old->thumbnail) {
|
||||
$oldFile = $imgDir . $old->thumbnail;
|
||||
if (file_exists($oldFile)) {
|
||||
@unlink($oldFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$_POST['thumbnail'] = $filename;
|
||||
|
||||
} else {
|
||||
// brak nowego pliku — zachowaj istniejącą miniaturkę
|
||||
if ($idPost) {
|
||||
$existing = new BlogPost($idPost);
|
||||
if (Validate::isLoadedObject($existing) && $existing->thumbnail) {
|
||||
$_POST['thumbnail'] = $existing->thumbnail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- auto-generowanie slugów z tytułu ---
|
||||
foreach (Language::getLanguages(false) as $lang) {
|
||||
$lid = (int) $lang['id_lang'];
|
||||
$title = trim((string) Tools::getValue('title_' . $lid));
|
||||
$slug = trim((string) Tools::getValue('link_rewrite_' . $lid));
|
||||
|
||||
if ($slug === '' && $title !== '') {
|
||||
$_POST['link_rewrite_' . $lid] = Tools::link_rewrite($title);
|
||||
}
|
||||
}
|
||||
|
||||
// --- zapis przez parent (ObjectModel) ---
|
||||
$result = parent::processSave();
|
||||
|
||||
// --- zapis kategorii ---
|
||||
if ($result !== false && isset($this->object) && Validate::isLoadedObject($this->object)) {
|
||||
$categoryIds = Tools::getValue('id_category', []);
|
||||
if (!is_array($categoryIds)) {
|
||||
$categoryIds = [];
|
||||
}
|
||||
BlogPost::setCategories((int) $this->object->id, $categoryIds);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Toggle aktywności z listy */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
public function processStatus()
|
||||
{
|
||||
$post = new BlogPost((int) Tools::getValue($this->identifier));
|
||||
|
||||
if (!Validate::isLoadedObject($post)) {
|
||||
$this->errors[] = $this->l('Nie znaleziono wpisu.');
|
||||
return false;
|
||||
}
|
||||
|
||||
$post->active = !(bool) $post->active;
|
||||
|
||||
return (bool) $post->update();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Usunięcie — thumbnail i kategorie obsługuje BlogPost::delete() */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
public function processDelete()
|
||||
{
|
||||
$post = new BlogPost((int) Tools::getValue($this->identifier));
|
||||
|
||||
if (!Validate::isLoadedObject($post)) {
|
||||
$this->errors[] = $this->l('Nie znaleziono wpisu.');
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = $post->delete();
|
||||
|
||||
if (!$result) {
|
||||
$this->errors[] = $this->l('Wystąpił błąd podczas usuwania wpisu.');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
8
modules/projectproblog/controllers/admin/index.php
Normal file
8
modules/projectproblog/controllers/admin/index.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
|
||||
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
|
||||
header("Cache-Control: no-store, no-cache, must-revalidate");
|
||||
header("Cache-Control: post-check=0, pre-check=0", false);
|
||||
header("Pragma: no-cache");
|
||||
header("Location: ../");
|
||||
exit;
|
||||
8
modules/projectproblog/controllers/front/index.php
Normal file
8
modules/projectproblog/controllers/front/index.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
|
||||
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
|
||||
header("Cache-Control: no-store, no-cache, must-revalidate");
|
||||
header("Cache-Control: post-check=0, pre-check=0", false);
|
||||
header("Pragma: no-cache");
|
||||
header("Location: ../");
|
||||
exit;
|
||||
252
modules/projectproblog/controllers/front/list.php
Normal file
252
modules/projectproblog/controllers/front/list.php
Normal file
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
/**
|
||||
* Project-Pro Blog — Front controller: lista wpisów
|
||||
*
|
||||
* @author Project-Pro <https://www.project-pro.pl>
|
||||
* @copyright 2024 Project-Pro
|
||||
*/
|
||||
|
||||
if (!defined('_PS_VERSION_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once _PS_MODULE_DIR_ . 'projectproblog/classes/BlogCategory.php';
|
||||
require_once _PS_MODULE_DIR_ . 'projectproblog/classes/BlogPost.php';
|
||||
|
||||
class ProjectproblogListModuleFrontController extends ModuleFrontController
|
||||
{
|
||||
public $php_self = 'list';
|
||||
|
||||
/** @var BlogCategory|null */
|
||||
protected $currentCategory = null;
|
||||
|
||||
/** @var string */
|
||||
protected $slug = '';
|
||||
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
|
||||
$idLang = (int) $this->context->language->id;
|
||||
$this->slug = Tools::getValue('slug', '');
|
||||
|
||||
// Jeśli podano slug kategorii — rozwiąż go
|
||||
if ($this->slug !== '') {
|
||||
$this->currentCategory = BlogCategory::getByLinkRewrite($this->slug, $idLang);
|
||||
if (!$this->currentCategory) {
|
||||
Tools::redirect('index.php?controller=404');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function initContent()
|
||||
{
|
||||
parent::initContent();
|
||||
|
||||
$this->registerStylesheet(
|
||||
'module-projectproblog-blog',
|
||||
'modules/projectproblog/views/css/blog.css',
|
||||
['media' => 'all', 'priority' => 200]
|
||||
);
|
||||
|
||||
$idLang = (int) $this->context->language->id;
|
||||
$page = max(1, (int) Tools::getValue('page', 1));
|
||||
$perPage = Projectproblog::POSTS_PER_PAGE;
|
||||
$idCategory = $this->currentCategory ? (int) $this->currentCategory->id : null;
|
||||
|
||||
// Wpisy
|
||||
$rawPosts = BlogPost::getList($idLang, $page, $perPage, $idCategory);
|
||||
$total = BlogPost::getCount($idLang, $idCategory);
|
||||
$pages = max(1, (int) ceil($total / $perPage));
|
||||
|
||||
// Wzbogac wpisy o URL-e
|
||||
$posts = $this->enrichPosts($rawPosts, $idLang);
|
||||
|
||||
// Paginacja
|
||||
$pagination = $this->buildPagination($page, $pages, $this->slug, $idLang);
|
||||
|
||||
// Drzewo kategorii z URL-ami
|
||||
$categoryTree = $this->enrichCategoryTree(
|
||||
BlogCategory::getCategoryTree($idLang),
|
||||
$idLang
|
||||
);
|
||||
|
||||
// Meta strony
|
||||
$this->setPageMeta($idLang);
|
||||
|
||||
$this->context->smarty->assign([
|
||||
'blog_posts' => $posts,
|
||||
'blog_category_tree' => $categoryTree,
|
||||
'blog_current_cat' => $this->currentCategory,
|
||||
'blog_pagination' => $pagination,
|
||||
'blog_page' => $page,
|
||||
'blog_pages_count' => $pages,
|
||||
'blog_total' => $total,
|
||||
'blog_url' => Projectproblog::getBlogUrl($idLang),
|
||||
]);
|
||||
|
||||
$this->setTemplate('module:projectproblog/views/templates/front/list.tpl');
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Breadcrumb */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
public function getBreadcrumbLinks()
|
||||
{
|
||||
$breadcrumb = parent::getBreadcrumbLinks();
|
||||
|
||||
$breadcrumb['links'][] = [
|
||||
'title' => $this->l('Blog'),
|
||||
'url' => Projectproblog::getBlogUrl(),
|
||||
];
|
||||
|
||||
if ($this->currentCategory) {
|
||||
$breadcrumb['links'][] = [
|
||||
'title' => $this->currentCategory->name,
|
||||
'url' => Projectproblog::getCategoryUrl($this->currentCategory->link_rewrite),
|
||||
];
|
||||
}
|
||||
|
||||
return $breadcrumb;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Canonical URL */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Wyłącza canonical redirection — PS generuje błędny fallback URL
|
||||
* (bez fc=module) gdy hookModuleRoutes nie załadował tras w Dispatcher.
|
||||
*/
|
||||
protected function canonicalRedirection($canonical_url = '')
|
||||
{
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Helpery prywatne */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Dodaje thumbnail_url i url do każdego wpisu.
|
||||
*/
|
||||
protected function enrichPosts(array $posts, $idLang)
|
||||
{
|
||||
$base = $this->context->link->getBaseLink();
|
||||
|
||||
foreach ($posts as &$post) {
|
||||
$post['thumbnail_url'] = $post['thumbnail']
|
||||
? $base . 'modules/projectproblog/views/img/posts/' . $post['thumbnail']
|
||||
: null;
|
||||
|
||||
$post['url'] = Projectproblog::getPostUrl($post['link_rewrite'], $idLang);
|
||||
}
|
||||
unset($post);
|
||||
|
||||
return $posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rekurencyjnie dodaje url do węzłów drzewa kategorii.
|
||||
*/
|
||||
protected function enrichCategoryTree(array $tree, $idLang)
|
||||
{
|
||||
foreach ($tree as &$node) {
|
||||
$node['category']['url'] = Projectproblog::getCategoryUrl(
|
||||
$node['category']['link_rewrite'],
|
||||
$idLang
|
||||
);
|
||||
$node['children'] = $this->enrichCategoryTree($node['children'], $idLang);
|
||||
}
|
||||
unset($node);
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Buduje tablicę linków paginacji.
|
||||
* Każdy element: ['page' => int, 'url' => string, 'current' => bool]
|
||||
*/
|
||||
protected function buildPagination($currentPage, $pagesCount, $slug, $idLang)
|
||||
{
|
||||
if ($pagesCount <= 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$links = [];
|
||||
|
||||
for ($i = 1; $i <= $pagesCount; $i++) {
|
||||
$params = [];
|
||||
if ($slug !== '') {
|
||||
$params['slug'] = $slug;
|
||||
}
|
||||
if ($i > 1) {
|
||||
$params['page'] = $i;
|
||||
}
|
||||
|
||||
$links[] = [
|
||||
'page' => $i,
|
||||
'url' => $this->context->link->getModuleLink(
|
||||
'projectproblog',
|
||||
'list',
|
||||
$params,
|
||||
true,
|
||||
$idLang
|
||||
),
|
||||
'current' => ($i === $currentPage),
|
||||
];
|
||||
}
|
||||
|
||||
// Linki poprzednia/następna
|
||||
$prev = null;
|
||||
$next = null;
|
||||
|
||||
if ($currentPage > 1) {
|
||||
$params = $slug !== '' ? ['slug' => $slug] : [];
|
||||
if ($currentPage - 1 > 1) {
|
||||
$params['page'] = $currentPage - 1;
|
||||
}
|
||||
$prev = $this->context->link->getModuleLink('projectproblog', 'list', $params, true, $idLang);
|
||||
}
|
||||
|
||||
if ($currentPage < $pagesCount) {
|
||||
$params = $slug !== '' ? ['slug' => $slug] : [];
|
||||
$params['page'] = $currentPage + 1;
|
||||
$next = $this->context->link->getModuleLink('projectproblog', 'list', $params, true, $idLang);
|
||||
}
|
||||
|
||||
return [
|
||||
'pages' => $links,
|
||||
'prev' => $prev,
|
||||
'next' => $next,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ustawia meta title/description strony listy.
|
||||
*/
|
||||
protected function setPageMeta($idLang)
|
||||
{
|
||||
if ($this->currentCategory) {
|
||||
$title = $this->currentCategory->meta_title ?: $this->currentCategory->name . ' — Blog';
|
||||
$desc = $this->currentCategory->meta_description ?: '';
|
||||
$kw = $this->currentCategory->meta_keywords ?: '';
|
||||
} else {
|
||||
$title = $this->l('Blog');
|
||||
$desc = '';
|
||||
$kw = '';
|
||||
}
|
||||
|
||||
$page = $this->context->smarty->getTemplateVars('page') ?: [];
|
||||
|
||||
if (!isset($page['meta'])) {
|
||||
$page['meta'] = [];
|
||||
}
|
||||
|
||||
$page['meta']['title'] = $title;
|
||||
$page['meta']['description'] = $desc;
|
||||
$page['meta']['keywords'] = $kw;
|
||||
|
||||
$this->context->smarty->assign('page', $page);
|
||||
}
|
||||
}
|
||||
225
modules/projectproblog/controllers/front/post.php
Normal file
225
modules/projectproblog/controllers/front/post.php
Normal file
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
/**
|
||||
* Project-Pro Blog — Front controller: szczegół wpisu
|
||||
*
|
||||
* @author Project-Pro <https://www.project-pro.pl>
|
||||
* @copyright 2024 Project-Pro
|
||||
*/
|
||||
|
||||
if (!defined('_PS_VERSION_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once _PS_MODULE_DIR_ . 'projectproblog/classes/BlogCategory.php';
|
||||
require_once _PS_MODULE_DIR_ . 'projectproblog/classes/BlogPost.php';
|
||||
|
||||
class ProjectproblogPostModuleFrontController extends ModuleFrontController
|
||||
{
|
||||
public $php_self = 'post';
|
||||
|
||||
/** @var BlogPost|null */
|
||||
protected $post = null;
|
||||
|
||||
/** @var string */
|
||||
protected $slug = '';
|
||||
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
|
||||
$idLang = (int) $this->context->language->id;
|
||||
$this->slug = Tools::getValue('slug', '');
|
||||
|
||||
if ($this->slug === '') {
|
||||
Tools::redirect('index.php?controller=404');
|
||||
}
|
||||
|
||||
$this->post = BlogPost::getByLinkRewrite($this->slug, $idLang);
|
||||
|
||||
if (!$this->post) {
|
||||
Tools::redirect('index.php?controller=404');
|
||||
}
|
||||
}
|
||||
|
||||
public function initContent()
|
||||
{
|
||||
parent::initContent();
|
||||
|
||||
$this->registerStylesheet(
|
||||
'module-projectproblog-blog',
|
||||
'modules/projectproblog/views/css/blog.css',
|
||||
['media' => 'all', 'priority' => 200]
|
||||
);
|
||||
|
||||
$idLang = (int) $this->context->language->id;
|
||||
|
||||
// Kategorie wpisu z URL-ami
|
||||
$categoryIds = BlogPost::getCategories((int) $this->post->id);
|
||||
$postCats = $this->loadCategories($categoryIds, $idLang);
|
||||
|
||||
// Pierwsza kategoria (do breadcrumba)
|
||||
$primaryCat = !empty($postCats) ? $postCats[0] : null;
|
||||
|
||||
// URL miniaturki
|
||||
$thumbnailUrl = $this->post->getThumbnailUrl();
|
||||
|
||||
// Powiązane data_upd — wyświetl tylko gdy różni się od data_add
|
||||
$showUpdated = $this->post->date_upd
|
||||
&& substr($this->post->date_upd, 0, 10) !== substr($this->post->date_add, 0, 10);
|
||||
|
||||
// Drzewo kategorii do sidebara
|
||||
$categoryTree = $this->enrichCategoryTree(
|
||||
BlogCategory::getCategoryTree($idLang),
|
||||
$idLang
|
||||
);
|
||||
|
||||
// Meta strony
|
||||
$this->setPageMeta();
|
||||
|
||||
$this->context->smarty->assign([
|
||||
'blog_post' => $this->post,
|
||||
'blog_post_cats' => $postCats,
|
||||
'blog_primary_cat' => $primaryCat,
|
||||
'blog_thumbnail' => $thumbnailUrl,
|
||||
'blog_show_updated' => $showUpdated,
|
||||
'blog_url' => Projectproblog::getBlogUrl($idLang),
|
||||
'blog_category_tree' => $categoryTree,
|
||||
'blog_current_cat' => null,
|
||||
]);
|
||||
|
||||
$this->setTemplate('module:projectproblog/views/templates/front/post.tpl');
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Breadcrumb */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
public function getBreadcrumbLinks()
|
||||
{
|
||||
$breadcrumb = parent::getBreadcrumbLinks();
|
||||
|
||||
$breadcrumb['links'][] = [
|
||||
'title' => $this->l('Blog'),
|
||||
'url' => Projectproblog::getBlogUrl(),
|
||||
];
|
||||
|
||||
// Jeśli wpis ma kategorię — dodaj ją do breadcrumba
|
||||
if ($this->post) {
|
||||
$idLang = (int) $this->context->language->id;
|
||||
$categoryIds = BlogPost::getCategories((int) $this->post->id);
|
||||
|
||||
if (!empty($categoryIds)) {
|
||||
$catRow = Db::getInstance()->getRow(
|
||||
'SELECT cl.`name`, cl.`link_rewrite`
|
||||
FROM `' . _DB_PREFIX_ . 'projectproblog_category_lang` cl
|
||||
WHERE cl.`id_category` = ' . (int) $categoryIds[0] . '
|
||||
AND cl.`id_lang` = ' . (int) $idLang
|
||||
);
|
||||
|
||||
if ($catRow) {
|
||||
$breadcrumb['links'][] = [
|
||||
'title' => $catRow['name'],
|
||||
'url' => Projectproblog::getCategoryUrl($catRow['link_rewrite']),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$breadcrumb['links'][] = [
|
||||
'title' => $this->post->title,
|
||||
'url' => '',
|
||||
];
|
||||
}
|
||||
|
||||
return $breadcrumb;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Canonical URL */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Wyłącza canonical redirection — patrz komentarz w list.php.
|
||||
*/
|
||||
protected function canonicalRedirection($canonical_url = '')
|
||||
{
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Helpery prywatne */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Rekurencyjnie dodaje url do węzłów drzewa kategorii (sidebar).
|
||||
*/
|
||||
protected function enrichCategoryTree(array $tree, $idLang)
|
||||
{
|
||||
foreach ($tree as &$node) {
|
||||
$node['category']['url'] = Projectproblog::getCategoryUrl(
|
||||
$node['category']['link_rewrite'],
|
||||
$idLang
|
||||
);
|
||||
$node['children'] = $this->enrichCategoryTree($node['children'], $idLang);
|
||||
}
|
||||
unset($node);
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobiera dane kategorii po ID-kach i dodaje URL-e.
|
||||
*/
|
||||
protected function loadCategories(array $categoryIds, $idLang)
|
||||
{
|
||||
if (empty($categoryIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$idList = implode(',', array_map('intval', $categoryIds));
|
||||
|
||||
$rows = Db::getInstance()->executeS(
|
||||
'SELECT c.`id_category`, cl.`name`, cl.`link_rewrite`
|
||||
FROM `' . _DB_PREFIX_ . 'projectproblog_category` c
|
||||
LEFT JOIN `' . _DB_PREFIX_ . 'projectproblog_category_lang` cl
|
||||
ON c.`id_category` = cl.`id_category` AND cl.`id_lang` = ' . (int) $idLang . '
|
||||
WHERE c.`id_category` IN (' . $idList . ') AND c.`active` = 1
|
||||
ORDER BY cl.`name` ASC'
|
||||
);
|
||||
|
||||
if (!is_array($rows)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($rows as &$row) {
|
||||
$row['url'] = Projectproblog::getCategoryUrl($row['link_rewrite'], $idLang);
|
||||
}
|
||||
unset($row);
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ustawia meta title / description / keywords dla layoutu.
|
||||
*/
|
||||
protected function setPageMeta()
|
||||
{
|
||||
if (!$this->post) {
|
||||
return;
|
||||
}
|
||||
|
||||
$metaTitle = $this->post->meta_title ?: $this->post->title;
|
||||
$metaDesc = $this->post->meta_description ?: '';
|
||||
$metaKw = $this->post->meta_keywords ?: '';
|
||||
|
||||
$page = $this->context->smarty->getTemplateVars('page') ?: [];
|
||||
|
||||
if (!isset($page['meta'])) {
|
||||
$page['meta'] = [];
|
||||
}
|
||||
|
||||
$page['meta']['title'] = $metaTitle;
|
||||
$page['meta']['description'] = $metaDesc;
|
||||
$page['meta']['keywords'] = $metaKw;
|
||||
|
||||
$this->context->smarty->assign('page', $page);
|
||||
}
|
||||
}
|
||||
8
modules/projectproblog/controllers/index.php
Normal file
8
modules/projectproblog/controllers/index.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
|
||||
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
|
||||
header("Cache-Control: no-store, no-cache, must-revalidate");
|
||||
header("Cache-Control: post-check=0, pre-check=0", false);
|
||||
header("Pragma: no-cache");
|
||||
header("Location: ../");
|
||||
exit;
|
||||
Reference in New Issue
Block a user