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:
159
modules/projectproblog/classes/BlogCategory.php
Normal file
159
modules/projectproblog/classes/BlogCategory.php
Normal file
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
/**
|
||||
* Project-Pro Blog — BlogCategory ObjectModel
|
||||
*
|
||||
* @author Project-Pro <https://www.project-pro.pl>
|
||||
* @copyright 2024 Project-Pro
|
||||
*/
|
||||
|
||||
if (!defined('_PS_VERSION_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class BlogCategory extends ObjectModel
|
||||
{
|
||||
/** @var int */
|
||||
public $id_parent = 0;
|
||||
|
||||
/** @var bool */
|
||||
public $active = true;
|
||||
|
||||
/** @var int */
|
||||
public $position = 0;
|
||||
|
||||
/** @var string */
|
||||
public $date_add;
|
||||
|
||||
/** @var string */
|
||||
public $date_upd;
|
||||
|
||||
// --- multilang ---
|
||||
|
||||
/** @var string */
|
||||
public $name;
|
||||
|
||||
/** @var string */
|
||||
public $description;
|
||||
|
||||
/** @var string */
|
||||
public $link_rewrite;
|
||||
|
||||
/** @var string */
|
||||
public $meta_title;
|
||||
|
||||
/** @var string */
|
||||
public $meta_keywords;
|
||||
|
||||
/** @var string */
|
||||
public $meta_description;
|
||||
|
||||
public static $definition = [
|
||||
'table' => 'projectproblog_category',
|
||||
'primary' => 'id_category',
|
||||
'multilang' => true,
|
||||
'fields' => [
|
||||
// główna tabela
|
||||
'id_parent' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'],
|
||||
'active' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
|
||||
'position' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'],
|
||||
'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
|
||||
'date_upd' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
|
||||
|
||||
// multilang
|
||||
'name' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'required' => true, 'size' => 255],
|
||||
'description' => ['type' => self::TYPE_HTML, 'lang' => true, 'validate' => 'isCleanHtml'],
|
||||
'link_rewrite' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isLinkRewrite', 'required' => true, 'size' => 255],
|
||||
'meta_title' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 255],
|
||||
'meta_keywords' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 255],
|
||||
'meta_description' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 512],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Zwraca płaską listę kategorii do selecta w formularzu.
|
||||
* Format: [['id_category' => x, 'name' => '...'], ...]
|
||||
*/
|
||||
public static function getCategoriesForSelect($idLang, $currentId = 0)
|
||||
{
|
||||
$rows = 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) $idLang . '
|
||||
WHERE c.`id_category` != ' . (int) $currentId . '
|
||||
ORDER BY cl.`name` ASC'
|
||||
);
|
||||
|
||||
$result = [['id_category' => 0, 'name' => '— brak (kategoria główna) —']];
|
||||
if (is_array($rows)) {
|
||||
$result = array_merge($result, $rows);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zwraca drzewo kategorii do wyświetlenia na froncie.
|
||||
* Wynik: tablica zagnieżdżona ['category' => [...], 'children' => [...]]
|
||||
*/
|
||||
public static function getCategoryTree($idLang, $idParent = 0)
|
||||
{
|
||||
$rows = Db::getInstance()->executeS(
|
||||
'SELECT c.`id_category`, c.`id_parent`, c.`position`, 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.`active` = 1
|
||||
ORDER BY c.`position` ASC, cl.`name` ASC'
|
||||
);
|
||||
|
||||
if (!is_array($rows)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// indeks po id_parent
|
||||
$byParent = [];
|
||||
foreach ($rows as $row) {
|
||||
$byParent[(int) $row['id_parent']][] = $row;
|
||||
}
|
||||
|
||||
return self::buildTree($byParent, $idParent);
|
||||
}
|
||||
|
||||
private static function buildTree(array $byParent, $parentId)
|
||||
{
|
||||
if (!isset($byParent[$parentId])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$tree = [];
|
||||
foreach ($byParent[$parentId] as $cat) {
|
||||
$tree[] = [
|
||||
'category' => $cat,
|
||||
'children' => self::buildTree($byParent, (int) $cat['id_category']),
|
||||
];
|
||||
}
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobiera kategorię po link_rewrite dla bieżącego języka.
|
||||
*/
|
||||
public static function getByLinkRewrite($linkRewrite, $idLang)
|
||||
{
|
||||
$row = Db::getInstance()->getRow(
|
||||
'SELECT c.`id_category`
|
||||
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 cl.`link_rewrite` = \'' . pSQL($linkRewrite) . '\' AND c.`active` = 1'
|
||||
);
|
||||
|
||||
if (!$row) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BlogCategory((int) $row['id_category'], $idLang);
|
||||
}
|
||||
}
|
||||
219
modules/projectproblog/classes/BlogPost.php
Normal file
219
modules/projectproblog/classes/BlogPost.php
Normal file
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
/**
|
||||
* Project-Pro Blog — BlogPost ObjectModel
|
||||
*
|
||||
* @author Project-Pro <https://www.project-pro.pl>
|
||||
* @copyright 2024 Project-Pro
|
||||
*/
|
||||
|
||||
if (!defined('_PS_VERSION_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class BlogPost extends ObjectModel
|
||||
{
|
||||
/** @var bool */
|
||||
public $active = true;
|
||||
|
||||
/** @var string|null */
|
||||
public $thumbnail;
|
||||
|
||||
/** @var string */
|
||||
public $date_add;
|
||||
|
||||
/** @var string */
|
||||
public $date_upd;
|
||||
|
||||
// --- multilang ---
|
||||
|
||||
/** @var string */
|
||||
public $title;
|
||||
|
||||
/** @var string */
|
||||
public $intro;
|
||||
|
||||
/** @var string */
|
||||
public $content;
|
||||
|
||||
/** @var string */
|
||||
public $link_rewrite;
|
||||
|
||||
/** @var string */
|
||||
public $meta_title;
|
||||
|
||||
/** @var string */
|
||||
public $meta_keywords;
|
||||
|
||||
/** @var string */
|
||||
public $meta_description;
|
||||
|
||||
public static $definition = [
|
||||
'table' => 'projectproblog_post',
|
||||
'primary' => 'id_post',
|
||||
'multilang' => true,
|
||||
'fields' => [
|
||||
// główna tabela
|
||||
'active' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
|
||||
'thumbnail' => ['type' => self::TYPE_STRING, 'validate' => 'isFileName', 'size' => 255],
|
||||
'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
|
||||
'date_upd' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
|
||||
|
||||
// multilang
|
||||
'title' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'required' => true, 'size' => 255],
|
||||
'intro' => ['type' => self::TYPE_HTML, 'lang' => true, 'validate' => 'isCleanHtml'],
|
||||
'content' => ['type' => self::TYPE_HTML, 'lang' => true, 'validate' => 'isCleanHtml'],
|
||||
'link_rewrite' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isLinkRewrite', 'required' => true, 'size' => 255],
|
||||
'meta_title' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 255],
|
||||
'meta_keywords' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 255],
|
||||
'meta_description' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 512],
|
||||
],
|
||||
];
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Delete — sprzątanie pliku i relacji z kategoriami */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
public function delete()
|
||||
{
|
||||
if ($this->thumbnail) {
|
||||
$file = _PS_MODULE_DIR_ . 'projectproblog/views/img/posts/' . $this->thumbnail;
|
||||
if (file_exists($file)) {
|
||||
@unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
Db::getInstance()->delete(
|
||||
'projectproblog_post_category',
|
||||
'id_post = ' . (int) $this->id
|
||||
);
|
||||
|
||||
return parent::delete();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Relacje z kategoriami */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Zwraca tablicę ID kategorii przypisanych do wpisu.
|
||||
*/
|
||||
public static function getCategories($idPost)
|
||||
{
|
||||
$rows = Db::getInstance()->executeS(
|
||||
'SELECT `id_category`
|
||||
FROM `' . _DB_PREFIX_ . 'projectproblog_post_category`
|
||||
WHERE `id_post` = ' . (int) $idPost
|
||||
);
|
||||
|
||||
return is_array($rows) ? array_column($rows, 'id_category') : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Zapisuje relacje wpis↔kategorie (zastępuje stare).
|
||||
*/
|
||||
public static function setCategories($idPost, array $categoryIds)
|
||||
{
|
||||
Db::getInstance()->delete(
|
||||
'projectproblog_post_category',
|
||||
'id_post = ' . (int) $idPost
|
||||
);
|
||||
|
||||
if (empty($categoryIds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
foreach ($categoryIds as $idCat) {
|
||||
if ((int) $idCat > 0) {
|
||||
$rows[] = [
|
||||
'id_post' => (int) $idPost,
|
||||
'id_category' => (int) $idCat,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ($rows) {
|
||||
Db::getInstance()->insert('projectproblog_post_category', $rows);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Zapytania dla frontu */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Lista wpisów do wyświetlenia na stronie (z opcjonalnym filtrem kategorii).
|
||||
*/
|
||||
public static function getList($idLang, $page, $perPage, $idCategory = null)
|
||||
{
|
||||
$page = max(1, (int) $page);
|
||||
$perPage = max(1, (int) $perPage);
|
||||
$offset = ($page - 1) * $perPage;
|
||||
|
||||
$join = $idCategory
|
||||
? 'INNER JOIN `' . _DB_PREFIX_ . 'projectproblog_post_category` pc
|
||||
ON p.`id_post` = pc.`id_post` AND pc.`id_category` = ' . (int) $idCategory
|
||||
: '';
|
||||
|
||||
return Db::getInstance()->executeS(
|
||||
'SELECT p.`id_post`, p.`thumbnail`, p.`date_add`,
|
||||
pl.`title`, pl.`intro`, pl.`link_rewrite`
|
||||
FROM `' . _DB_PREFIX_ . 'projectproblog_post` p
|
||||
LEFT JOIN `' . _DB_PREFIX_ . 'projectproblog_post_lang` pl
|
||||
ON p.`id_post` = pl.`id_post` AND pl.`id_lang` = ' . (int) $idLang . '
|
||||
' . $join . '
|
||||
WHERE p.`active` = 1
|
||||
ORDER BY p.`date_add` DESC
|
||||
LIMIT ' . (int) $offset . ', ' . (int) $perPage
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Liczba wpisów (do paginacji).
|
||||
*/
|
||||
public static function getCount($idLang, $idCategory = null)
|
||||
{
|
||||
$join = $idCategory
|
||||
? 'INNER JOIN `' . _DB_PREFIX_ . 'projectproblog_post_category` pc
|
||||
ON p.`id_post` = pc.`id_post` AND pc.`id_category` = ' . (int) $idCategory
|
||||
: '';
|
||||
|
||||
return (int) Db::getInstance()->getValue(
|
||||
'SELECT COUNT(DISTINCT p.`id_post`)
|
||||
FROM `' . _DB_PREFIX_ . 'projectproblog_post` p
|
||||
' . $join . '
|
||||
WHERE p.`active` = 1'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobiera wpis po link_rewrite dla danego języka.
|
||||
*/
|
||||
public static function getByLinkRewrite($linkRewrite, $idLang)
|
||||
{
|
||||
$row = Db::getInstance()->getRow(
|
||||
'SELECT p.`id_post`
|
||||
FROM `' . _DB_PREFIX_ . 'projectproblog_post` p
|
||||
INNER JOIN `' . _DB_PREFIX_ . 'projectproblog_post_lang` pl
|
||||
ON p.`id_post` = pl.`id_post` AND pl.`id_lang` = ' . (int) $idLang . '
|
||||
WHERE pl.`link_rewrite` = \'' . pSQL($linkRewrite) . '\' AND p.`active` = 1'
|
||||
);
|
||||
|
||||
return $row ? new BlogPost((int) $row['id_post'], $idLang) : null;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Helper URL miniaturki */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
public function getThumbnailUrl()
|
||||
{
|
||||
if (!$this->thumbnail) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Context::getContext()->link->getBaseLink()
|
||||
. 'modules/projectproblog/views/img/posts/'
|
||||
. $this->thumbnail;
|
||||
}
|
||||
}
|
||||
8
modules/projectproblog/classes/index.php
Normal file
8
modules/projectproblog/classes/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