Compare commits

..

10 Commits

10 changed files with 265 additions and 180 deletions

View File

@ -1,3 +1,16 @@
2023.06.17
- require dotclear 2.26
- fix php7.4 compatibility
2023.04.22
- require dotclear 2.26
- use latest helper namespace
- fix nullsafe warnings
2023.03.25
- require dotclear 2.26
- use namespace
2022.11.20
- fix compatibility with Dotclear 2.24 (required)

View File

@ -3,7 +3,7 @@
[![Release](https://img.shields.io/github/v/release/JcDenis/lastpostsExtend)](https://github.com/JcDenis/lastpostsExtend/releases)
[![Date](https://img.shields.io/github/release-date/JcDenis/lastpostsExtend)](https://github.com/JcDenis/lastpostsExtend/releases)
[![Issues](https://img.shields.io/github/issues/JcDenis/lastpostsExtend)](https://github.com/JcDenis/lastpostsExtend/issues)
[![Dotclear](https://img.shields.io/badge/dotclear-v2.24-blue.svg)](https://fr.dotclear.org/download)
[![Dotclear](https://img.shields.io/badge/dotclear-v2.26-blue.svg)](https://fr.dotclear.org/download)
[![Dotaddict](https://img.shields.io/badge/dotaddict-official-green.svg)](https://plugins.dotaddict.org/dc2/details/lastpostsExtend)
[![License](https://img.shields.io/github/license/JcDenis/lastpostsExtend)](https://github.com/JcDenis/lastpostsExtend/blob/master/LICENSE)
@ -19,7 +19,7 @@ Like widget lastposts but with more options.
lastpostsExtend requires:
* permissions to manage widgets
* Dotclear 2.24
* Dotclear 2.26
## USAGE

View File

@ -1,17 +0,0 @@
<?php
/**
* @brief lastpostsExtend, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugin
*
* @author Jean-Christian Denis and contributors
*
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
if (!defined('DC_CONTEXT_ADMIN')) {
return null;
}
require __DIR__ . '/_widgets.php';

View File

@ -18,13 +18,15 @@ $this->registerModule(
'Last entries (Extended)',
'Extended list of entries',
'Jean-Christian Denis and contributors',
'2022.11.20',
'2023.06.17',
[
'requires' => [['core', '2.24']],
'permissions' => dcAuth::PERMISSION_ADMIN,
'type' => 'plugin',
'support' => 'https://github.com/JcDenis/lastpostsExtend',
'details' => 'http://plugins.dotaddict.org/dc2/details/lastpostsExtend',
'repository' => 'https://raw.githubusercontent.com/JcDenis/lastpostsExtend/master/repository.xml',
'requires' => [['core', '2.26']],
'permissions' => dcCore::app()->auth->makePermissions([
dcAuth::PERMISSION_ADMIN,
]),
'type' => 'plugin',
'support' => 'https://github.com/JcDenis/' . basename(__DIR__),
'details' => 'http://plugins.dotaddict.org/dc2/details/' . basename(__DIR__),
'repository' => 'https://raw.githubusercontent.com/JcDenis/' . basename(__DIR__) . '/master/repository.xml',
]
);

View File

@ -1,17 +0,0 @@
<?php
/**
* @brief lastpostsExtend, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugin
*
* @author Jean-Christian Denis and contributors
*
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
if (!defined('DC_RC_PATH')) {
return null;
}
require __DIR__ . '/_widgets.php';

View File

@ -2,11 +2,11 @@
<modules xmlns:da="http://dotaddict.org/da/">
<module id="lastpostsExtend">
<name>Last entries (Extended)</name>
<version>2022.11.20</version>
<version>2023.06.17</version>
<author>Jean-Christian Denis and contributors</author>
<desc>Extended list of entries</desc>
<file>https://github.com/JcDenis/lastpostsExtend/releases/download/v2022.11.20/plugin-lastpostsExtend.zip</file>
<da:dcmin>2.24</da:dcmin>
<file>https://github.com/JcDenis/lastpostsExtend/releases/download/v2023.06.17/plugin-lastpostsExtend.zip</file>
<da:dcmin>2.26</da:dcmin>
<da:details>http://plugins.dotaddict.org/dc2/details/lastpostsExtend</da:details>
<da:support>https://github.com/JcDenis/lastpostsExtend</da:support>
</module>

View File

@ -0,0 +1,28 @@
<?php
/**
* @package Dotclear
*
* @copyright Olivier Meunier & Association Dotclear
* @copyright GPL-2.0-only
*/
#
# DOT NOT MODIFY THIS FILE !
#
use Dotclear\Helper\L10n;
L10n::$locales['Last entries (Extended)'] = 'Derniers billets (étendu)';
L10n::$locales['Extended list of entries'] = 'Liste étendue de billets';
L10n::$locales['Post'] = 'Billet';
L10n::$locales['Gallery'] = 'Galerie';
L10n::$locales['Protection:'] = 'Protection :';
L10n::$locales['only without password'] = 'seulement sans mot de passe';
L10n::$locales['only with password'] = 'seulement avec mot de passe';
L10n::$locales['Selected entries only'] = 'Billets sélectionnés seulement';
L10n::$locales['Updated entries only'] = 'Billets mis à jour seulement';
L10n::$locales['Limit to tags:'] = 'Limiter aux mots-clés :';
L10n::$locales['Limit to words:'] = 'Limiter aux mots :';
L10n::$locales['Show entries first image:'] = 'Afficher la première image du billet :';
L10n::$locales['Show entries excerpt'] = 'Afficher l\'extrait';
L10n::$locales['Excerpt length:'] = 'Taille de l\'extrait :';
L10n::$locales['Show comments count'] = 'Afficher le nombre de commentaires';

41
src/Backend.php 100644
View File

@ -0,0 +1,41 @@
<?php
/**
* @brief lastpostsExtend, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugin
*
* @author Jean-Christian Denis and contributors
*
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
declare(strict_types=1);
namespace Dotclear\Plugin\lastpostsExtend;
use dcCore;
use dcNsProcess;
class Backend extends dcNsProcess
{
public static function init(): bool
{
static::$init = defined('DC_CONTEXT_ADMIN');
return static::$init;
}
public static function process(): bool
{
if (!static::$init) {
return false;
}
dcCore::app()->addBehaviors([
'initWidgets' => [Widgets::class, 'initWidgets'],
]);
return true;
}
}

41
src/Frontend.php 100644
View File

@ -0,0 +1,41 @@
<?php
/**
* @brief lastpostsExtend, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugin
*
* @author Jean-Christian Denis and contributors
*
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
declare(strict_types=1);
namespace Dotclear\Plugin\lastpostsExtend;
use dcCore;
use dcNsProcess;
class Frontend extends dcNsProcess
{
public static function init(): bool
{
static::$init = true;
return static::$init;
}
public static function process(): bool
{
if (!static::$init) {
return false;
}
dcCore::app()->addBehaviors([
'initWidgets' => [Widgets::class, 'initWidgets'],
]);
return true;
}
}

View File

@ -10,43 +10,49 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
if (!defined('DC_RC_PATH')) {
return null;
}
declare(strict_types=1);
dcCore::app()->addBehavior(
'initWidgets',
['lastpostsextendWidget', 'initWidget']
);
namespace Dotclear\Plugin\lastpostsExtend;
class lastpostsextendWidget
use dcBlog;
use dcCore;
use dcMeta;
use Dotclear\Helper\Date;
use Dotclear\Helper\Text;
use Dotclear\Helper\File\Path;
use Dotclear\Helper\Html\Html;
use Dotclear\Plugin\widgets\WidgetsStack;
use Dotclear\Plugin\widgets\WidgetsElement;
class Widgets
{
public static function initWidget($w)
public static function initWidgets(WidgetsStack $w): void
{
# Create widget
// nullsafe
if (is_null(dcCore::app()->blog)) {
return;
}
// Create widget
$w->create(
'lastpostsextend',
__('Last entries (Extended)'),
['lastpostsextendWidget', 'parseWidget'],
[self::class, 'parseWidget'],
null,
__('Extended list of entries')
);
# Title
$w->lastpostsextend->setting(
'title',
__('Title:'),
__('Last entries'),
'text'
);
# type
// Title
$w->lastpostsextend->addTitle(__('Last entries'));
// post type
$posttypes = [
__('Post') => 'post',
__('Page') => 'page',
__('Gallery') => 'galitem',
];
# plugin muppet types
if (dcCore::app()->plugins->moduleExists('muppet')) {
$muppet_types = muppet::getPostTypes();
// plugin muppet types
if (dcCore::app()->plugins->moduleExists('muppet') && class_exists('\muppet')) {
$muppet_types = \muppet::getPostTypes();
if (is_array($muppet_types) && !empty($muppet_types)) {
foreach ($muppet_types as $k => $v) {
$posttypes[$v['name']] = $k;
@ -60,7 +66,8 @@ class lastpostsextendWidget
'combo',
$posttypes
);
# Category (post and page have same category)
// Category (post and page have same category)
$rs = dcCore::app()->blog->getCategories([
'post_type' => 'post',
]);
@ -71,8 +78,8 @@ class lastpostsextendWidget
while ($rs->fetch()) {
$categories[str_repeat(
'&nbsp;&nbsp;',
$rs->level - 1
) . '&bull; ' . html::escapeHTML($rs->cat_title)] = $rs->cat_id;
(int) $rs->f('level') - 1
) . '&bull; ' . Html::escapeHTML($rs->f('cat_title'))] = $rs->f('cat_id');
}
$w->lastpostsextend->setting(
'category',
@ -82,7 +89,8 @@ class lastpostsextendWidget
$categories
);
unset($rs, $categories);
# Pasworded
// Passworded
$w->lastpostsextend->setting(
'passworded',
__('Protection:'),
@ -94,7 +102,8 @@ class lastpostsextendWidget
__('only with password') => 'yes',
]
);
# Status
// Status
$w->lastpostsextend->setting(
'status',
__('Status:'),
@ -108,21 +117,24 @@ class lastpostsextendWidget
__('published') => '1',
]
);
# Selected entries only
// Selected entries only
$w->lastpostsextend->setting(
'selectedonly',
__('Selected entries only'),
0,
'check'
);
# Updated entries only
// Updated entries only
$w->lastpostsextend->setting(
'updatedonly',
__('Updated entries only'),
0,
'check'
);
# Tag
// Tag
if (dcCore::app()->plugins->moduleExists('tags')) {
$w->lastpostsextend->setting(
'tag',
@ -131,21 +143,24 @@ class lastpostsextendWidget
'text'
);
}
# Search
// Search
$w->lastpostsextend->setting(
'search',
__('Limit to words:'),
'',
'text'
);
# Entries limit
// Entries limit
$w->lastpostsextend->setting(
'limit',
__('Entries limit:'),
10,
'text'
);
# Sort type
// Sort
$w->lastpostsextend->setting(
'sortby',
__('Order by:'),
@ -157,7 +172,6 @@ class lastpostsextendWidget
__('Comments') => 'nb_comment',
]
);
# Sort order
$w->lastpostsextend->setting(
'sort',
__('Sort:'),
@ -168,7 +182,8 @@ class lastpostsextendWidget
__('Descending') => 'desc',
]
);
# First image
// First image
$w->lastpostsextend->setting(
'firstimage',
__('Show entries first image:'),
@ -183,103 +198,76 @@ class lastpostsextendWidget
__('original') => 'o',
]
);
# With excerpt
// With excerpt
$w->lastpostsextend->setting(
'excerpt',
__('Show entries excerpt'),
0,
'check'
);
# Excerpt length
// Excerpt cut length
$w->lastpostsextend->setting(
'excerptlen',
__('Excerpt length:'),
100,
'text'
);
# Comment count
// Comment count
$w->lastpostsextend->setting(
'commentscount',
__('Show comments count'),
0,
'check'
);
# Home only
$w->lastpostsextend->setting(
'homeonly',
__('Display on:'),
0,
'combo',
[
__('All pages') => 0,
__('Home page only') => 1,
__('Except on home page') => 2,
]
);
# widget option - content only
$w->lastpostsextend->setting(
'content_only',
__('Content only'),
0,
'check'
);
# widget option - additionnal CSS
$w->lastpostsextend->setting(
'class',
__('CSS class:'),
''
);
# widget option - put offline
$w->lastpostsextend->setting(
'offline',
__('Offline'),
0,
'check'
);
// commons
$w->lastpostsextend
->addHomeOnly()
->addContentOnly()
->addClass()
->addOffline();
}
public static function parseWidget($w)
public static function parseWidget(WidgetsElement $w): string
{
// Widget is offline & Home page only
if (is_null(dcCore::app()->auth) || is_null(dcCore::app()->blog) || $w->offline || !$w->checkHomeOnly(dcCore::app()->url->type)) {
return '';
}
// Need posts excerpt
if ($w->excerpt) {
$params['columns'][] = 'post_excerpt';
}
// prepare request params
$params = [
'sql' => '',
'columns' => [],
'from' => '',
];
# Widget is offline
if ($w->offline) {
return;
}
# Home page only
if ($w->homeonly == 1 && dcCore::app()->url->type != 'default'
|| $w->homeonly == 2 && dcCore::app()->url->type == 'default') {
return null;
}
# Need posts excerpt
if ($w->excerpt) {
$params['columns'][] = 'post_excerpt';
}
# Passworded
// Passworded
if ($w->passworded == 'yes') {
$params['sql'] .= 'AND post_password IS NOT NULL ';
} elseif ($w->passworded == 'no') {
$params['sql'] .= 'AND post_password IS NULL ';
}
# Status
// Status
if ($w->status != 'all') {
$params['post_status'] = $w->status;
}
# Search words
// Search words
if ('' != $w->search) {
$params['search'] = $w->search;
}
# Updated posts only
// Updated posts only
if ($w->updatedonly) {
$params['sql'] .= 'AND post_creadt < post_upddt ' .
'AND post_dt < post_upddt ';
@ -298,15 +286,15 @@ class lastpostsextendWidget
$params['limit'] = abs((int) $w->limit);
$params['no_content'] = true;
# Selected posts only
// Selected posts only
if ($w->selectedonly) {
$params['post_selected'] = 1;
}
# Type
// Post type
$params['post_type'] = $w->posttype;
# Category
// Category
if ($w->category) {
if ($w->category == 'null') {
$params['sql'] .= ' AND P.cat_id IS NULL ';
@ -317,13 +305,13 @@ class lastpostsextendWidget
}
}
# Tags
// Tags
if (dcCore::app()->plugins->moduleExists('tags') && $w->tag) {
$tags = explode(',', $w->tag);
foreach ($tags as $i => $tag) {
$tags[$i] = trim($tag);
}
$params['from'] .= ', ' . dcCore::app()->prefix . 'meta META ';
$params['from'] .= ', ' . dcCore::app()->prefix . dcMeta::META_TABLE_NAME . ' META ';
$params['sql'] .= 'AND META.post_id = P.post_id ';
$params['sql'] .= 'AND META.meta_id ' . dcCore::app()->con->in($tags) . ' ';
$params['sql'] .= "AND META.meta_type = 'tag' ";
@ -335,53 +323,58 @@ class lastpostsextendWidget
false
);
# No result
// No result
if ($rs->isEmpty()) {
return null;
return '';
}
# Return
$res = $w->title ? $w->renderTitle(html::escapeHTML($w->title)) : '';
// Parse result
$res = $w->title ? $w->renderTitle(Html::escapeHTML($w->title)) : '';
while ($rs->fetch()) {
$res .= '<li>' .
'<' . ($rs->post_status == 1 ? 'a href="' . $rs->getURL() . '"' : 'span') .
' title="' .
dt::dt2str(
dcCore::app()->blog->settings->system->date_format,
$rs->post_upddt
) . ', ' .
dt::dt2str(
dcCore::app()->blog->settings->system->time_format,
$rs->post_upddt
) . '">' .
html::escapeHTML($rs->post_title) .
'</' . ($rs->post_status == 1 ? 'a' : 'span') . '>';
if (is_null(dcCore::app()->blog)) { // phpstan ignores previous check !?
break;
}
$published = ((int) $rs->f('post_status')) == dcBlog::POST_PUBLISHED;
# Nb comments
if ($w->commentscount && $rs->post_status == 1) {
$res .= '<li>' .
'<' . ($published ? 'a href="' . $rs->getURL() . '"' : 'span') .
' title="' .
Date::dt2str(
dcCore::app()->blog->settings->get('system')->get('date_format'),
$rs->f('post_upddt')
) . ', ' .
Date::dt2str(
dcCore::app()->blog->settings->get('system')->get('time_format'),
$rs->f('post_upddt')
) . '">' .
Html::escapeHTML($rs->f('post_title')) .
'</' . ($published ? 'a' : 'span') . '>';
// Nb comments
if ($w->commentscount && $published) {
$res .= ' (' . $rs->nb_comment . ')';
}
# First image
// First image
if ($w->firstimage != '') {
$res .= self::entryFirstImage(
$rs->post_type,
$rs->post_id,
$rs->f('post_type'),
$rs->f('post_id'),
$w->firstimage
);
}
# Excerpt
// Excerpt
if ($w->excerpt) {
$excerpt = $rs->post_excerpt;
if ($rs->post_format == 'wiki') {
$excerpt = $rs->f('post_excerpt');
if ($rs->f('post_format') == 'wiki') {
dcCore::app()->initWikiComment();
$excerpt = dcCore::app()->wikiTransform($excerpt);
$excerpt = dcCore::app()->HTMLfilter($excerpt);
}
if (strlen($excerpt) > 0) {
$cut = text::cutString(
$cut = Text::cutString(
$excerpt,
abs((int) $w->excerptlen)
);
@ -394,16 +387,16 @@ class lastpostsextendWidget
}
return $w->renderDiv(
$w->content_only,
(bool) $w->content_only,
'lastpostsextend ' . $w->class,
'',
'<ul>' . $res . '</ul>'
);
}
private static function entryFirstImage($type, $id, $size = 's')
private static function entryFirstImage(string $type, $id, string $size = 's'): string
{
if (!in_array($type, ['post', 'page', 'galitem'])) {
if (is_null(dcCore::app()->auth) || is_null(dcCore::app()->blog) || !in_array($type, ['post', 'page', 'galitem'])) {
return '';
}
@ -421,8 +414,8 @@ class lastpostsextendWidget
$size = 's';
}
$p_url = dcCore::app()->blog->settings->system->public_url;
$p_site = preg_replace(
$p_url = (string) dcCore::app()->blog->settings->get('system')->get('public_url');
$p_site = (string) preg_replace(
'#^(.+?//.+?)/(.*)$#',
'$1',
dcCore::app()->blog->url
@ -435,10 +428,10 @@ class lastpostsextendWidget
$src = '';
$alt = '';
$subject = $rs->post_excerpt_xhtml . $rs->post_content_xhtml . $rs->cat_desc;
$subject = $rs->f('post_excerpt_xhtml') . $rs->f('post_content_xhtml') . $rs->f('cat_desc');
if (preg_match_all($pattern, $subject, $m) > 0) {
foreach ($m[1] as $i => $img) {
if (($src = self::ContentFirstImageLookup($p_root, $img, $size)) !== false) {
if (($src = self::ContentFirstImageLookup($p_root, $img, $size)) != '') {
$src = $p_url . (dirname($img) != '/' ? dirname($img) : '') . '/' . $src;
if (preg_match('/alt="([^"]+)"/', $m[0][$i], $malt)) {
$alt = $malt[1];
@ -456,23 +449,24 @@ class lastpostsextendWidget
return
'<div class="img-box">' .
'<div class="img-thumbnail">' .
'<a title="' . html::escapeHTML($rs->post_title) . '" href="' . $rs->getURL() . '">' .
'<a title="' . Html::escapeHTML($rs->f('post_title')) . '" href="' . $rs->getURL() . '">' .
'<img alt="' . $alt . '" src="' . stripslashes($src) . '" />' .
'</a></div>' .
"</div>\n";
}
private static function ContentFirstImageLookup($root, $img, $size)
private static function ContentFirstImageLookup(string $root, string $img, string $size): string
{
$res = '';
# Get base name and extension
$info = path::info($img);
$info = Path::info($img);
$base = $info['base'];
if (preg_match('/^\.(.+)_(sq|t|s|m)$/', $base, $m)) {
$base = $m[1];
}
$res = false;
if ($size != 'o' && file_exists($root . '/' . $info['dirname'] . '/.' . $base . '_' . $size . '.jpg')) {
$res = '.' . $base . '_' . $size . '.jpg';
} else {
@ -488,6 +482,6 @@ class lastpostsextendWidget
}
}
return $res ? $res : false;
return $res;
}
}