use namespace

This commit is contained in:
Jean-Christian Paul Denis 2023-05-08 00:40:58 +02:00
parent b7affac99d
commit ef04d66d44
Signed by: JcDenis
GPG Key ID: 1B5B8C5B90B6C951
35 changed files with 4781 additions and 3535 deletions

View File

@ -7,7 +7,7 @@
$opts = getopt('d:c:b:u:h');
function zchelp($status = 0)
function zchelp(string|int $status = 0): void
{
echo
"Options: \n" .
@ -33,7 +33,7 @@ if (isset($opts['d'])) {
$dc_root = $_SERVER['DC_ROOT'];
}
if (isset($opts['c'])) {
if (isset($opts['c']) && is_string($opts['c'])) {
$dc_conf = realpath($opts['c']);
} elseif (isset($_SERVER['DC_RC_PATH'])) {
$dc_conf = realpath($_SERVER['DC_RC_PATH']);
@ -55,7 +55,7 @@ if (!$dc_conf || !is_readable($dc_conf)) {
zchelp(1);
}
if (!$blog_id) {
if (!$blog_id || !is_string($blog_id)) {
fwrite(STDERR, "Blog ID is not defined\n\n");
zchelp(1);
}
@ -69,23 +69,21 @@ unset($blog_id);
require $dc_root . '/inc/prepend.php';
unset($dc_root);
dcCore::app()->setBlog(DC_BLOG_ID);
if (dcCore::app()->blog->id == null) {
dcCore::app()->setBlog(is_string(DC_BLOG_ID) ? DC_BLOG_ID : '');
if (is_null(dcCore::app()->blog) || dcCore::app()->blog->id == null) {
fwrite(STDERR, "Blog is not defined\n");
exit(1);
}
if (!isset($opts['u']) || !dcCore::app()->auth->checkUser($opts['u'])) {
if (!isset($opts['u']) || is_null(dcCore::app()->auth) || !dcCore::app()->auth->checkUser(is_string($opts['u']) ? $opts['u'] : '')) {
fwrite(STDERR, "Unable to set user\n");
exit(1);
}
dcCore::app()->plugins->loadModules(DC_PLUGINS_ROOT);
dcCore::app()->blog->settings->addNamespace(basename(__DIR__));
try {
$zc = new zoneclearFeedServer();
$zc = Dotclear\Plugin\zoneclearFeedServer\ZoneclearFeedServer::instance();
$zc->checkFeedsUpdate();
} catch (Exception $e) {
fwrite(STDERR, $e->getMessage() . "\n");

View File

@ -1,243 +0,0 @@
<?php
/**
* @brief zoneclearFeedServer, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugin
*
* @author Jean-Christian Denis, BG, Pierre Van Glabeke
*
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
if (!defined('DC_CONTEXT_ADMIN')) {
return null;
}
/**
* @ingroup DC_PLUGIN_ZONECLEARFEEDSERVER
* @brief Mix your blog with a feeds planet - admin methods.
* @since 2.6
*/
class zcfsAdminBehaviors
{
public static function basename()
{
}
public static function feedsSortbyCombo()
{
return [
__('Date') => 'feed_upddt',
__('Name') => 'lowername',
__('Frequency') => 'feed_upd_int',
__('Update date') => 'feed_upd_last',
__('Status') => 'feed_status',
];
}
public static function entriesSortbyCombo()
{
return [
__('Date') => 'post_dt',
__('Title') => 'post_title',
__('Category') => 'cat_title',
__('Author') => 'user_id',
__('Status') => 'post_status',
];
}
/**
* Favorites.
*
* @param dcFavorites $favs Array of favorites
*/
public static function adminDashboardFavoritesV2(dcFavorites $favs)
{
$favs->register('zcfs', [
'title' => __('Feeds server'),
'url' => dcCore::app()->adminurl->get('admin.plugin.' . basename(dirname('../' . __DIR__))),
'small-icon' => dcPage::getPF(basename(dirname('../' . __DIR__)) . '/icon.svg'),
'large-icon' => dcPage::getPF(basename(dirname('../' . __DIR__)) . '/icon.svg'),
'permissions' => dcCore::app()->auth->makePermissions([
dcAuth::PERMISSION_USAGE,
dcAuth::PERMISSION_CONTENT_ADMIN,
]),
'dashboard_cb' => ['zcfsAdminBehaviors', 'adminDashboardFavoritesCallback'],
]);
}
/**
* Favorites hack.
*
* @param arrayObject $fav Fav attributes
*/
public static function adminDashboardFavoritesCallback($fav)
{
$zcfs = new zoneclearFeedServer();
$count = $zcfs->getFeeds(['feed_status' => '0'], true)->f(0);
if (!$count) {
return null;
}
$fav['title'] .= '<br />' . sprintf(__('%s feed disabled', '%s feeds disabled', $count), $count);
$fav['large-icon'] = dcPage::getPF(basename(dirname('../' . __DIR__)) . '/icon-update.svg');
$fav['url'] = dcCore::app()->adminurl->get(
'admin.plugin.' . basename(dirname('../' . __DIR__)),
['part' => 'feeds', 'sortby' => 'feed_status', 'order' => 'asc']
);
}
/**
* User pref columns lists.
*
* @param arrayObject $cols Columns
*/
public static function adminColumnsListsV2($cols)
{
$cols['zcfs_feeds'] = [
__('Feeds server: Feeds'),
[
'desc' => [true, __('Feed')],
'period' => [true, __('Frequency')],
'update' => [true, __('Last update')],
'entries' => [true, __('Entries')],
],
];
$cols['zcfs_entries'] = [
__('Feeds server: Entries'),
[
'date' => [true, __('Date')],
'category' => [true, __('Category')],
'author' => [true, __('Author')],
],
];
}
/**
* User pref filters options.
*
* @param arrayObject $sorts Sort options
*/
public static function adminFiltersListsV2($sorts)
{
$sorts['zcfs_feeds'] = [
__('Feeds server: Feeds'),
self::feedsSortbyCombo(),
'lowername',
'asc',
[__('feeds per page'), 30],
];
$sorts['zcfs_entries'] = [
__('Feeds server: Entries'),
self::entriesSortbyCombo(),
'post_dt',
'desc',
[__('entries per page'), 30],
];
}
/**
* Add javascript for toggle to post edition page header.
*
* @return string Page header
*/
public static function adminPostHeaders()
{
return dcPage::jsLoad(dcPage::getPF(basename(dirname('../' . __DIR__)) . '/js/post.js'));
}
/**
* Add form to post sidebar.
*
* @param ArrayObject $main_items Main items
* @param ArrayObject $sidebar_items Sidebar items
* @param record $post Post record or null
*/
public static function adminPostFormItems(ArrayObject $main_items, ArrayObject $sidebar_items, $post)
{
if ($post === null || $post->post_type != 'post') {
return null;
}
$url = dcCore::app()->meta->getMetadata([
'post_id' => $post->post_id,
'meta_type' => 'zoneclearfeed_url',
'limit' => 1,
]);
$url = $url->isEmpty() ? '' : $url->meta_id;
if (!$url) {
return null;
}
$author = dcCore::app()->meta->getMetadata([
'post_id' => $post->post_id,
'meta_type' => 'zoneclearfeed_author',
'limit' => 1,
]);
$author = $author->isEmpty() ? '' : $author->meta_id;
$site = dcCore::app()->meta->getMetadata([
'post_id' => $post->post_id,
'meta_type' => 'zoneclearfeed_site',
'limit' => 1,
]);
$site = $site->isEmpty() ? '' : $site->meta_id;
$sitename = dcCore::app()->meta->getMetadata([
'post_id' => $post->post_id,
'meta_type' => 'zoneclearfeed_sitename',
'limit' => 1,
]);
$sitename = $sitename->isEmpty() ? '' : $sitename->meta_id;
$edit = '';
if (dcCore::app()->auth->check(dcCore::app()->auth->makePermissions([dcAuth::PERMISSION_CONTENT_ADMIN]), dcCore::app()->blog->id)) {
$fid = dcCore::app()->meta->getMetadata([
'post_id' => $post->post_id,
'meta_type' => 'zoneclearfeed_id',
'limit' => 1,
]);
if (!$fid->isEmpty()) {
$edit = sprintf(
'<p><a href="%s">%s</a></p>',
dcCore::app()->adminurl->get(
'admin.plugin.' . basename(dirname('../' . __DIR__)),
['part' => 'feed', 'feed_id' => $fid->meta_id]
),
__('Edit this feed')
);
}
}
$sidebar_items['options-box']['items']['zcfs'] = '<div id="zcfs">' .
'<h5>' . __('Feed source') . '</h5>' .
'<p>' .
'<a href="' . $url . '" title="' . $author . ' - ' . $url . '">' . __('feed URL') . '</a> - ' .
'<a href="' . $site . '" title="' . $sitename . ' - ' . $site . '">' . __('site URL') . '</a>' .
'</p>' .
$edit .
'</div>';
}
/**
* Delete related info about feed post in meta table.
*
* @param integer $post_id Post id
*/
public static function adminBeforePostDelete($post_id)
{
dcCore::app()->con->execute(
'DELETE FROM ' . dcCore::app()->prefix . dcMeta::META_TABLE_NAME . ' ' .
'WHERE post_id = ' . ((int) $post_id) . ' ' .
'AND meta_type ' . dcCore::app()->con->in([
'zoneclearfeed_url',
'zoneclearfeed_author',
'zoneclearfeed_site',
'zoneclearfeed_sitename',
'zoneclearfeed_id',
]) . ' '
);
}
}

View File

@ -1,87 +0,0 @@
<?php
/**
* @brief zoneclearFeedServer, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugin
*
* @author Jean-Christian Denis, BG, Pierre Van Glabeke
*
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
if (!defined('DC_RC_PATH')) {
return null;
}
/**
* @ingroup DC_PLUGIN_ZONECLEARFEEDSERVER
* @brief Mix your blog with a feeds planet - public methods.
* @since 2.6
*/
class zcfsPublicBehaviors
{
/**
* Remember others post extension.
*
* @param dcRecord $rs record instance
*/
public static function coreBlogGetPosts(dcRecord $rs)
{
dcCore::app()->__set('beforeZcFeedRsExt', $rs->extensions());
$rs->extend('zcfsRsExtPosts');
}
/**
* Update feeds after contents.
*/
public static function publicAfterDocumentV2()
{
# Limit feeds update to home page et feed page
# Like publishScheduledEntries
if (!in_array(dcCore::app()->url->type, ['default', 'feed'])) {
return null;
}
self::publicDocumentV2();
}
/**
* Generic behavior for before and after public content.
*/
public static function publicDocumentV2()
{
$zc = new zoneclearFeedServer();
$zc->checkFeedsUpdate();
return null;
}
/**
* Update feeds by an Ajax request (background).
*/
public static function publicHeadContent()
{
# Limit update to home page
if (dcCore::app()->url->type != 'default') {
return null;
}
$blog_url = html::escapeJS(
dcCore::app()->blog->url .
dcCore::app()->url->getBase('zoneclearFeedsPage') .
'/zcfsupd'
);
$blog_id = html::escapeJS(dcCore::app()->blog->id);
echo
"\n<!-- JS for zoneclearFeedServer --> \n" .
dcUtils::jsLoad(dcCore::app()->blog->url . dcCore::app()->url->getBase('zoneclearFeedsPage') . '/zcfsupd.js') .
"<script type=\"text/javascript\"> \n//<![CDATA[\n" .
' $(function(){if(!document.getElementById){return;} ' .
" $('body').zoneclearFeedServer({blog_url:'" .
$blog_url . "',blog_id:'" . $blog_id . "'}); " .
" })\n" .
"//]]>\n</script>\n";
}
}

View File

@ -1,3 +1,8 @@
$(function(){
/*global $, dotclear */
'use strict';
$(() => {
$('.checkboxes-helpers').each(function(){dotclear.checkboxesHelpers(this);});
dotclear.condSubmit('#form-feeds td input[type=checkbox]', '#form-feeds #feeds-action');
dotclear.condSubmit('#form-entries td input[type=checkbox]', '#form-entries #feed-action');
});

View File

@ -1,7 +1,10 @@
$(function(){
/*global $, dotclear */
'use strict';
$(() => {
/* toogle admin form sidebar */
$('#zcfs h5').toggleWithLegend(
$('#zcfs').children().not('h5'),
{cookie:'dcx_zcfs_admin_form_sidebar',legend_click:true}
{user_pref:'dcx_zcfs_admin_form_sidebar',legend_click:true}
);
});

160
locales/fr/main.lang.php Normal file
View File

@ -0,0 +1,160 @@
<?php
/**
* @package Dotclear
*
* @copyright Olivier Meunier & Association Dotclear
* @copyright GPL-2.0-only
*/
#
# DOT NOT MODIFY THIS FILE !
#
use Dotclear\Helper\L10n;
L10n::$locales['List of feeds'] = 'Liste des sources';
L10n::$locales['Feed properties update'] = 'Mise à jour de flux';
L10n::$locales['Feed named "%s" point to "%s" has been updated by "%s"'] = 'Le flux nommé "%s" pointant vers "%s" a été mis à jour par "%s"';
L10n::$locales['Feed creation'] = 'Création de flux';
L10n::$locales['A new feed named "%s" point to "%s" was added by "%s"'] = 'Un nouveau flux nommé "%s" pointant vers "%s" a été ajouté par "%s"';
L10n::$locales['Feed status'] = 'Satut de flux';
L10n::$locales['Feed named "%s" point to "%s" has been set to "%s"'] = 'Le flux nommé "%s" pointant vers "%s" a été marqué comme "%s"';
L10n::$locales['Feed deletion'] = 'Suppression de flux';
L10n::$locales['Feed named "%s" point to "%s" has been deleted by "%s"'] = 'Le flux nommé "%s" pointant vers "%s" a été supprimé par "%s"';
L10n::$locales['Check feed update'] = 'Vérification de flux';
L10n::$locales['Feed named "%s" has been updated automatically'] = 'Le flux nommé "%s" pointant vers "%s" a été vérifié automatiquement';
L10n::$locales['%s feed disabled'] = [
'%s flux désactivé',
'%s flux désactivés',
];
L10n::$locales['Feeds server: Feeds'] = 'Serveur de flux : Flux';
L10n::$locales['Feed'] = 'Flux';
L10n::$locales['Feeds server: Entries'] = 'Serveur de flux : Billets';
L10n::$locales['Feed server'] = 'Serveur de flux';
L10n::$locales['feeds per page'] = 'flux par page';
L10n::$locales['edit feed'] = 'Modifier le flux';
L10n::$locales['Edit this feed'] = 'Modifier ce flux';
L10n::$locales['Feed source'] = 'Source du flux';
L10n::$locales['feed URL'] = 'URL du fil';
L10n::$locales['site URL'] = 'URL du site';
L10n::$locales['Update date'] = 'Date de mise à jour';
L10n::$locales['Disabled'] = 'Désactivé';
L10n::$locales['Enabled'] = 'Activé';
L10n::$locales['Every hour'] = 'Toutes les heures';
L10n::$locales['Every two hours'] = 'Toutes les deux heures';
L10n::$locales['Two times per day'] = 'Deux fois par jour';
L10n::$locales['Every day'] = 'Chaque jour';
L10n::$locales['Every two days'] = 'Tous les deux jours';
L10n::$locales['Keep source case'] = 'Garder la casse de la source';
L10n::$locales['First upper case'] = 'Premier caractère en majuscule';
L10n::$locales['All lower case'] = 'Tout en minuscule';
L10n::$locales['All upper case'] = 'Tout en majuscule';
L10n::$locales['Disable'] = 'Désactiver';
L10n::$locales['Before display'] = 'Avant l\'affichage';
L10n::$locales['After display'] = 'Après l\'affichage';
L10n::$locales['Through Ajax'] = 'À travers Ajax';
L10n::$locales['Configuration has been successfully updated.'] = 'La configuration a été mise à jour avec succés.';
L10n::$locales['Dotclear cache is not writable or not well configured!'] = 'Le cache de Dotclear n\'est pas accessible en écriture ou n\'est pas configuré !';
L10n::$locales['View the public list of feeds'] = 'Voir la page publique de la liste des flux';
L10n::$locales['Enable plugin'] = 'Activer le plugin';
L10n::$locales['Status of new posts:'] = 'Status des nouveaux billets :';
L10n::$locales['Owner of entries created by the feed server:'] = 'Propriétaire des billets créés par le serveur de flux :';
L10n::$locales['How to transform imported tags:'] = 'Comment tranformer la casse des mots-clés importés :';
L10n::$locales['Update feeds on public side:'] = 'Mettre à jour les flux depuis la partie publique :';
L10n::$locales['Number of feeds to update at one time:'] = 'Nombre de flux mis à jour à la fois :';
L10n::$locales['Keep active empty feeds'] = 'Garder actif les flux vides';
L10n::$locales['Enable public page'] = 'Activer la page publique';
L10n::$locales['Redirect to original post on:'] = 'Rediriger vers le billet original sur :';
L10n::$locales['Show full content on:'] = 'Afficher le contenu complet sur :';
L10n::$locales['Feeds'] = 'Flux de syndication';
L10n::$locales['Feeds server'] = 'Serveur de flux';
L10n::$locales['Back to feeds list'] = 'Retour à la liste des flux';
L10n::$locales['Feeds actions'] = 'Actions sur les flux';
L10n::$locales['Change update interval'] = 'Changer l\'interval de mise à jour';
L10n::$locales['Disable feed update'] = 'Désactiver la mise à jour du flux';
L10n::$locales['Enable feed update'] = 'Activer la mise à jour du flux';
L10n::$locales['Reset last update'] = 'Remettre à zéro le chronomètre de mise à jour';
L10n::$locales['Update (check) feed'] = 'Mettre à jour (vérifier) le flux';
L10n::$locales['Delete related posts'] = 'Effacer les billets liés';
L10n::$locales['Delete feed (without related posts)'] = 'Effacer le flux (sans effacer les billets liés)';
L10n::$locales['No feeds selected'] = 'Aucun flux selectionné';
L10n::$locales['Entries have been successfully deleted.'] = 'Les billets ont été effacé avec succès.';
L10n::$locales['%d feed has been successfully updated.'] = [
'%s flux a été mis à jour.',
'%s flux ont été mis à jour.',
];
L10n::$locales['Last update of %s feed successfully reseted.'] = [
'La date de dernière mise à jour de %s flux a été remis à zéro.',
'Les date de dernière mise à jour de %s flux ont été remis à zéro.',
];
L10n::$locales['Category of %s feed successfully changed.'] = [
'La catégorie de %s flux a été changé.',
'Les catégories de %s flux ont été changé.',
];
L10n::$locales['Update frequency of %s feed successfully changed.'] = [
'La fréquence de mise à jour de %s flux a été changé.',
'Les fréquences de mise à jour de %s flux ont été changé.',
];
L10n::$locales['Change update frequency for this selection'] = 'Changer la fréquence de mise à jour pour la selection';
L10n::$locales['Frequency:'] = 'Fréquence :';
L10n::$locales['No feeds matches the filter'] = 'Aucun flux correspondant au filtre.';
L10n::$locales['No feeds'] = 'Aucun flux';
L10n::$locales['List of %s feeds matching the filter.'] = 'Liste des %s flux correspondant au filtre.';
L10n::$locales['List of feeds. (%s)'] = 'Liste des flux. (%s)';
L10n::$locales['enabled'] = 'activé';
L10n::$locales['disabled'] = 'désactivé';
L10n::$locales['never'] = 'jamais';
L10n::$locales['View entries'] = 'Voir les billets';
L10n::$locales['Module is not wel configured'] = 'Le module n\'est pas configuré correctement';
L10n::$locales['Feeds list'] = 'Liste de flux';
L10n::$locales['New feed'] = 'Nouveau flux';
L10n::$locales['Selected feeds action:'] = 'Action sur les flux sélectionnés :';
L10n::$locales['Record with same feed URL already exists.'] = 'Un enregistrement avec la même URL de flux existe déjà.';
L10n::$locales['You must provide a name.'] = 'Vous devez indiquer un nom.';
L10n::$locales['You must provide an owner.'] = 'Vous devez indiquer un propriétaire.';
L10n::$locales['You must provide valid site URL.'] = 'Vous devez donner une URL de site valide.';
L10n::$locales['You must provide valid feed URL.'] = 'Vous devez donner une URL de flux valide.';
L10n::$locales['You must provide valid category.'] = 'Vous devez donner une catégorie valide.';
L10n::$locales['Failed to save feed.'] = 'Impossible de sauvegarder le flux.';
L10n::$locales['Feed successfully created.'] = 'Flux créé avec succcès.';
L10n::$locales['Edit feed'] = 'Edition de flux';
L10n::$locales['Edit feed "%s"'] = 'Modifier le flux "%s"';
L10n::$locales['Feed information'] = 'Information sur le flux';
L10n::$locales['Owner:'] = 'Propriétaire :';
L10n::$locales['Site URL:'] = 'URL du site :';
L10n::$locales['Tweeter or Identica ident:'] = 'Identifiant Twiiter ou Identi.ca :';
L10n::$locales['Local settings'] = 'Paramètres locaux';
L10n::$locales['Update:'] = 'Mise à jour :';
L10n::$locales['Import tags from feed'] = 'Importer les tags depuis le flux';
L10n::$locales['This feed does not exist.'] = 'Ce flux n\'existe pas.';
L10n::$locales['next feed'] = 'flux suivant';
L10n::$locales['previous feed'] = 'flux précédent';
L10n::$locales['No entries matches the filter'] = 'Aucun billet correspondant au filtre.';
L10n::$locales['No entries'] = 'Aucun billet';
L10n::$locales['List of entries. (%s)'] = 'Liste des billets. (%s)';
L10n::$locales['Original post on <a href="%s">%s</a>'] = 'Billet original sur <a href="%s">%s</a>';
L10n::$locales['Read more details about this feed'] = 'Lire plus de détails à propos de ce flux';
L10n::$locales['Feeds server: sources'] = 'Serveur de flux : les sources';
L10n::$locales['List sources of feeds'] = 'Liste des sources du flux';
L10n::$locales['Feeds sources'] = 'Sources des flux';
L10n::$locales['Create date'] = 'Date de création';
L10n::$locales['Limit:'] = 'Limite :';
L10n::$locales['Link to the list of sources:'] = 'Lien vers la liste des sources :';
L10n::$locales['All sources'] = 'Toutes les sources';
L10n::$locales['Feeds server: numbers'] = 'Serveur de flux : les chiffres';
L10n::$locales['Show some numbers about feeds'] = 'Afficher quelques chiffres à propose du flux';
L10n::$locales['Feeds numbers'] = 'Le flux en chiffres';
L10n::$locales['Show feeds count'] = 'Afficher le nombre de flux';
L10n::$locales['Title for feeds count:'] = 'Titre pour le nombre de flux :';
L10n::$locales['Feeds:'] = 'Flux :';
L10n::$locales['Show entries count'] = 'Afficher le nombre de billets';
L10n::$locales['Title for entries count:'] = 'Titre pour le nombre de billets :';
L10n::$locales['Entries:'] = 'Billets :';
L10n::$locales['one source'] = 'une source';
L10n::$locales['no sources'] = 'aucune source';
L10n::$locales['one entry'] = 'une publication';
L10n::$locales['no entries'] = 'aucune publication';
L10n::$locales['Entries pages'] = 'Pages des billets';
L10n::$locales['Tags pages'] = 'Pages des mots-clés';
L10n::$locales['Archives pages'] = 'Pages des archives';
L10n::$locales['Category pages'] = 'Pages de catégories';
L10n::$locales['Mix your blog with a feeds planet'] = 'Mixer votre blog avec un planet';

View File

@ -1,399 +1,560 @@
# Language: Français
# Module: zoneclearFeedServer - 2022.12.11.00001
# Date: 2023-05-07 22:36:31
# Author: Jean-Christian Denis
# Translated with translater 2023.04.23
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: zoneclearFeedServer 2022.11.26\n"
"Project-Id-Version: zoneclearFeedServer 2022.12.11.00001\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2022-12-10T22:19:59+00:00\n"
"PO-Revision-Date: 2023-05-07T22:36:31+00:00\n"
"Last-Translator: Jean-Christian Denis\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
msgid "Feeds server"
msgstr "Serveur de flux"
msgid "Disable"
msgstr "Désactiver"
msgid "Before display"
msgstr "Avant l'affichage"
msgid "After display"
msgstr "Après l'affichage"
msgid "Through Ajax"
msgstr "À travers Ajax"
msgid "Keep source case"
msgstr "Garder la casse de la source"
msgid "First upper case"
msgstr "Premier caractère en majuscule"
msgid "All lower case"
msgstr "Tout en minuscule"
msgid "All upper case"
msgstr "Tout en majuscule"
msgid "Dotclear cache is not writable or not well configured!"
msgstr "Le cache de Dotclear n'est pas accessible en écriture ou n'est pas configuré !"
msgid "Enable plugin"
msgstr "Activer le plugin"
msgid "View the public list of feeds"
msgstr "Voir la page publique de la liste des flux"
msgid "Rules"
msgstr "Règles"
msgid "Status of new posts:"
msgstr "Status des nouveaux billets :"
msgid "Owner of entries created by zoneclearFeedServer:"
msgstr "Propriétaire des billets créés par zoneclearFeedServer :"
msgid "How to transform imported tags:"
msgstr "Comment tranformer la casse des mots-clés importés :"
msgid "Update feeds on public side:"
msgstr "Mettre à jour les flux depuis la partie publique :"
msgid "Update %s feed(s) at a time."
msgstr "Mettre à jour %s flux à la fois."
msgid "Keep active empty feeds"
msgstr "Garder actif les flux vides"
msgid "Enable public page"
msgstr "Activer la page publique"
msgid "Redirect to original post on:"
msgstr "Rediriger vers le billet original sur :"
msgid "Show full content on:"
msgstr "Afficher le contenu complet sur :"
#: default-templates/currywurst/zcfeeds.html:4
#: default-templates/mustek/zcfeeds.html:4
#: src/Frontend.php:52
msgid "List of feeds"
msgstr "Liste des sources"
msgid "Feeds server: sources"
msgstr "Serveur de flux : les sources"
msgid "List sources of feeds"
msgstr "Liste des sources du flux"
msgid "Feeds sources"
msgstr "Sources des flux"
msgid "Create date"
msgstr "Date de création"
msgid "Limit:"
msgstr "Limite :"
msgid "Link to the list of sources:"
msgstr "Lien vers la liste des sources :"
msgid "All sources"
msgstr "Toutes les sources"
msgid "Feeds server: numbers"
msgstr "Serveur de flux : les chiffres"
msgid "Show some numbers about feeds"
msgstr "Afficher quelques chiffres à propose du flux"
msgid "Feeds numbers"
msgstr "Le flux en chiffres"
msgid "Show feeds count"
msgstr "Afficher le nombre de flux"
msgid "Title for feeds count:"
msgstr "Titre pour le nombre de flux :"
msgid "Feeds:"
msgstr "Flux :"
msgid "Show entries count"
msgstr "Afficher le nombre de billets"
msgid "Title for entries count:"
msgstr "Titre pour le nombre de billets :"
msgid "Entries:"
msgstr "Billets :"
msgid "one source"
msgstr "une source"
msgid "no sources"
msgstr "aucune source"
msgid "one entry"
msgstr "une publication"
msgid "no entries"
msgstr "aucune publication"
msgid "feed creation"
msgstr "création d'un flux"
msgid "A new feed named \"%s\" point to \"%s\" was added by \"%s\""
msgstr "Un nouveau flux nommé \"%s\" pointant vers \"%s\" a été ajouté par \"%s\""
msgid "updating feed info"
msgstr "mise à jour des informations du flux (administration)"
#: src/ActivityReportActions.php:36
msgid "Feed properties update"
msgstr "Mise à jour de flux"
#: src/ActivityReportActions.php:37
msgid "Feed named \"%s\" point to \"%s\" has been updated by \"%s\""
msgstr "Le flux nommé \"%s\" pointant vers \"%s\" a été mis à jour par \"%s\""
msgid "updating feed records"
msgstr "mise à jour des enregistrements du flux (automatique)"
#: src/ActivityReportActions.php:63
msgid "Feed creation"
msgstr "Création de flux"
msgid "Records of the feed named \"%s\" have been updated automatically"
msgstr "Les enregistrements du flux nommé \"%s\" ont été automatiquement mis à jour"
#: src/ActivityReportActions.php:64
msgid "A new feed named \"%s\" point to \"%s\" was added by \"%s\""
msgstr "Un nouveau flux nommé \"%s\" pointant vers \"%s\" a été ajouté par \"%s\""
msgid "feed deletion"
msgstr "suppression d'un flux"
msgid "Feed named \"%s\" point to \"%s\" has been deleted by \"%s\""
msgstr "Le flux nommé \"%s\" pointant vers \"%s\" a été supprimé par \"%s\""
msgid "feed status"
msgstr "status d'un flux"
#: src/ActivityReportActions.php:83
msgid "Feed status"
msgstr "Satut de flux"
#: src/ActivityReportActions.php:84
msgid "Feed named \"%s\" point to \"%s\" has been set to \"%s\""
msgstr "Le flux nommé \"%s\" pointant vers \"%s\" a été marqué comme \"%s\""
msgid "Update date"
msgstr "Date de mise à jour"
#: src/ActivityReportActions.php:105
msgid "Feed deletion"
msgstr "Suppression de flux"
#: src/ActivityReportActions.php:106
msgid "Feed named \"%s\" point to \"%s\" has been deleted by \"%s\""
msgstr "Le flux nommé \"%s\" pointant vers \"%s\" a été supprimé par \"%s\""
#: src/ActivityReportActions.php:132
msgid "Check feed update"
msgstr "Vérification de flux"
#: src/ActivityReportActions.php:133
msgid "Feed named \"%s\" has been updated automatically"
msgstr "Le flux nommé \"%s\" pointant vers \"%s\" a été vérifié automatiquement"
#: src/BackendBehaviors.php:68
msgid "%s feed disabled"
msgid_plural "%s feeds disabled"
msgstr[0] "%s flux désactivé"
msgstr[1] "%s flux désactivés"
#: src/BackendBehaviors.php:85
msgid "Feeds server: Feeds"
msgstr "Serveur de flux : Flux"
#: src/BackendBehaviors.php:87
#: src/FeedsList.php:62
#: src/ManageFeed.php:236
msgid "Feed"
msgstr "Flux"
#: src/BackendBehaviors.php:95
msgid "Feeds server: Entries"
msgstr "Serveur de flux : Billets"
#: src/BackendBehaviors.php:103
msgid "Feed server"
msgstr "Serveur de flux"
#: src/BackendBehaviors.php:117
msgid "feeds per page"
msgstr "flux par page"
msgid "entries per page"
msgstr "billets par page"
#: src/BackendBehaviors.php:149
msgid "edit feed"
msgstr "Modifier le flux"
#: src/BackendBehaviors.php:225
msgid "Edit this feed"
msgstr "Modifier ce flux"
#: src/BackendBehaviors.php:231
msgid "Feed source"
msgstr "Source du flux"
#: src/BackendBehaviors.php:238
msgid "feed URL"
msgstr "URL du fil"
#: src/BackendBehaviors.php:242
msgid "site URL"
msgstr "URL du site"
#: src/Combo.php:35
msgid "Update date"
msgstr "Date de mise à jour"
#: src/Combo.php:60
msgid "Disabled"
msgstr "Désactivé"
#: src/Combo.php:61
msgid "Enabled"
msgstr "Activé"
#: src/Combo.php:82
msgid "Every hour"
msgstr "Toutes les heures"
#: src/Combo.php:83
msgid "Every two hours"
msgstr "Toutes les deux heures"
#: src/Combo.php:84
msgid "Two times per day"
msgstr "Deux fois par jour"
#: src/Combo.php:85
msgid "Every day"
msgstr "Chaque jour"
#: src/Combo.php:86
msgid "Every two days"
msgstr "Tous les deux jours"
#: src/Combo.php:97
msgid "Keep source case"
msgstr "Garder la casse de la source"
#: src/Combo.php:98
msgid "First upper case"
msgstr "Premier caractère en majuscule"
#: src/Combo.php:99
msgid "All lower case"
msgstr "Tout en minuscule"
#: src/Combo.php:100
msgid "All upper case"
msgstr "Tout en majuscule"
#: src/Combo.php:110
msgid "Disable"
msgstr "Désactiver"
#: src/Combo.php:111
msgid "Before display"
msgstr "Avant l'affichage"
#: src/Combo.php:112
msgid "After display"
msgstr "Après l'affichage"
#: src/Combo.php:113
msgid "Through Ajax"
msgstr "À travers Ajax"
#: src/Config.php:69
msgid "Configuration has been successfully updated."
msgstr "La configuration a été mise à jour avec succés."
#: src/Config.php:101
msgid "Dotclear cache is not writable or not well configured!"
msgstr "Le cache de Dotclear n'est pas accessible en écriture ou n'est pas configuré !"
#: src/Config.php:108
msgid "View the public list of feeds"
msgstr "Voir la page publique de la liste des flux"
#: src/Config.php:146
msgid "Enable plugin"
msgstr "Activer le plugin"
#: src/Config.php:158
msgid "Status of new posts:"
msgstr "Status des nouveaux billets :"
#: src/Config.php:166
msgid "Owner of entries created by the feed server:"
msgstr "Propriétaire des billets créés par le serveur de flux :"
#: src/Config.php:174
msgid "How to transform imported tags:"
msgstr "Comment tranformer la casse des mots-clés importés :"
#: src/Config.php:186
msgid "Update feeds on public side:"
msgstr "Mettre à jour les flux depuis la partie publique :"
#: src/Config.php:194
msgid "Number of feeds to update at one time:"
msgstr "Nombre de flux mis à jour à la fois :"
#: src/Config.php:205
msgid "Keep active empty feeds"
msgstr "Garder actif les flux vides"
#: src/Config.php:213
msgid "Enable public page"
msgstr "Activer la page publique"
#: src/Config.php:225
msgid "Redirect to original post on:"
msgstr "Rediriger vers le billet original sur :"
#: src/Config.php:232
msgid "Show full content on:"
msgstr "Afficher le contenu complet sur :"
#: src/FeedsActions.php:46
msgid "Feeds"
msgstr "Flux de syndication"
#: src/FeedsActions.php:61
#: src/FeedsDefaultActions.php:271
msgid "Feeds server"
msgstr "Serveur de flux"
#: src/FeedsActions.php:67
msgid "Back to feeds list"
msgstr "Retour à la liste des flux"
#: src/FeedsActions.php:82
msgid "Feeds actions"
msgstr "Actions sur les flux"
#: src/FeedsDefaultActions.php:49
msgid "Change update interval"
msgstr "Changer l'interval de mise à jour"
#: src/FeedsDefaultActions.php:53
msgid "Disable feed update"
msgstr "Désactiver la mise à jour du flux"
#: src/FeedsDefaultActions.php:57
msgid "Enable feed update"
msgstr "Activer la mise à jour du flux"
#: src/FeedsDefaultActions.php:61
msgid "Reset last update"
msgstr "Remettre à zéro le chronomètre de mise à jour"
#: src/FeedsDefaultActions.php:65
msgid "Update (check) feed"
msgstr "Mettre à jour (vérifier) le flux"
#: src/FeedsDefaultActions.php:69
msgid "Delete related posts"
msgstr "Effacer les billets liés"
#: src/FeedsDefaultActions.php:73
msgid "Delete feed (without related posts)"
msgstr "Effacer le flux (sans effacer les billets liés)"
#: src/FeedsDefaultActions.php:87
msgid "No feeds selected"
msgstr "Aucun flux selectionné"
#: src/FeedsDefaultActions.php:154
msgid "Entries have been successfully deleted."
msgstr "Les billets ont été effacé avec succès."
#: src/FeedsDefaultActions.php:205
msgid "%d feed has been successfully updated."
msgid_plural "%d feeds have been successfully updated."
msgstr[0] "%s flux a été mis à jour."
msgstr[1] "%s flux ont été mis à jour."
#: src/FeedsDefaultActions.php:233
msgid "Last update of %s feed successfully reseted."
msgid_plural "Last update of %s feeds successfully reseted."
msgstr[0] "La date de dernière mise à jour de %s flux a été remis à zéro."
msgstr[1] "Les date de dernière mise à jour de %s flux ont été remis à zéro."
#: src/FeedsDefaultActions.php:263
msgid "Category of %s feed successfully changed."
msgid_plural "Category of %s feeds successfully changed."
msgstr[0] "La catégorie de %s flux a été changé."
msgstr[1] "Les catégories de %s flux ont été changé."
#: src/FeedsDefaultActions.php:329
msgid "Update frequency of %s feed successfully changed."
msgid_plural "Update frequency of %s feeds successfully changed."
msgstr[0] "La fréquence de mise à jour de %s flux a été changé."
msgstr[1] "Les fréquences de mise à jour de %s flux ont été changé."
#: src/FeedsDefaultActions.php:340
msgid "Change update frequency for this selection"
msgstr "Changer la fréquence de mise à jour pour la selection"
#: src/FeedsDefaultActions.php:355
msgid "Frequency:"
msgstr "Fréquence :"
msgid "No entries matches the filter"
msgstr "Aucun billet correspondant au filtre."
msgid "No entries"
msgstr "Aucun billet"
msgid "Feeds"
msgstr "Flux de syndication"
msgid "Back to feeds list"
msgstr "Retour à la liste des flux"
msgid "Feeds actions"
msgstr "Actions sur les flux"
#: src/FeedsList.php:44
msgid "No feeds matches the filter"
msgstr "Aucun flux correspondant au filtre."
#: src/FeedsList.php:45
msgid "No feeds"
msgstr "Aucun flux"
#: src/FeedsList.php:93
msgid "List of %s feeds matching the filter."
msgstr "Liste des %s flux correspondant au filtre."
msgid "List of feeds (%s)"
msgstr "Liste des flux (%s)"
#: src/FeedsList.php:94
msgid "List of feeds. (%s)"
msgstr "Liste des flux. (%s)"
#: src/FeedsList.php:110
msgid "enabled"
msgstr "activé"
#: src/FeedsList.php:110
msgid "disabled"
msgstr "désactivé"
#: src/FeedsList.php:161
msgid "never"
msgstr "jamais"
#: src/FeedsList.php:169
msgid "View entries"
msgstr "Voir les billets"
msgid "Original post on <a href=\"%s\">%s</a>"
msgstr "Billet original sur <a href=\"%s\">%s</a>"
#: src/Manage.php:68
msgid "Module is not wel configured"
msgstr "Le module n'est pas configuré correctement"
msgid "Read more details about this feed"
msgstr "Lire plus de détails à propos de ce flux"
msgid "Disabled"
msgstr "Désactivé"
msgid "Enabled"
msgstr "Activé"
msgid "Every hour"
msgstr "Toutes les heures"
msgid "Every two hours"
msgstr "Toutes les deux heures"
msgid "Two times per day"
msgstr "Deux fois par jour"
msgid "Every day"
msgstr "Chaque jour"
msgid "Every two days"
msgstr "Tous les deux jours"
msgid "Entries pages"
msgstr "Pages des billets"
msgid "Tags pages"
msgstr "Pages des mots-clés"
msgid "Archives pages"
msgstr "Pages des archives"
msgid "Category pages"
msgstr "Pages de catégories"
msgid "This feed does not exist."
msgstr "Ce flux n'existe pas."
msgid "next feed"
msgstr "flux suivant"
msgid "previous feed"
msgstr "flux précédent"
msgid "Record with same feed URL already exists."
msgstr "Un enregistrement avec la même URL de flux existe déjà."
msgid "You must provide a name."
msgstr "Vous devez indiquer un nom."
msgid "You must provide an owner."
msgstr "Vous devez indiquer un propriétaire."
msgid "You must provide valid site URL."
msgstr "Vous devez donner une URL de site valide."
msgid "You must provide valid feed URL."
msgstr "Vous devez donner une URL de flux valide."
msgid "You must provide valid category."
msgstr "Vous devez donner une catégorie valide."
msgid "Feed successfully updated."
msgstr "Flux mis à jour avec succès."
msgid "Feed successfully created."
msgstr "Flux créé avec succcès."
msgid "Edit feed"
msgstr "Edition de flux"
#: src/Manage.php:157
msgid "Feeds list"
msgstr "Liste de flux"
#: src/Manage.php:167
#: src/ManageFeed.php:212
msgid "New feed"
msgstr "Nouveau flux"
#: src/Manage.php:195
msgid "Selected feeds action:"
msgstr "Action sur les flux sélectionnés :"
#: src/ManageFeed.php:78
msgid "Record with same feed URL already exists."
msgstr "Un enregistrement avec la même URL de flux existe déjà."
#: src/ManageFeed.php:81
msgid "You must provide a name."
msgstr "Vous devez indiquer un nom."
#: src/ManageFeed.php:84
msgid "You must provide an owner."
msgstr "Vous devez indiquer un propriétaire."
#: src/ManageFeed.php:87
msgid "You must provide valid site URL."
msgstr "Vous devez donner une URL de site valide."
#: src/ManageFeed.php:90
msgid "You must provide valid feed URL."
msgstr "Vous devez donner une URL de flux valide."
#: src/ManageFeed.php:93
msgid "You must provide valid category."
msgstr "Vous devez donner une catégorie valide."
#: src/ManageFeed.php:105
msgid "Failed to save feed."
msgstr "Impossible de sauvegarder le flux."
#: src/ManageFeed.php:109
msgid "Feed successfully created."
msgstr "Flux créé avec succcès."
#: src/ManageFeed.php:212
msgid "Edit feed"
msgstr "Edition de flux"
#: src/ManageFeed.php:215
msgid "Edit feed \"%s\""
msgstr "Modifier le flux \"%s\""
#: src/ManageFeed.php:248
msgid "Feed information"
msgstr "Information sur le flux"
#: src/ManageFeed.php:264
msgid "Owner:"
msgstr "Propriétaire :"
msgid "Tweeter or Identica ident:"
msgstr "Identifiant Twiiter ou Identi.ca :"
#: src/ManageFeed.php:276
msgid "Site URL:"
msgstr "URL du site :"
#: src/ManageFeed.php:322
msgid "Tweeter or Identica ident:"
msgstr "Identifiant Twiiter ou Identi.ca :"
#: src/ManageFeed.php:334
msgid "Local settings"
msgstr "Paramètres locaux"
#: src/ManageFeed.php:358
msgid "Update:"
msgstr "Mise à jour :"
#: src/ManageFeed.php:379
msgid "Import tags from feed"
msgstr "Importer les tags depuis le flux"
msgid "Selected feeds action:"
msgstr "Action sur les flux sélectionnés :"
#: src/ManageFeedVars.php:85
msgid "This feed does not exist."
msgstr "Ce flux n'existe pas."
#: src/ManageFeedVars.php:115
msgid "next feed"
msgstr "flux suivant"
#: src/ManageFeedVars.php:137
msgid "previous feed"
msgstr "flux précédent"
#: src/PostsList.php:43
msgid "No entries matches the filter"
msgstr "Aucun billet correspondant au filtre."
#: src/PostsList.php:44
msgid "No entries"
msgstr "Aucun billet"
#: src/PostsList.php:92
msgid "List of entries. (%s)"
msgstr "Liste des billets. (%s)"
#: src/RsExtPosts.php:146
msgid "Original post on <a href=\"%s\">%s</a>"
msgstr "Billet original sur <a href=\"%s\">%s</a>"
#: src/RsExtPosts.php:157
msgid "Read more details about this feed"
msgstr "Lire plus de détails à propos de ce flux"
#: src/Widgets.php:38
msgid "Feeds server: sources"
msgstr "Serveur de flux : les sources"
#: src/Widgets.php:41
msgid "List sources of feeds"
msgstr "Liste des sources du flux"
#: src/Widgets.php:44
msgid "Feeds sources"
msgstr "Sources des flux"
#: src/Widgets.php:54
msgid "Create date"
msgstr "Date de création"
#: src/Widgets.php:69
msgid "Limit:"
msgstr "Limite :"
#: src/Widgets.php:75
msgid "Link to the list of sources:"
msgstr "Lien vers la liste des sources :"
#: src/Widgets.php:76
msgid "All sources"
msgstr "Toutes les sources"
#: src/Widgets.php:87
msgid "Feeds server: numbers"
msgstr "Serveur de flux : les chiffres"
#: src/Widgets.php:90
msgid "Show some numbers about feeds"
msgstr "Afficher quelques chiffres à propose du flux"
#: src/Widgets.php:93
msgid "Feeds numbers"
msgstr "Le flux en chiffres"
#: src/Widgets.php:103
msgid "Show feeds count"
msgstr "Afficher le nombre de flux"
#: src/Widgets.php:109
msgid "Title for feeds count:"
msgstr "Titre pour le nombre de flux :"
#: src/Widgets.php:110
msgid "Feeds:"
msgstr "Flux :"
#: src/Widgets.php:115
msgid "Show entries count"
msgstr "Afficher le nombre de billets"
#: src/Widgets.php:121
msgid "Title for entries count:"
msgstr "Titre pour le nombre de billets :"
#: src/Widgets.php:122
msgid "Entries:"
msgstr "Billets :"
#: src/Widgets.php:219
msgid "one source"
msgstr "une source"
#: src/Widgets.php:219
msgid "no sources"
msgstr "aucune source"
#: src/Widgets.php:250
msgid "one entry"
msgstr "une publication"
#: src/Widgets.php:250
msgid "no entries"
msgstr "aucune publication"
#: src/ZoneclearFeedServer.php:964
msgid "Entries pages"
msgstr "Pages des billets"
#: src/ZoneclearFeedServer.php:965
msgid "Tags pages"
msgstr "Pages des mots-clés"
#: src/ZoneclearFeedServer.php:966
msgid "Archives pages"
msgstr "Pages des archives"
#: src/ZoneclearFeedServer.php:967
msgid "Category pages"
msgstr "Pages de catégories"
msgid "Mix your blog with a feeds planet"
msgstr "Mixer votre blog avec un planet"

View File

@ -10,138 +10,137 @@
* @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);
namespace Dotclear\Plugin\zoneclearFeedServer;
use dcCore;
use Dotclear\Database\Cursor;
use Dotclear\Plugin\activityReport\{
Action,
ActivityReport,
Group
};
/**
* Add feeds actions to the plugin activity report.
*/
class ActivityReportActions
{
public static function init(): void
{
$group = new Group(My::id(), My::name());
$group->add(new Action(
'updateFeed',
__('Feed properties update'),
__('Feed named "%s" point to "%s" has been updated by "%s"'),
'zoneclearFeedServerAfterUpdateFeed',
function (Cursor $cur, int $id): void {
$user = dcCore::app()->auth?->getInfo('user_cn');
if (!is_string($user)) {
return;
}
class zcfsActivityReportBehaviors
{
public static function init()
{
# This file is used with plugin activityReport
dcCore::app()->activityReport->addGroup(
'zoneclearFeedServer',
__('Plugin zoneclearFeedServer')
);
$rs = ZoneclearFeedServer::instance()->getFeeds(['feed_id' => $id]);
if ($rs->isEmpty()) {
return;
}
$row = new FeedRow($rs);
# from BEHAVIOR zoneclearFeedServerAfterAddFeed in zoneclearFeedServer/inc/class.zoneclear.feed.server.php
dcCore::app()->activityReport->addAction(
'zoneclearFeedServer',
'create',
__('feed creation'),
$logs = [
$row->name,
$row->feed,
$user,
];
ActivityReport::instance()->addLog(My::id(), 'updateFeed', $logs);
}
));
$group->add(new Action(
'addFeed',
__('Feed creation'),
__('A new feed named "%s" point to "%s" was added by "%s"'),
'zoneclearFeedServerAfterAddFeed',
function ($cur) {
function (Cursor $cur, int $id): void {
$user = dcCore::app()->auth?->getInfo('user_cn');
if (!is_string($user)) {
return;
}
$logs = [
$cur->feed_name,
$cur->feed_feed,
dcCore::app()->auth->getInfo('user_cn'),
$cur->getField('feed_name'),
$cur->getField('feed_feed'),
$user,
];
dcCore::app()->activityReport->addLog(
'zoneclearFeedServer',
'create',
$logs
);
ActivityReport::instance()->addLog(My::id(), 'addFeed', $logs);
}
);
# from BEHAVIOR zoneclearFeedServerAfterUpdFeed in in zoneclearFeedServer/inc/class.zoneclear.feed.server.php
dcCore::app()->activityReport->addAction(
'zoneclearFeedServer',
'updatefeedinfo',
__('updating feed info'),
__('Feed named "%s" point to "%s" has been updated by "%s"'),
'zoneclearFeedServerAfterUpdFeed',
function ($cur, $id) {
if (defined('DC_CONTEXT_ADMIN')) {
$zc = new zoneclearFeedServer();
$rs = $zc->getFeeds(['feed_id' => $id]);
));
$logs = [
$rs->feed_name,
$rs->feed_feed,
dcCore::app()->auth->getInfo('user_cn'),
];
dcCore::app()->activityReport->addLog(
'zoneclearFeedServer',
'updatefeedinfo',
$logs
);
}
}
);
# from BEHAVIOR zoneclearFeedServerAfterUpdFeed in in zoneclearFeedServer/inc/class.zoneclear.feed.server.php
dcCore::app()->activityReport->addAction(
'zoneclearFeedServer',
'updatefeedrecords',
__('updating feed records'),
__('Records of the feed named "%s" have been updated automatically'),
'zoneclearFeedServerAfterUpdFeed',
function ($cur, $id) {
if (!defined('DC_CONTEXT_ADMIN')) {
$zc = new zoneclearFeedServer();
$rs = $zc->getFeeds(['feed_id' => $id]);
$logs = [
$rs->feed_name,
];
dcCore::app()->activityReport->addLog(
'zoneclearFeedServer',
'updatefeedrecords',
$logs
);
}
}
);
# from BEHAVIOR zoneclearFeedServerAfterDelFeed in in zoneclearFeedServer/inc/class.zoneclear.feed.server.php
dcCore::app()->activityReport->addAction(
'zoneclearFeedServer',
'delete',
__('feed deletion'),
__('Feed named "%s" point to "%s" has been deleted by "%s"'),
'zoneclearFeedServerAfterDelFeed',
function ($id) {
$zc = new zoneclearFeedServer();
$rs = $zc->getFeeds(['feed_id' => $id]);
$logs = [
$rs->feed_name,
$rs->feed_feed,
dcCore::app()->auth->getInfo('user_cn'),
];
dcCore::app()->activityReport->addLog(
'zoneclearFeedServer',
'delete',
$logs
);
}
);
# from BEHAVIOR zoneclearFeedServerAfterEnableFeed in in zoneclearFeedServer/inc/class.zoneclear.feed.server.php
dcCore::app()->activityReport->addAction(
'zoneclearFeedServer',
'status',
__('feed status'),
$group->add(new Action(
'enableFeed',
__('Feed status'),
__('Feed named "%s" point to "%s" has been set to "%s"'),
'zoneclearFeedServerAfterEnableFeed',
function ($id, $enable, $time) {
$zc = new zoneclearFeedServer();
$rs = $zc->getFeeds(['feed_id' => $id]);
function (int $id, bool $enable, int $time): void {
$rs = ZoneclearFeedServer::instance()->getFeeds(['feed_id' => $id]);
if ($rs->isEmpty()) {
return;
}
$row = new FeedRow($rs);
$logs = [
$rs->feed_name,
$rs->feed_feed,
$enable ? 'enable' : 'disable',
$row->name,
$row->feed,
$enable ? 'enabled' : 'disabled',
];
dcCore::app()->activityReport->addLog(
'zoneclearFeedServer',
'status',
$logs
);
ActivityReport::instance()->addLog(My::id(), 'enableFeed', $logs);
}
);
));
$group->add(new Action(
'deleteFeed',
__('Feed deletion'),
__('Feed named "%s" point to "%s" has been deleted by "%s"'),
'zoneclearFeedServerBeforeDeleteFeed',
function (int $id): void {
$rs = ZoneclearFeedServer::instance()->getFeeds(['feed_id' => $id]);
if ($rs->isEmpty()) {
return;
}
$row = new FeedRow($rs);
$user = dcCore::app()->auth?->getInfo('user_cn');
if (!is_string($user)) {
return;
}
$logs = [
$row->name,
$row->feed,
$user,
];
ActivityReport::instance()->addLog(My::id(), 'deleteFeed', $logs);
}
));
$group->add(new Action(
'checkFeedUpdate',
__('Check feed update'),
__('Feed named "%s" has been updated automatically'),
'zoneclearFeedServerAfterCheckFeedUpdate',
function (FeedRow $row): void {
$logs = [
$row->name,
];
ActivityReport::instance()->addLog(My::id(), 'checkFeedUpdate', $logs);
}
));
ActivityReport::instance()->groups->add($group);
}
}

View File

@ -10,46 +10,97 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
if (!defined('DC_CONTEXT_ADMIN')) {
return null;
declare(strict_types=1);
namespace Dotclear\Plugin\zoneclearFeedServer;
use dcAdmin;
use dcCore;
use dcPage;
use dcMenu;
use dcNsProcess;
/**
* Backend prepend.
*/
class Backend extends dcNsProcess
{
public static function init(): bool
{
static::$init = defined('DC_CONTEXT_ADMIN')
&& My::phpCompliant();
return static::$init;
}
dcCore::app()->blog->settings->addNamespace(basename(__DIR__));
public static function process(): bool
{
if (!static::$init) {
return false;
}
require_once __DIR__ . '/_widgets.php';
// behaviors that will be always loaded
dcCore::app()->addBehaviors([
// Allways take care to delete related info about feed post in meta table
'adminBeforePostDelete' => function (int $post_id): void {
ZoneclearFeedServer::instance()::deletePostsMeta($post_id);
},
// widgets registration
'initWidgets' => [Widgets::class, 'init'],
// add Uninstaller cleaner for special direct action
'UninstallerCleanersConstruct' => [UninstallCleaner::class, 'init'],
]);
$perm = dcCore::app()->auth->check(dcCore::app()->auth->makePermissions([
dcAuth::PERMISSION_CONTENT_ADMIN,
// nullsafe
if (is_null(dcCore::app()->auth)
|| is_null(dcCore::app()->blog)
|| is_null(dcCore::app()->adminurl)
) {
return false;
}
// not active
if (!dcCore::app()->blog->settings->get(My::id())->get('active')
|| '' == dcCore::app()->blog->settings->get(My::id())->get('user')
) {
return false;
}
// get user perm
$has_perm = dcCore::app()->auth->check(dcCore::app()->auth->makePermissions([
dcCore::app()->auth::PERMISSION_CONTENT_ADMIN,
]), dcCore::app()->blog->id);
if (dcCore::app()->blog->settings->__get(basename(__DIR__))->active
&& '' != dcCore::app()->blog->settings->__get(basename(__DIR__))->user
) {
// add sidebar menu icon
if ((dcCore::app()->menu[dcAdmin::MENU_PLUGINS] instanceof dcMenu)) {
dcCore::app()->menu[dcAdmin::MENU_PLUGINS]->addItem(
__('Feeds server'),
dcCore::app()->adminurl->get('admin.plugin.' . basename(__DIR__)),
dcPage::getPF(basename(__DIR__) . '/icon.svg'),
My::name(),
dcCore::app()->adminurl->get('admin.plugin.' . My::id()),
dcPage::getPF(My::id() . '/icon.svg'),
preg_match(
'/' . preg_quote(dcCore::app()->adminurl->get('admin.plugin.' . basename(__DIR__))) . '(&.*)?$/',
'/' . preg_quote(dcCore::app()->adminurl->get('admin.plugin.' . My::id())) . '(&.*)?$/',
$_SERVER['REQUEST_URI']
),
$perm
$has_perm
);
if ($perm) {
# Dashboard icon
dcCore::app()->addBehavior('adminDashboardFavoritesV2', ['zcfsAdminBehaviors', 'adminDashboardFavoritesV2']);
# User pref
dcCore::app()->addBehavior('adminColumnsListsV2', ['zcfsAdminBehaviors', 'adminColumnsListsV2']);
dcCore::app()->addBehavior('adminFiltersListsV2', ['zcfsAdminBehaviors', 'adminFiltersListsV2']);
# Add info about feed on post page sidebar
dcCore::app()->addBehavior('adminPostHeaders', ['zcfsAdminBehaviors', 'adminPostHeaders']);
dcCore::app()->addBehavior('adminPostFormItems', ['zcfsAdminBehaviors', 'adminPostFormItems']);
}
# Take care about tweakurls (thanks Mathieu M.)
dcCore::app()->addbehavior('zcfsAfterPostCreate', ['zoneclearFeedServer', 'tweakurlsAfterPostCreate']);
// no perm
if (!$has_perm) {
return true;
}
# Delete related info about feed post in meta table
dcCore::app()->addBehavior('adminBeforePostDelete', ['zcfsAdminBehaviors', 'adminBeforePostDelete']);
// behaviors that require user perm
dcCore::app()->addBehaviors([
'adminDashboardFavoritesV2' => [BackendBehaviors::class, 'adminDashboardFavoritesV2'],
'adminColumnsListsV2' => [BackendBehaviors::class, 'adminColumnsListsV2'],
'adminFiltersListsV2' => [BackendBehaviors::class, 'adminFiltersListsV2'],
'adminPostListHeaderV2' => [BackendBehaviors::class, 'adminPostListHeaderV2'],
'adminPostListValueV2' => [BackendBehaviors::class, 'adminPostListValueV2'],
'adminPostHeaders' => [BackendBehaviors::class, 'adminPostHeaders'],
'adminPostFormItems' => [BackendBehaviors::class, 'adminPostFormItems'],
]);
return true;
}
}

248
src/BackendBehaviors.php Normal file
View File

@ -0,0 +1,248 @@
<?php
/**
* @brief zoneclearFeedServer, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugin
*
* @author Jean-Christian Denis, BG, Pierre Van Glabeke
*
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
declare(strict_types=1);
namespace Dotclear\Plugin\zoneclearFeedServer;
use ArrayObject;
use dcCore;
use dcPage;
use dcFavorites;
use Dotclear\Database\MetaRecord;
use Dotclear\Helper\Html\Form\{
Checkbox,
Div,
Input,
Label,
Link,
Para,
Text
};
use Dotclear\Helper\Html\Html;
/**
* Backend behaviors.
*/
class BackendBehaviors
{
/**
* User dashboard favorites icon.
*/
public static function adminDashboardFavoritesV2(dcFavorites $favs): void
{
// nullsafe
if (is_null(dcCore::app()->auth) || is_null(dcCore::app()->adminurl)) {
return;
}
$favs->register(My::id(), [
'title' => My::name(),
'url' => dcCore::app()->adminurl->get('admin.plugin.' . My::id()),
'small-icon' => dcPage::getPF(My::id() . '/icon.svg'),
'large-icon' => dcPage::getPF(My::id() . '/icon.svg'),
'permissions' => dcCore::app()->auth->makePermissions([
dcCore::app()->auth::PERMISSION_USAGE,
dcCore::app()->auth::PERMISSION_CONTENT_ADMIN,
]),
// update user dashboard favorites icon with nb of updated feeds
'dashboard_cb' => function (ArrayObject $fav): void {
if (is_null(dcCore::app()->adminurl)) {
return;
}
$count = ZoneclearFeedServer::instance()->getFeeds(['feed_status' => '0'], true)->f(0);
if (!$count || !is_numeric($count)) {
return;
}
$fav['title'] .= '<br />' . sprintf(__('%s feed disabled', '%s feeds disabled', (int) $count), (int) $count);
$fav['large-icon'] = dcPage::getPF(My::id() . '/icon-update.svg');
$fav['url'] = dcCore::app()->adminurl->get(
'admin.plugin.' . My::id(),
['part' => 'feeds', 'sortby' => 'feed_status', 'order' => 'asc']
);
},
]);
}
/**
* Lists columns user preference.
*/
public static function adminColumnsListsV2(ArrayObject $cols): void
{
// feeds
$cols[My::id() . 'feeds'] = [
__('Feeds server: Feeds'),
[
'desc' => [true, __('Feed')],
'period' => [true, __('Frequency')],
'update' => [true, __('Last update')],
'entries' => [true, __('Entries')],
],
];
// feed posts
$cols[My::id() . 'posts'] = [
__('Feeds server: Entries'),
[
'date' => [true, __('Date')],
'category' => [true, __('Category')],
'author' => [true, __('Author')],
],
];
// posts feed
$cols['posts'][1]['feed'] = [true, __('Feed server')];
}
/**
* Lists filter.
*/
public static function adminFiltersListsV2(ArrayObject $sorts): void
{
// feeds
$sorts[My::id() . 'feeds'] = [
__('Feeds server: Feeds'),
Combo::feedsSortby(),
'lowername',
'asc',
[__('feeds per page'), 30],
];
// feed posts
$sorts[My::id() . 'posts'] = [
__('Feeds server: Entries'),
Combo::postsSortby(),
'post_dt',
'desc',
[__('entries per page'), 30],
];
}
/**
* Add head column to posts list.
*/
public static function adminPostListHeaderV2(MetaRecord $rs, ArrayObject $cols): void
{
$cols['feed'] = '<th scope="col">' . __('Feed') . '</th>';
}
/**
* Add body column to posts list.
*/
public static function adminPostListValueV2(MetaRecord $rs, ArrayObject $cols): void
{
$rs_meta = dcCore::app()->meta->getMetadata(['post_id' => $rs->f('post_id'), 'meta_type' => My::META_PREFIX . 'id']);
if ($rs_meta->isEmpty()) {
$item = (new Text('', '-'));
} else {
$row = new FeedRow(ZoneclearFeedServer::instance()->getFeeds(['feed_id' => $rs_meta->f('meta_id')]));
$item = (new Link())
->href(dcCore::app()->adminurl?->get('admin.plugin.' . My::id(), ['part' => 'feed', 'feed_id' => $row->id]) . '#feed')
->title(__('edit feed'))
->text(Html::escapeHTML($row->name));
}
$cols['feed'] = (new Para(null, 'td'))->class('nowrap')->items([$item])->render();
}
/**
* Add info about feed on post page sidebar.
*/
public static function adminPostHeaders(): string
{
return dcPage::jsModuleLoad(My::id() . '/js/post.js');
}
/**
* Add info about feed on post page sidebar.
*/
public static function adminPostFormItems(ArrayObject $main_items, ArrayObject $sidebar_items, ?MetaRecord $post): void
{
// nullsafe
if (is_null(dcCore::app()->auth) || is_null(dcCore::app()->blog) || is_null(dcCore::app()->adminurl)) {
return;
}
// not feed on new post
if ($post === null || $post->f('post_type') != 'post') {
return;
}
$url = dcCore::app()->meta->getMetadata([
'post_id' => $post->f('post_id'),
'meta_type' => My::META_PREFIX . 'url',
'limit' => 1,
]);
$url = $url->isEmpty() ? '' : $url->f('meta_id');
if (!$url) {
return;
}
$author = dcCore::app()->meta->getMetadata([
'post_id' => $post->f('post_id'),
'meta_type' => My::META_PREFIX . 'author',
'limit' => 1,
]);
$author = $author->isEmpty() ? '' : $author->f('meta_id');
$site = dcCore::app()->meta->getMetadata([
'post_id' => $post->f('post_id'),
'meta_type' => My::META_PREFIX . 'site',
'limit' => 1,
]);
$site = $site->isEmpty() ? '' : $site->f('meta_id');
$sitename = dcCore::app()->meta->getMetadata([
'post_id' => $post->f('post_id'),
'meta_type' => My::META_PREFIX . 'sitename',
'limit' => 1,
]);
$sitename = $sitename->isEmpty() ? '' : $sitename->f('meta_id');
$edit = (new Text('', ''));
if (dcCore::app()->auth->check(dcCore::app()->auth->makePermissions([
dcCore::app()->auth::PERMISSION_CONTENT_ADMIN,
]), dcCore::app()->blog->id)
) {
$fid = dcCore::app()->meta->getMetadata([
'post_id' => $post->f('post_id'),
'meta_type' => My::META_PREFIX . 'id',
'limit' => 1,
]);
if (!$fid->isEmpty()) {
$edit = (new Link())
->href(dcCore::app()->adminurl->get(
'admin.plugin.' . My::id(),
['part' => 'feed', 'feed_id' => $fid->f('meta_id')]
))
->text(__('Edit this feed'));
}
}
$sidebar_items['options-box']['items'][My::id()] = (new Div('zcfs'))
->items([
(new Text('h5', __('Feed source'))),
(new Para())
->separator('<br />')
->items([
(new Link())
->href($url)
->title($author . ' - ' . $url)
->text(__('feed URL')),
(new Link())
->href($site)
->title($sitename . ' - ' . $site)
->text(__('site URL')),
$edit,
]),
])
->render();
}
}

143
src/Combo.php Normal file
View File

@ -0,0 +1,143 @@
<?php
/**
* @brief zoneclearFeedServer, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugin
*
* @author Jean-Christian Denis, BG, Pierre Van Glabeke
*
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
declare(strict_types=1);
namespace Dotclear\Plugin\zoneclearFeedServer;
use dcCore;
use Dotclear\Helper\Html\Html;
use Exception;
/**
* Various combo list.
*/
class Combo
{
/**
* @return array<string,string>
*/
public static function feedsSortby(): array
{
return [
__('Date') => 'feed_upddt',
__('Name') => 'lowername',
__('Frequency') => 'feed_upd_int',
__('Update date') => 'feed_upd_last',
__('Status') => 'feed_status',
];
}
/**
* @return array<string,string>
*/
public static function postsSortby(): array
{
return [
__('Date') => 'post_dt',
__('Title') => 'post_title',
__('Category') => 'cat_title',
__('Author') => 'user_id',
__('Status') => 'post_status',
];
}
/**
* @return array<string,string>
*/
public static function feedsStatus(): array
{
return [
__('Disabled') => '0',
__('Enabled') => '1',
];
}
/**
* @return array<string,int>
*/
public static function postsStatus(): array
{
return [
__('Unpublished') => 0,
__('Published') => 1,
];
}
/**
* @return array<string,int>
*/
public static function updateInterval(): array
{
return [
__('Every hour') => 3600,
__('Every two hours') => 7200,
__('Two times per day') => 43200,
__('Every day') => 86400,
__('Every two days') => 172800,
__('Every week') => 604800,
];
}
/**
* @return array<string,int>
*/
public static function tagCase(): array
{
return [
__('Keep source case') => 0,
__('First upper case') => 1,
__('All lower case') => 2,
__('All upper case') => 3,
];
}
/**
* @return array<string,int>
*/
public static function pubUpdate(): array
{
return [
__('Disable') => 0,
__('Before display') => 1,
__('After display') => 2,
__('Through Ajax') => 3,
];
}
/**
* @return array<string,string>
*/
public static function postCategories(): array
{
$combo = ['-' => ''];
try {
$categories = dcCore::app()->blog?->getCategories(['post_type' => 'post']);
if (!is_null($categories)) {
while ($categories->fetch()) {
$level = is_numeric($categories->f('level')) ? (int) $categories->f('level') : 1;
$cat_title = is_string($categories->f('cat_title')) ? $categories->f('cat_title') : '';
$cat_id = is_numeric($categories->f('cat_id')) ? (string) $categories->f('cat_id') : '';
$combo[
str_repeat('&nbsp;&nbsp;', $level - 1) .
'&bull; ' . Html::escapeHTML($cat_title)
] = $cat_id;
}
}
} catch (Exception $e) {
}
return $combo;
}
}

View File

@ -10,188 +10,233 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
if (!defined('DC_CONTEXT_MODULE')) {
return null;
declare(strict_types=1);
namespace Dotclear\Plugin\zoneclearFeedServer;
use adminModulesList;
use dcCore;
use dcPage;
use dcNsProcess;
use Dotclear\Helper\Html\Form\{
Checkbox,
Div,
Input,
Label,
Link,
Number,
Para,
Select,
Text
};
use Exception;
/**
* Backend module configuration.
*/
class Config extends dcNsProcess
{
public static function init(): bool
{
static::$init == defined('DC_CONTEXT_ADMIN')
&& dcCore::app()->auth?->isSuperAdmin()
&& My::phpCompliant();
return static::$init;
}
$redir = empty($_REQUEST['redir']) ? dcCore::app()->admin->list->getURL() . '#plugins' : $_REQUEST['redir'];
# -- Get settings --
dcCore::app()->blog->settings->addNamespace(basename(__DIR__));
$s = dcCore::app()->blog->settings->__get(basename(__DIR__));
$active = (bool) $s->active;
$pub_active = (bool) $s->pub_active;
$post_status_new = (bool) $s->post_status_new;
$bhv_pub_upd = (int) $s->bhv_pub_upd;
$update_limit = (int) $s->update_limit;
$keep_empty_feed = (bool) $s->keep_empty_feed;
$tag_case = (int) $s->tag_case;
$post_full_tpl = json_decode($s->post_full_tpl);
$post_title_redir = json_decode($s->post_title_redir);
$feeduser = (string) $s->user;
if ($update_limit < 1) {
$update_limit = 10;
}
if (!is_array($post_full_tpl)) {
$post_full_tpl = [];
}
if (!is_array($post_title_redir)) {
$post_title_redir = [];
public static function process(): bool
{
if (!static::$init) {
return false;
}
$zc = new zoneclearFeedServer();
// no action
if (empty($_POST['save'])) {
return true;
}
# -- Set settings --
if (!empty($_POST['save'])) {
// read settings
$s = ZoneclearFeedServer::instance()->settings;
// write settings
try {
$active = !empty($_POST['active']);
$pub_active = !empty($_POST['pub_active']);
$post_status_new = !empty($_POST['post_status_new']);
$bhv_pub_upd = (int) $_POST['bhv_pub_upd'];
$limit = abs((int) $_POST['update_limit']);
$keep_empty_feed = !empty($_POST['keep_empty_feed']);
$tag_case = (int) $_POST['tag_case'];
$post_full_tpl = $_POST['post_full_tpl'];
$post_title_redir = $_POST['post_title_redir'];
$feeduser = (string) $_POST['feeduser'];
if ($limit < 1) {
$limit = 10;
foreach ($s->dump() as $key => $value) {
$s->set($key, $_POST[$key] ?? $value);
}
$s->put('active', $active);
$s->put('pub_active', $pub_active);
$s->put('post_status_new', $post_status_new);
$s->put('bhv_pub_upd', $bhv_pub_upd);
$s->put('update_limit', $limit);
$s->put('keep_empty_feed', $keep_empty_feed);
$s->put('tag_case', $tag_case);
$s->put('post_full_tpl', json_encode($post_full_tpl));
$s->put('post_title_redir', json_encode($post_title_redir));
$s->put('user', $feeduser);
dcCore::app()->blog->triggerBlog();
dcAdminNotices::addSuccessNotice(
__('Configuration successfully updated.')
);
dcCore::app()->adminurl->redirect(
'admin.plugins',
['module' => basename(__DIR__), 'conf' => 1, 'redir' => dcCore::app()->admin->list->getRedir()]
dcPage::addSuccessNotice(
__('Configuration has been successfully updated.')
);
dcCore::app()->adminurl?->redirect('admin.plugins', [
'module' => My::id(),
'conf' => '1',
'redir' => !(dcCore::app()->admin->__get('list') instanceof adminModulesList) ? '' : dcCore::app()->admin->__get('list')->getRedir(),
]);
} catch (Exception $e) {
dcCore::app()->error->add($e->getMessage());
}
return true;
}
# -- Form combos --
$combo_admins = $zc->getAllBlogAdmins();
$combo_pubupd = [
__('Disable') => 0,
__('Before display') => 1,
__('After display') => 2,
__('Through Ajax') => 3,
];
$combo_status = [
__('Unpublished') => 0,
__('Published') => 1,
];
$combo_tagcase = [
__('Keep source case') => 0,
__('First upper case') => 1,
__('All lower case') => 2,
__('All upper case') => 3,
];
public static function render(): void
{
if (!static::$init) {
return;
}
$pub_page_url = dcCore::app()->blog->url . dcCore::app()->url->getBase('zoneclearFeedsPage');
// nullsafe
if (is_null(dcCore::app()->blog)) {
return;
}
# -- Display form --
$z = ZoneclearFeedServer::instance();
$s = $z->settings;
$msg = [];
if (!is_writable(DC_TPL_CACHE)) {
echo '<p class="error">' . __('Dotclear cache is not writable or not well configured!') . '</p>';
$msg[] = (new Para())
->class('error')
->text(__('Dotclear cache is not writable or not well configured!'));
}
echo '
<div class="fieldset">
<h4>' . __('Activation') . '</h4>
<p><label for="active">' .
form::checkbox('active', 1, $active) .
__('Enable plugin') . '</label></p>
</div>
<div class="fieldset">';
if ($s->pub_active) {
echo sprintf(
'<p><a class="onblog_link outgoing" href="%s" title="%s">%s <img alt="" src="images/outgoing-link.svg"></a></p>',
$pub_page_url,
$pub_page_url,
__('View the public list of feeds')
);
$msg[] = (new Para())
->items([
(new Link())
->class('onblog_link outgoing')
->text(__('View the public list of feeds') . ' <img alt="" src="images/outgoing-link.svg">')
->href(dcCore::app()->blog->url . dcCore::app()->url->getBase('zoneclearFeedsPage')),
]);
}
echo '
<h4>' . __('Rules') . '</h4>
<div class="two-boxes">
<p><label for="post_status_new">' . __('Status of new posts:') . '</label>' .
form::combo('post_status_new', $combo_status, $post_status_new) . '</p>
<p><label for="feeduser">' .
__('Owner of entries created by zoneclearFeedServer:') . '</label>' .
form::combo('feeduser', $combo_admins, $feeduser) . '</p>
<p><label for="tag_case">' . __('How to transform imported tags:') . '</label>' .
form::combo('tag_case', $combo_tagcase, $tag_case) . '</p>
</div><div class="two-boxes">
<p><label for="bhv_pub_upd">' . __('Update feeds on public side:') . '</label>' .
form::combo('bhv_pub_upd', $combo_pubupd, $bhv_pub_upd) . '</p>
<p class="classic"><label for="update_limit" class="ib">' . sprintf(
__('Update %s feed(s) at a time.'),
form::number('update_limit', ['min' => 0, 'max' => 20, 'default' => $update_limit])
) . '</label></p>
<p><label for="keep_empty_feed">' .
form::checkbox('keep_empty_feed', 1, $keep_empty_feed) . __('Keep active empty feeds') . '</label></p>
<p><label for="pub_active">' .
form::checkbox('pub_active', 1, $pub_active) . __('Enable public page') . '</label></p>
</div><div class="two-boxes">
<p>' . __('Redirect to original post on:') . '</p><ul>';
foreach ($zc->getPublicUrlTypes() as $k => $v) {
echo sprintf(
'<li><label for="post_title_redir_%s">%s%s</label></li>',
$v,
form::checkbox(['post_title_redir[]', 'post_title_redir_' . $v], $v, in_array($v, $post_title_redir)),
__($k)
);
}
echo '
</ul>
</div><div class="two-boxes">
<p>' . __('Show full content on:') . '</p><ul>';
foreach ($zc->getPublicUrlTypes() as $k => $v) {
echo sprintf(
'<li><label for="post_full_tpl_%s">%s%s</label></li>',
$v,
form::checkbox(['post_full_tpl[]', 'post_full_tpl_' . $v], $v, in_array($v, $post_full_tpl)),
__($k)
);
$titles = [];
foreach ($z->getPublicUrlTypes() as $k => $v) {
$titles[] = (new Para(null, 'li'))
->items([
(new Checkbox(['post_title_redir[]', 'post_title_redir_' . $v], in_array($v, $s->post_title_redir)))
->value($v),
(new Label(__($k), Label::OUTSIDE_LABEL_AFTER))
->class('classic')
->for('post_title_redir_' . $v),
]);
}
echo '</ul></div></div>';
$contents = [];
foreach ($z->getPublicUrlTypes() as $k => $v) {
$contents[] = (new Para(null, 'li'))
->items([
(new Checkbox(['post_full_tpl_[]', 'post_full_tpl_' . $v], in_array($v, $s->post_full_tpl)))
->value($v),
(new Label(__($k), Label::OUTSIDE_LABEL_AFTER))
->class('classic')
->for('post_full_tpl_' . $v),
]);
}
echo
(new Div())
->items([
(new Div())
->items($msg),
(new Para())
->items([
(new Checkbox('active', $s->active))
->value(1),
(new Label(__('Enable plugin'), Label::OUTSIDE_LABEL_AFTER))
->class('classic')
->for('active'),
]),
(new Div())
->class('clear two-cols')
->items([
(new Div())
->class('fieldset col')
->items([
(new Para())
->items([
(new Label(__('Status of new posts:'), Label::OUTSIDE_LABEL_BEFORE))
->for('post_status_new'),
(new Select('post_status_new'))
->items(Combo::postsStatus())
->default((string) $s->post_status_new),
]),
(new Para())
->items([
(new Label(__('Owner of entries created by the feed server:'), Label::OUTSIDE_LABEL_BEFORE))
->for('user'),
(new Select('user'))
->items($z->getAllBlogAdmins())
->default($s->user),
]),
(new Para())
->items([
(new Label(__('How to transform imported tags:'), Label::OUTSIDE_LABEL_BEFORE))
->for('tag_case'),
(new Select('tag_case'))
->items(Combo::tagCase())
->default((string) $s->tag_case),
]),
]),
(new Div())
->class('fieldset col')
->items([
(new Para())
->items([
(new Label(__('Update feeds on public side:'), Label::OUTSIDE_LABEL_BEFORE))
->for('bhv_pub_upd'),
(new Select('bhv_pub_upd'))
->items(Combo::pubUpdate())
->default((string) $s->bhv_pub_upd),
]),
(new Para())
->items([
(new Label(__('Number of feeds to update at one time:'), Label::OUTSIDE_LABEL_BEFORE))
->for('update_limit'),
(new Number('update_limit'))
->min(0)
->max(20)
->value($s->update_limit),
]),
(new Para())
->items([
(new Checkbox('keep_empty_feed', $s->keep_empty_feed))
->value(1),
(new Label(__('Keep active empty feeds'), Label::OUTSIDE_LABEL_AFTER))
->class('classic')
->for('keep_empty_feed'),
]),
(new Para())
->items([
(new Checkbox('pub_active', $s->pub_active))
->value(1),
(new Label(__('Enable public page'), Label::OUTSIDE_LABEL_AFTER))
->class('classic')
->for('pub_active'),
]),
]),
]),
(new Div())
->class('two-cols')
->items([
(new Div())
->class('fieldset col')
->items([
(new Text('p', __('Redirect to original post on:'))),
(new Para(null, 'ul'))
->items($titles),
]),
(new Div())
->class('fieldset col')
->items([
(new Text('p', __('Show full content on:'))),
(new Para(null, 'ul'))
->items($contents),
]),
]),
])
->render();
dcPage::helpBlock('zoneclearFeedServer');
}
}

72
src/FeedRow.php Normal file
View File

@ -0,0 +1,72 @@
<?php
/**
* @brief zoneclearFeedServer, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugin
*
* @author Jean-Christian Denis, BG, Pierre Van Glabeke
*
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
declare(strict_types=1);
namespace Dotclear\Plugin\zoneclearFeedServer;
use Dotclear\Database\MetaRecord;
/**
* Feed meta record row type hinting.
*/
class FeedRow
{
public readonly int $id;
public readonly int $creadt;
public readonly int $upddt;
public readonly string $type;
public readonly ?string $blog_id;
public readonly ?int $cat_id;
public readonly int $upd_int;
public readonly int $upd_last;
public readonly int $status;
public readonly string $name;
public readonly string $desc;
public readonly string $url;
public readonly string $feed;
public readonly string $tags;
public readonly bool $get_tags;
public readonly string $owner;
public readonly string $tweeter;
public readonly string $lang;
public readonly int $nb_out;
public readonly int $nb_in;
/**
* Constructor sets properties.
*/
public function __construct(
public readonly MetaRecord $rs
) {
$this->id = is_numeric($this->rs->f('feed_id')) ? (int) $this->rs->f('feed_id') : 0;
$this->creadt = is_numeric($this->rs->f('feed_creadt')) ? (int) $this->rs->f('feed_creadt') : 0;
$this->upddt = is_numeric($this->rs->f('feed_upddt')) ? (int) $this->rs->f('feed_upddt') : 0;
$this->type = is_string($this->rs->f('feed_type')) ? $this->rs->f('feed_type') : '';
$this->blog_id = is_string($this->rs->f('blog_id')) ? $this->rs->f('blog_id') : null;
$this->cat_id = is_numeric($this->rs->f('cat_id')) ? (int) $this->rs->f('cat_id') : null;
$this->upd_int = is_numeric($this->rs->f('feed_upd_int')) ? (int) $this->rs->f('feed_upd_int') : 0;
$this->upd_last = is_numeric($this->rs->f('feed_upd_last')) ? (int) $this->rs->f('feed_upd_last') : 0;
$this->status = is_numeric($this->rs->f('feed_status')) ? (int) $this->rs->f('feed_status') : 0;
$this->name = is_string($this->rs->f('feed_name')) ? $this->rs->f('feed_name') : '';
$this->desc = is_string($this->rs->f('feed_desc')) ? $this->rs->f('feed_desc') : '';
$this->url = is_string($this->rs->f('feed_url')) ? $this->rs->f('feed_url') : '';
$this->feed = is_string($this->rs->f('feed_feed')) ? $this->rs->f('feed_feed') : '';
$this->tags = is_string($this->rs->f('feed_tags')) ? $this->rs->f('feed_tags') : '';
$this->get_tags = !empty($this->rs->f('feed_get_tags'));
$this->owner = is_string($this->rs->f('feed_owner')) ? $this->rs->f('feed_owner') : '';
$this->tweeter = is_string($this->rs->f('feed_tweeter')) ? $this->rs->f('feed_tweeter') : '';
$this->lang = is_string($this->rs->f('feed_lang')) ? $this->rs->f('feed_lang') : '';
$this->nb_out = is_numeric($this->rs->f('feed_nb_out')) ? (int) $this->rs->f('feed_nb_out') : 0;
$this->nb_in = is_numeric($this->rs->f('feed_nb_in')) ? (int) $this->rs->f('feed_nb_in') : 0;
}
}

View File

@ -10,19 +10,35 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
if (!defined('DC_CONTEXT_ADMIN')) {
return null;
}
declare(strict_types=1);
class zcfsFeedsActions extends dcActions
{
public $zcfs;
namespace Dotclear\Plugin\zoneclearFeedServer;
public function __construct($uri, $redirect_args = [])
use ArrayObject;
use dcActions;
use dcCore;
use dcPage;
use Dotclear\Database\MetaRecord;
use Dotclear\Helper\Html\Html;
use Exception;
/**
* Backend feeds list actions handler.
*/
class FeedsActions extends dcActions
{
$this->zcfs = new zoneclearFeedServer();
public ZoneclearFeedServer $zcfs;
/**
* @param string $uri
* @param array<string,mixed> $redirect_args
*/
public function __construct(string $uri, array $redirect_args = [])
{
$this->zcfs = ZoneclearFeedServer::instance();
parent::__construct($uri, $redirect_args);
$this->redirect_fields = [
'sortby', 'order', 'page', 'nb',
];
@ -31,13 +47,15 @@ class zcfsFeedsActions extends dcActions
$this->loadDefaults();
}
protected function loadDefaults()
protected function loadDefaults(): void
{
zcfsDefaultFeedsActions::zcfsFeedsActions($this);
dcCore::app()->callBehavior('zcfsFeedsActions', $this);
FeedsDefaultActions::addDefaultFeedsActions($this);
# --BEHAVIOR-- zoneclearFeedServerAddFeedsActions - FeedsActions
dcCore::app()->callBehavior('zoneclearFeedServerAddFeedsActions', $this);
}
public function beginPage($breadcrumb = '', $head = '')
public function beginPage(string $breadcrumb = '', string $head = ''): void
{
echo
'<html><head><title>' . __('Feeds server') . '</title>' .
@ -49,17 +67,17 @@ class zcfsFeedsActions extends dcActions
__('Back to feeds list') . '</a></p>';
}
public function endPage()
public function endPage(): void
{
echo '</body></html>';
}
public function error(Exception $e)
public function error(Exception $e): void
{
dcCore::app()->error->add($e->getMessage());
$this->beginPage(
dcPage::breadcrumb([
html::escapeHTML(dcCore::app()->blog->name) => '',
Html::escapeHTML((string) dcCore::app()->blog?->name) => '',
$this->getCallerTitle() => $this->getRedirection(true),
__('Feeds actions') => '',
])
@ -67,313 +85,21 @@ class zcfsFeedsActions extends dcActions
$this->endPage();
}
protected function fetchEntries($from)
protected function fetchEntries(ArrayObject $from): void
{
if (!empty($from['feeds'])) {
$params['feed_id'] = $from['feeds'];
if (!empty($from['feeds']) && is_array($from['feeds'])) {
$params = [
'feed_id' => $from['feeds'],
];
$feeds = $this->zcfs->getFeeds($params);
$feeds = ZoneclearFeedServer::instance()->getFeeds($params);
while ($feeds->fetch()) {
$this->entries[$feeds->feed_id] = $feeds->feed_name;
$row = new FeedRow($feeds);
$this->entries[$row->id] = $row->name;
}
$this->rs = $feeds;
} else {
$this->rs = dcCore::app()->con->select(
'SELECT blog_id FROM ' . dcCore::app()->prefix . dcBlog::BLOG_TABLE_NAME . ' WHERE false'
);
}
}
}
/**
* @ingroup DC_PLUGIN_ZONECLEARFEEDSERVER
* @brief Feeds server - Default actions methods
* @since 2.6
* @see dcDefaultPostsActionsPage for mor info
*/
class zcfsDefaultFeedsActions
{
public static function zcfsFeedsActions(zcfsFeedsActions $ap)
{
$ap->addAction(
[__('Change category') => 'changecat'],
['zcfsDefaultFeedsActions', 'doChangeCategory']
);
$ap->addAction(
[__('Change update interval') => 'changeint'],
['zcfsDefaultFeedsActions', 'doChangeInterval']
);
$ap->addAction(
[__('Disable feed update') => 'disablefeed'],
['zcfsDefaultFeedsActions', 'doEnableFeed']
);
$ap->addAction(
[__('Enable feed update') => 'enablefeed'],
['zcfsDefaultFeedsActions', 'doEnableFeed']
);
$ap->addAction(
[__('Reset last update') => 'resetupdlast'],
['zcfsDefaultFeedsActions', 'doResetUpdate']
);
$ap->addAction(
[__('Update (check) feed') => 'updatefeed'],
['zcfsDefaultFeedsActions', 'doUpdateFeed']
);
$ap->addAction(
[__('Delete related posts') => 'deletepost'],
['zcfsDefaultFeedsActions', 'doDeletePost']
);
$ap->addAction(
[__('Delete feed (without related posts)') => 'deletefeed'],
['zcfsDefaultFeedsActions', 'doDeleteFeed']
);
}
public static function doEnableFeed(zcfsFeedsActions $ap, $post)
{
$enable = $ap->getAction() == 'enablefeed';
$ids = $ap->getIDs();
if (empty($ids)) {
throw new Exception(__('No feeds selected'));
}
foreach ($ids as $id) {
$ap->zcfs->enableFeed($id, $enable);
}
dcAdminNotices::addSuccessNotice(sprintf(
$enable ?
__(
'%d feed has been successfully enabled.',
'%d feeds have been successfully enabled.',
count($ids)
)
:
__(
'%d feed has been successfully disabled.',
'%d feeds have been successfully disabled.',
count($ids)
),
count($ids)
));
$ap->redirect(true);
}
public static function doDeletePost(zcfsFeedsActions $ap, $post)
{
$types = [
'zoneclearfeed_url',
'zoneclearfeed_author',
'zoneclearfeed_site',
'zoneclearfeed_sitename',
'zoneclearfeed_id',
];
$ids = $ap->getIDs();
if (empty($ids)) {
throw new Exception(__('No feeds selected'));
}
foreach ($ids as $id) {
$posts = $ap->zcfs->getPostsByFeed([
'feed_id' => $id,
]);
while ($posts->fetch()) {
dcCore::app()->blog->delPost($posts->post_id);
dcCore::app()->con->execute(
'DELETE FROM ' . dcCore::app()->prefix . dcMeta::META_TABLE_NAME . ' ' .
'WHERE post_id = ' . $posts->post_id . ' ' .
'AND meta_type ' . dcCore::app()->con->in($types) . ' '
);
}
}
dcAdminNotices::addSuccessNotice(
__('Entries have been successfully deleted.')
);
$ap->redirect(true);
}
public static function doDeleteFeed(zcfsFeedsActions $ap, $post)
{
$ids = $ap->getIDs();
if (empty($ids)) {
throw new Exception(__('No feeds selected'));
}
foreach ($ids as $id) {
$ap->zcfs->delFeed($id);
}
dcAdminNotices::addSuccessNotice(sprintf(
__(
'%d feed has been successfully deleted.',
'%d feeds have been successfully deleted.',
count($ids)
),
count($ids)
));
$ap->redirect(true);
}
public static function doUpdateFeed(zcfsFeedsActions $ap, $post)
{
$ids = $ap->getIDs();
if (empty($ids)) {
throw new Exception(__('No feeds selected'));
}
foreach ($ids as $id) {
$ap->zcfs->checkFeedsUpdate($id, true);
}
dcAdminNotices::addSuccessNotice(sprintf(
__(
'%d feed has been successfully updated.',
'%d feeds have been successfully updated.',
count($ids)
),
count($ids)
));
$ap->redirect(true);
}
public static function doResetUpdate(zcfsFeedsActions $ap, $post)
{
$ids = $ap->getIDs();
if (empty($ids)) {
throw new Exception(__('No feeds selected'));
}
foreach ($ids as $id) {
$cur = $ap->zcfs->openCursor();
$cur->feed_upd_last = 0;
$ap->zcfs->updFeed($id, $cur);
$ap->zcfs->checkFeedsUpdate($id, true);
}
dcAdminNotices::addSuccessNotice(sprintf(
__(
'Last update of %s feed successfully reseted.',
'Last update of %s feeds successfully reseted.',
count($ids)
),
count($ids)
));
$ap->redirect(true);
}
public static function doChangeCategory(zcfsFeedsActions $ap, $post)
{
if (isset($post['upd_cat_id'])) {
$ids = $ap->getIDs();
if (empty($ids)) {
throw new Exception(__('No feeds selected'));
}
$cat_id = abs((int) $post['upd_cat_id']);
foreach ($ids as $id) {
$cur = $ap->zcfs->openCursor();
$cur->cat_id = $cat_id == 0 ? null : $cat_id;
$ap->zcfs->updFeed($id, $cur);
}
dcAdminNotices::addSuccessNotice(sprintf(
__(
'Category of %s feed successfully changed.',
'Category of %s feeds successfully changed.',
count($ids)
),
count($ids)
));
$ap->redirect(true);
} else {
$categories_combo = dcAdminCombos::getCategoriesCombo(
dcCore::app()->blog->getCategories()
);
$ap->beginPage(
dcPage::breadcrumb([
html::escapeHTML(dcCore::app()->blog->name) => '',
__('Feeds server') => '',
$ap->getCallerTitle() => $ap->getRedirection(true),
__('Change category for this selection') => '',
])
);
echo
'<form action="' . $ap->getURI() . '" method="post">' .
$ap->getCheckboxes() .
'<p><label for="upd_cat_id" class="classic">' . __('Category:') . '</label> ' .
form::combo(['upd_cat_id'], $categories_combo, '') .
dcCore::app()->formNonce() .
$ap->getHiddenFields() .
form::hidden(['action'], 'changecat') .
'<input type="submit" value="' . __('Save') . '" /></p>' .
'</form>';
$ap->endPage();
}
}
public static function doChangeInterval(zcfsFeedsActions $ap, $post)
{
if (isset($post['upd_upd_int'])) {
$ids = $ap->getIDs();
if (empty($ids)) {
throw new Exception(__('No feeds selected'));
}
$upd_int = abs((int) $post['upd_upd_int']);
foreach ($ids as $id) {
$cur = $ap->zcfs->openCursor();
$cur->feed_upd_int = $upd_int;
$ap->zcfs->updFeed($id, $cur);
}
dcAdminNotices::addSuccessNotice(sprintf(
__(
'Update frequency of %s feed successfully changed.',
'Update frequency of %s feeds successfully changed.',
count($ids)
),
count($ids)
));
$ap->redirect(true);
} else {
$ap->beginPage(
dcPage::breadcrumb(
[
html::escapeHTML(dcCore::app()->blog->name) => '',
__('Feeds server') => '',
$ap->getCallerTitle() => $ap->getRedirection(true),
__('Change update frequency for this selection') => '',
]
)
);
echo
'<form action="' . $ap->getURI() . '" method="post">' .
$ap->getCheckboxes() .
'<p><label for="upd_upd_int" class="classic">' . __('Frequency:') . '</label> ' .
form::combo(['upd_upd_int'], $ap->zcfs->getAllUpdateInterval(), '') .
dcCore::app()->formNonce() .
$ap->getHiddenFields() .
form::hidden(['action'], 'changeint') .
'<input type="submit" value="' . __('Save') . '" /></p>' .
'</form>';
$ap->endPage();
$this->rs = MetaRecord::newFromArray([]);
}
}
}

View File

@ -10,68 +10,90 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
if (!defined('DC_CONTEXT_ADMIN')) {
return null;
}
declare(strict_types=1);
namespace Dotclear\Plugin\zoneclearFeedServer;
use ArrayObject;
use dcCore;
use dcMeta;
use dcPage;
use Dotclear\Database\Statement\DeleteStatement;
use Dotclear\Helper\Html\Form\{
Form,
Hidden,
Label,
Para,
Select,
Submit,
Text
};
use Dotclear\Helper\Html\Html;
use Exception;
/**
* @ingroup DC_PLUGIN_ZONECLEARFEEDSERVER
* @brief Feeds server - Default actions methods
* @since 2.6
* @see dcDefaultPostsActionsPage for mor info
* Backend feeds list default actions.
*/
class zcfsDefaultFeedsActions
class FeedsDefaultActions
{
public static function zcfsFeedsActions(zcfsFeedsActions $ap)
/**
* Add feeds list actions.
*/
public static function addDefaultFeedsActions(FeedsActions $ap): void
{
$ap->addAction(
[__('Change category') => 'changecat'],
['zcfsDefaultFeedsActions', 'doChangeCategory']
[self::class, 'doChangeCategory']
);
$ap->addAction(
[__('Change update interval') => 'changeint'],
['zcfsDefaultFeedsActions', 'doChangeInterval']
[self::class, 'doChangeInterval']
);
$ap->addAction(
[__('Disable feed update') => 'disablefeed'],
['zcfsDefaultFeedsActions', 'doEnableFeed']
[self::class, 'doEnableFeed']
);
$ap->addAction(
[__('Enable feed update') => 'enablefeed'],
['zcfsDefaultFeedsActions', 'doEnableFeed']
[self::class, 'doEnableFeed']
);
$ap->addAction(
[__('Reset last update') => 'resetupdlast'],
['zcfsDefaultFeedsActions', 'doResetUpdate']
[self::class, 'doResetUpdate']
);
$ap->addAction(
[__('Update (check) feed') => 'updatefeed'],
['zcfsDefaultFeedsActions', 'doUpdateFeed']
[self::class, 'doUpdateFeed']
);
$ap->addAction(
[__('Delete related posts') => 'deletepost'],
['zcfsDefaultFeedsActions', 'doDeletePost']
[self::class, 'doDeletePost']
);
$ap->addAction(
[__('Delete feed (without related posts)') => 'deletefeed'],
['zcfsDefaultFeedsActions', 'doDeleteFeed']
[self::class, 'doDeleteFeed']
);
}
public static function doEnableFeed(zcfsFeedsActions $ap, $post)
/**
* Enable / disable feeds.
*/
public static function doEnableFeed(FeedsActions $ap, ArrayObject $post): void
{
$enable = $ap->getAction() == 'enablefeed';
$ids = $ap->getIDs();
if (empty($ids)) {
throw new Exception(__('No feeds selected'));
$ap->error(new Exception(__('No feeds selected')));
return;
}
foreach ($ids as $id) {
$ap->zcfs->enableFeed($id, $enable);
}
dcAdminNotices::addSuccessNotice(sprintf(
dcPage::addSuccessNotice(sprintf(
$enable ?
__(
'%d feed has been successfully enabled.',
@ -89,20 +111,25 @@ class zcfsDefaultFeedsActions
$ap->redirect(true);
}
public static function doDeletePost(zcfsFeedsActions $ap, $post)
/**
* Delete feeds posts.
*/
public static function doDeletePost(FeedsActions $ap, ArrayObject $post): void
{
$types = [
'zoneclearfeed_url',
'zoneclearfeed_author',
'zoneclearfeed_site',
'zoneclearfeed_sitename',
'zoneclearfeed_id',
My::META_PREFIX . 'url',
My::META_PREFIX . 'author',
My::META_PREFIX . 'site',
My::META_PREFIX . 'sitename',
My::META_PREFIX . 'id',
];
$ids = $ap->getIDs();
if (empty($ids)) {
throw new Exception(__('No feeds selected'));
$ap->error(new Exception(__('No feeds selected')));
return;
}
foreach ($ids as $id) {
@ -111,34 +138,42 @@ class zcfsDefaultFeedsActions
]);
while ($posts->fetch()) {
dcCore::app()->blog->delPost($posts->post_id);
dcCore::app()->con->execute(
'DELETE FROM ' . dcCore::app()->prefix . dcMeta::META_TABLE_NAME . ' ' .
'WHERE post_id = ' . $posts->post_id . ' ' .
'AND meta_type ' . dcCore::app()->con->in($types) . ' '
);
if (is_numeric($posts->f('post_id'))) {
dcCore::app()->blog?->delPost((int) $posts->f('post_id'));
$sql = new DeleteStatement();
$sql
->from(dcCore::app()->prefix . dcMeta::META_TABLE_NAME)
->where('post_id = ' . $posts->f('post_id'))
->and('meta_type ' . $sql->in($types))
->delete();
}
}
}
dcAdminNotices::addSuccessNotice(
dcPage::addSuccessNotice(
__('Entries have been successfully deleted.')
);
$ap->redirect(true);
}
public static function doDeleteFeed(zcfsFeedsActions $ap, $post)
/**
* Delete feeds.
*/
public static function doDeleteFeed(FeedsActions $ap, ArrayObject $post): void
{
$ids = $ap->getIDs();
if (empty($ids)) {
throw new Exception(__('No feeds selected'));
$ap->error(new Exception(__('No feeds selected')));
return;
}
foreach ($ids as $id) {
$ap->zcfs->delFeed($id);
$ap->zcfs->deleteFeed($id);
}
dcAdminNotices::addSuccessNotice(sprintf(
dcPage::addSuccessNotice(sprintf(
__(
'%d feed has been successfully deleted.',
'%d feeds have been successfully deleted.',
@ -149,77 +184,90 @@ class zcfsDefaultFeedsActions
$ap->redirect(true);
}
public static function doUpdateFeed(zcfsFeedsActions $ap, $post)
/**
* Update feeds properties.
*/
public static function doUpdateFeed(FeedsActions $ap, ArrayObject $post): void
{
$ids = $ap->getIDs();
if (empty($ids)) {
throw new Exception(__('No feeds selected'));
$ap->error(new Exception(__('No feeds selected')));
return;
}
foreach ($ids as $id) {
$ap->zcfs->checkFeedsUpdate($id, true);
}
dcAdminNotices::addSuccessNotice(sprintf(
dcPage::addSuccessNotice(sprintf(
__('%d feed has been successfully updated.', '%d feeds have been successfully updated.', count($ids)),
count($ids)
));
$ap->redirect(true);
}
public static function doResetUpdate(zcfsFeedsActions $ap, $post)
/**
* Reset feeds update timer.
*/
public static function doResetUpdate(FeedsActions $ap, ArrayObject $post): void
{
$ids = $ap->getIDs();
if (empty($ids)) {
throw new Exception(__('No feeds selected'));
$ap->error(new Exception(__('No feeds selected')));
return;
}
foreach ($ids as $id) {
$cur = $ap->zcfs->openCursor();
$cur->feed_upd_last = 0;
$ap->zcfs->updFeed($id, $cur);
foreach ($ids as $id) {
$cur->clean();
$cur->setField('feed_upd_last', 0);
$ap->zcfs->updateFeed($id, $cur);
$ap->zcfs->checkFeedsUpdate($id, true);
}
dcAdminNotices::addSuccessNotice(sprintf(
dcPage::addSuccessNotice(sprintf(
__('Last update of %s feed successfully reseted.', 'Last update of %s feeds successfully reseted.', count($ids)),
count($ids)
));
$ap->redirect(true);
}
public static function doChangeCategory(zcfsFeedsActions $ap, $post)
/**
* Change feeds categories.
*/
public static function doChangeCategory(FeedsActions $ap, ArrayObject $post): void
{
if (isset($post['upd_cat_id'])) {
$ids = $ap->getIDs();
if (empty($ids)) {
throw new Exception(__('No feeds selected'));
$ap->error(new Exception(__('No feeds selected')));
return;
}
$cat_id = abs((int) $post['upd_cat_id']);
$cat_id = is_numeric($post['upd_cat_id']) ? abs((int) $post['upd_cat_id']) : null;
foreach ($ids as $id) {
$cur = $ap->zcfs->openCursor();
$cur->cat_id = $cat_id == 0 ? null : $cat_id;
$ap->zcfs->updFeed($id, $cur);
foreach ($ids as $id) {
$cur->clean();
$cur->setField('cat_id', $cat_id == 0 ? null : $cat_id);
$ap->zcfs->updateFeed($id, $cur);
}
dcAdminNotices::addSuccessNotice(sprintf(
dcPage::addSuccessNotice(sprintf(
__('Category of %s feed successfully changed.', 'Category of %s feeds successfully changed.', count($ids)),
count($ids)
));
$ap->redirect(true);
} else {
$categories_combo = dcAdminCombos::getCategoriesCombo(
dcCore::app()->blog->getCategories()
);
$ap->beginPage(
dcPage::breadcrumb([
html::escapeHTML(dcCore::app()->blog->name) => '',
Html::escapeHTML((string) dcCore::app()->blog?->name) => '',
__('Feeds server') => '',
$ap->getCallerTitle() => $ap->getRedirection(true),
__('Change category for this selection') => '',
@ -227,38 +275,57 @@ class zcfsDefaultFeedsActions
);
echo
'<form action="' . $ap->getURI() . '" method="post">' .
$ap->getCheckboxes() .
'<p><label for="upd_cat_id" class="classic">' . __('Category:') . '</label> ' .
form::combo(['upd_cat_id'], $categories_combo, '') .
dcCore::app()->formNonce() .
$ap->getHiddenFields() .
form::hidden(['action'], 'changecat') .
'<input type="submit" value="' . __('Save') . '" /></p>' .
'</form>';
(new Form('form-action'))
->method('post')
->action($ap->getURI())
->fields([
(new Text('', $ap->getCheckboxes())),
(new Para())
->items(array_merge(
$ap->hiddenFields(),
[
(new Label(__('Category:'), Label::OUTSIDE_LABEL_BEFORE))
->for('upd_cat_id'),
(new Select('upd_cat_id'))
->items(Combo::postCategories()),
(new Submit('do-action'))
->value(__('Save')),
(new Hidden(['action'], 'changecat')),
dcCore::app()->formNonce(false),
]
)),
])
->render();
$ap->endPage();
}
}
public static function doChangeInterval(zcfsFeedsActions $ap, $post)
/**
* Change feeds update interval.
*/
public static function doChangeInterval(FeedsActions $ap, ArrayObject $post): void
{
if (isset($post['upd_upd_int'])) {
$ids = $ap->getIDs();
if (empty($ids)) {
throw new Exception(__('No feeds selected'));
$ap->error(new Exception(__('No feeds selected')));
return;
}
$upd_int = abs((int) $post['upd_upd_int']);
$upd_int = is_numeric($post['upd_upd_int']) ? abs((int) $post['upd_upd_int']) : 0;
foreach ($ids as $id) {
$cur = $ap->zcfs->openCursor();
$cur->feed_upd_int = $upd_int;
$ap->zcfs->updFeed($id, $cur);
foreach ($ids as $id) {
$cur->clean();
$cur->setField('feed_upd_int', $upd_int);
$ap->zcfs->updateFeed($id, $cur);
}
dcAdminNotices::addSuccessNotice(sprintf(
dcPage::addSuccessNotice(sprintf(
__('Update frequency of %s feed successfully changed.', 'Update frequency of %s feeds successfully changed.', count($ids)),
count($ids)
));
@ -267,7 +334,7 @@ class zcfsDefaultFeedsActions
$ap->beginPage(
dcPage::breadcrumb(
[
html::escapeHTML(dcCore::app()->blog->name) => '',
Html::escapeHTML((string) dcCore::app()->blog?->name) => '',
__('Feeds server') => '',
$ap->getCallerTitle() => $ap->getRedirection(true),
__('Change update frequency for this selection') => '',
@ -276,15 +343,28 @@ class zcfsDefaultFeedsActions
);
echo
'<form action="' . $ap->getURI() . '" method="post">' .
$ap->getCheckboxes() .
'<p><label for="upd_upd_int" class="classic">' . __('Frequency:') . '</label> ' .
form::combo(['upd_upd_int'], $ap->zcfs->getAllUpdateInterval(), '') .
dcCore::app()->formNonce() .
$ap->getHiddenFields() .
form::hidden(['action'], 'changeint') .
'<input type="submit" value="' . __('Save') . '" /></p>' .
'</form>';
(new Form('form-action'))
->method('post')
->action($ap->getURI())
->fields([
(new Text('', $ap->getCheckboxes())),
(new Para())
->items(array_merge(
$ap->hiddenFields(),
[
(new Label(__('Frequency:'), Label::OUTSIDE_LABEL_BEFORE))
->for('upd_upd_int'),
(new Select('upd_upd_int'))
->items(Combo::updateInterval()),
(new Submit('do-action'))
->value(__('Save')),
(new Hidden(['action'], 'changeint')),
dcCore::app()->formNonce(false),
]
)),
])
->render();
$ap->endPage();
}

View File

@ -10,132 +10,179 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
if (!defined('DC_CONTEXT_ADMIN')) {
return null;
}
declare(strict_types=1);
namespace Dotclear\Plugin\zoneclearFeedServer;
use ArrayObject;
use adminGenericFilterV2;
use adminGenericListV2;
use dcCore;
use dcPager;
use Dotclear\Helper\Date;
use Dotclear\Helper\Html\Form\{
Checkbox,
Div,
Link,
Para,
Text
};
use Dotclear\Helper\Html\Html;
/**
* @ingroup DC_PLUGIN_ZONECLEARFEEDSERVER
* @brief Feeds server - feeds list methods
* @since 2.6
* @see adminGenericList for more info
* Backend feeds list.
*/
class zcfsFeedsList extends adminGenericList
class FeedsList extends adminGenericListV2
{
private $zc = null;
public function feedsDisplay($page, $nb_per_page, $enclose_block = '', $filter = false)
public function display(adminGenericFilterV2 $filter, string $enclose_block = ''): void
{
if ($this->rs->isEmpty()) {
if ($filter) {
echo '<p><strong>' . __('No feeds matches the filter') . '</strong></p>';
} else {
echo '<p><strong>' . __('No feeds') . '</strong></p>';
}
} else {
$this->zc = new zoneclearFeedServer();
$pager = new dcPager($page, $this->rs_count, $nb_per_page, 10);
$entries = [];
if (isset($_REQUEST['feeds'])) {
foreach ($_REQUEST['feeds'] as $v) {
$entries[(int) $v] = true;
}
}
$html_block = '<div class="table-outer">' .
'<table>' .
'<caption>' . (
$filter ?
sprintf(__('List of %s feeds matching the filter.'), $this->rs_count) :
sprintf(__('List of feeds (%s)'), $this->rs_count)
) . '</caption>';
echo
(new Text(
'p',
$filter->show() ?
__('No feeds matches the filter') :
__('No feeds')
))
->class('info')
->render();
$cols = [
'title' => '<th colspan="2" class="first nowrap">' . __('Name') . '</th>',
'desc' => '<th class="nowrap" scope="col">' . __('Feed') . '</th>',
'period' => '<th class="nowrap" scope="col">' . __('Frequency') . '</th>',
'update' => '<th class="nowrap" scope="col">' . __('Last update') . '</th>',
'entries' => '<th class="nowrap" scope="col">' . __('Entries') . '</th>',
'status' => '<th class="nowrap" scope="col">' . __('Status') . '</th>',
];
$cols = new ArrayObject($cols);
dcCore::app()->callBehavior('adminZcfsFeedsListHeader', $this->rs, $cols);
$this->userColumns('zcfs_feeds', $cols);
$html_block .= '<tr>' . implode(iterator_to_array($cols)) . '</tr>%s</table>%s</div>';
if ($enclose_block) {
$html_block = sprintf($enclose_block, $html_block);
return;
}
echo $pager->getLinks();
$page = is_numeric($filter->value('page')) ? (int) $filter->value('page') : 1;
$nbpp = is_numeric($filter->value('nb')) ? (int) $filter->value('nb') : 10;
$count = (int) $this->rs_count;
$pager = new dcPager($page, $count, $nbpp, 10);
$blocks = explode('%s', $html_block);
$cols = new ArrayObject([
'title' => (new Text('th', __('Name')))
->class('first')
->extra('colspan="2"'),
'desc' => (new Text('th', __('Feed')))
->extra('scope="col"'),
'period' => (new Text('th', __('Frequency')))
->extra('scope="col"'),
'update' => (new Text('th', __('Last update')))
->extra('scope="col"')->class('nowrap'),
'entries' => (new Text('th', __('Entries')))
->extra('scope="col"'),
'status' => (new Text('th', __('Status')))
->extra('scope="col"'),
]);
echo $blocks[0];
$this->userColumns(My::id() . 'feeds', $cols);
$lines = [];
while ($this->rs->fetch()) {
echo $this->feedsLine(isset($entries[$this->rs->feed_id]));
$lines[] = $this->line(isset($_POST['feeds']) && in_array($this->rs->post_id, $_POST['feeds']));
}
echo $blocks[1];
echo $blocks[2];
echo $pager->getLinks();
}
echo
$pager->getLinks() .
sprintf(
$enclose_block,
(new Div())
->class('table-outer')
->items([
(new Para(null, 'table'))
->items([
(new Text(
'caption',
$filter->show() ?
sprintf(__('List of %s feeds matching the filter.'), $this->rs_count) :
sprintf(__('List of feeds. (%s)'), $this->rs_count)
)),
(new Para(null, 'tr'))
->items(iterator_to_array($cols)),
(new Para(null, 'tbody'))
->items($lines),
]),
])
->render()
) .
$pager->getLinks();
}
private function feedsLine($checked)
private function line(bool $checked): Para
{
$combo_status = zoneclearFeedServer::getAllStatus();
$combo_upd_int = zoneclearFeedServer::getAllUpdateInterval();
$status = $this->rs->feed_status ?
'<img src="images/check-on.png" alt="enable" />' :
'<img src="images/check-off.png" alt="disable" />';
$row = new FeedRow($this->rs);
$img_title = $row->status ? __('enabled') : __('disabled');
$img_src = $row->status ? 'check-on.png' : 'check-off.png';
$entries_count = $this->zc->getPostsByFeed(['feed_id' => $this->rs->feed_id], true)->f(0);
$shunk_feed = $this->rs->feed_feed;
$entries_count = ZoneclearFeedServer::instance()->getPostsByFeed(['feed_id' => $row->id], true)->f(0);
if (!is_numeric($entries_count)) {
$entries_count = 0;
}
$shunk_feed = $row->feed;
if (strlen($shunk_feed) > 83) {
$shunk_feed = substr($shunk_feed, 0, 50) . '...' . substr($shunk_feed, -20);
}
$url = dcCore::app()->adminurl->get('admin.plugin.' . basename(dirname('../' . __DIR__)), ['part' => 'feed', 'feed_id' => $this->rs->feed_id]);
$url = dcCore::app()->adminurl?->get('admin.plugin.' . My::id(), ['part' => 'feed', 'feed_id' => $row->id]);
if (!is_string($url)) {
$url = '';
}
$tz = dcCore::app()->auth?->getInfo('user_tz');
if (!is_string($tz)) {
$tz = 'UTC';
}
$cols = [
'check' => '<td class="nowrap minimal">' .
form::checkbox(['feeds[]'], $this->rs->feed_id, ['checked' => $checked]) .
'</td>',
'title' => '<td class="nowrap" scope="row">' .
'<a href="' . $url . '#feed" title="' . __('Edit') . '">' . html::escapeHTML($this->rs->feed_name) . '</a>' .
'</td>',
'desc' => '<td class="nowrap maximal">' .
'<a href="' . $this->rs->feed_feed . '" title="' . html::escapeHTML($this->rs->feed_desc) . '">' . html::escapeHTML($shunk_feed) . '</a>' .
'</td>',
'period' => '<td class="nowrap minimal count">' .
array_search($this->rs->feed_upd_int, $combo_upd_int) .
'</td>',
'update' => '<td class="nowrap minimal count">' .
(
$this->rs->feed_upd_last < 1 ?
$cols = new ArrayObject([
'check' => (new Para(null, 'td'))
->class('nowrap minimal')
->items([
(new Checkbox(['feeds[]'], $checked))
->value($row->id),
]),
'title' => (new Para(null, 'td'))
->class('nowrap')
->items([
(new Link())
->title(__('Edit'))
->text(Html::escapeHTML($row->name))
->href($url . '#feed'),
]),
'desc' => (new Para(null, 'td'))
->class('nowrap maximal')
->items([
(new Link())
->title(Html::escapeHTML($row->desc))
->text(Html::escapeHTML($shunk_feed))
->href($row->feed),
])
->class('nowrap minimal'),
'period' => (new Text('td', (string) array_search($row->upd_int, Combo::updateInterval())))
->class('nowrap minimal'),
'update' => (new Text(
'td',
$row->upd_last < 1 ?
__('never') :
dt::str(__('%Y-%m-%d %H:%M'), (int) $this->rs->feed_upd_last, dcCore::app()->auth->getInfo('user_tz'))
) . '</td>',
'entries' => '<td class="nowrap minimal count">' .
(
$entries_count ?
'<a href="' . $url . '#entries" title="' . __('View entries') . '">' . $entries_count . '</a>' :
$entries_count
) . '</td>',
'status' => '<td class="nowrap minimal status">' . $status . '</td>',
];
Date::str(__('%Y-%m-%d %H:%M'), $row->upd_last, $tz)
))
->class('nowrap minimal'),
'entries' => (new Para(null, 'td'))
->class('nowrap minimal count')
->items([
(new Link())
->title(Html::escapeHTML(__('View entries')))
->text(Html::escapeHTML((string) $entries_count))
->href($url . '#entries'),
]),
'status' => (new Para(null, 'td'))
->class('nowrap minimal status')
->items([
(new Text('img', ''))
->title($img_title)
->extra('src="images/' . $img_src . '"'),
]),
]);
$cols = new ArrayObject($cols);
dcCore::app()->callBehavior('adminZcfsFeedsListValue', $this->rs, $cols);
$this->userColumns(My::id() . 'feeds', $cols);
$this->userColumns('zcfs_feeds', $cols);
return
'<tr class="line ' . ($this->rs->feed_status ? '' : 'offline ') . '" id="p' . $this->rs->feed_id . '">' .
implode(iterator_to_array($cols)) .
'</tr>';
return (new Para('p' . $row->id, 'tr'))
->class('line' . ($row->status != 1 ? ' offline ' : ''))
->items(iterator_to_array($cols));
}
}

View File

@ -10,71 +10,109 @@
* @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);
namespace Dotclear\Plugin\zoneclearFeedServer;
use dcCore;
use dcNsProcess;
use dcUtils;
use Dotclear\Database\MetaRecord;
use Dotclear\Helper\Html\Html;
use Exception;
/**
* Frontend prepend.
*/
class Frontend extends dcNsProcess
{
public static function init(): bool
{
static::$init = My::phpCompliant();
return static::$init;
}
# Namespace for settings
dcCore::app()->blog->settings->addNamespace(basename(__DIR__));
$s = dcCore::app()->blog->settings->__get(basename(__DIR__));
public static function process(): bool
{
if (!static::$init) {
return false;
}
# Widgets
require_once __DIR__ . '/_widgets.php';
$s = ZoneclearFeedServer::instance()->settings;
dcCore::app()->addBehavior('coreBlogGetPosts', ['zcfsPublicBehaviors', 'coreBlogGetPosts']);
dcCore::app()->addBehaviors([
// posts record
'coreBlogGetPosts' => function (MetaRecord $rs): void {
RsExtPosts::$brother_extensions = $rs->extensions();
$rs->extend(RsExtPosts::class);
},
// breadcrumb
'publicBreadcrumb' => function (string $context, string $separator): string {
return $context == 'zoneclearFeedsPage' ? __('List of feeds') : '';
},
// widgets registration
'initWidgets' => [Widgets::class, 'init'],
]);
// Register template blocks
foreach (My::TPL_BLOCKS as $block) {
dcCore::app()->tpl->addBlock('zc' . $block, [Template::class, $block]);
}
// Register template values
foreach (My::TPL_VALUES as $value) {
dcCore::app()->tpl->addValue('zc' . $value, [Template::class, $value]);
}
// module not active
if (!$s->active) {
return null;
return true;
}
// feeds update methods
if (1 == $s->bhv_pub_upd) {
dcCore::app()->addBehavior('publicBeforeDocumentV2', ['zcfsPublicBehaviors', 'publicDocumentV2']);
dcCore::app()->addBehavior('publicBeforeDocumentV2', function (): void {
if (in_array(dcCore::app()->url->type, ['default', 'feed'])) {
try {
ZoneclearFeedServer::instance()->checkFeedsUpdate();
} catch (Exception $e) {
}
};
});
} elseif (2 == $s->bhv_pub_upd) {
dcCore::app()->addBehavior('publicAfterDocumentV2', ['zcfsPublicBehaviors', 'publicAfterDocumentV2']);
} elseif (3 == $s->bhv_pub_upd) {
dcCore::app()->addBehavior('publicHeadContent', ['zcfsPublicBehaviors', 'publicHeadContent']);
}
# Take care about tweakurls (thanks Mathieu M.)
if (version_compare(dcCore::app()->plugins->moduleInfo('tweakurls', 'version'), '0.8', '>=')) {
dcCore::app()->addbehavior('zoneclearFeedServerAfterPostCreate', ['zoneclearFeedServer', 'tweakurlsAfterPostCreate']);
}
# Register tempalte blocks
$tpl_blocks = [
'Feeds',
'FeedsFooter',
'FeedsHeader',
'FeedIf',
];
foreach ($tpl_blocks as $v) {
dcCore::app()->tpl->addBlock('zc' . $v, ['zcfsTemplate', $v]);
}
# Register tempalte values
$tpl_values = [
'FeedsCount',
'FeedsEntriesCount',
'FeedEntriesCount',
'FeedCategory',
'FeedCategoryID',
'FeedCategoryURL',
'FeedCategoryShortURL',
'FeedID',
'FeedIfFirst',
'FeedIfOdd',
'FeedLang',
'FeedName',
'FeedOwner',
'FeedDesc',
'FeedSiteURL',
'FeedFeedURL',
];
foreach ($tpl_values as $v) {
dcCore::app()->tpl->addValue('zc' . $v, ['zcfsTemplate', $v]);
}
dcCore::app()->addBehavior('publicBreadcrumb', function ($context, $separator) {
if ($context == 'zoneclearFeedsPage') {
return __('List of feeds');
dcCore::app()->addBehavior('publicAfterDocumentV2', function (): void {
try {
ZoneclearFeedServer::instance()->checkFeedsUpdate();
} catch (Exception $e) {
pdump($e);
}
});
} elseif (3 == $s->bhv_pub_upd) {
dcCore::app()->addBehavior('publicHeadContent', function (): void {
if (is_null(dcCore::app()->blog) || dcCore::app()->url->type != 'default') {
return;
}
$blog_url = Html::escapeJS(
dcCore::app()->blog->url .
dcCore::app()->url->getBase('zoneclearFeedsPage') .
'/zcfsupd'
);
$blog_id = Html::escapeJS(dcCore::app()->blog->id);
echo
"\n<!-- JS for zoneclearFeedServer --> \n" .
dcUtils::jsLoad(dcCore::app()->blog->url . dcCore::app()->url->getBase('zoneclearFeedsPage') . '/zcfsupd.js') .
"<script type=\"text/javascript\"> \n//<![CDATA[\n" .
' $(function(){if(!document.getElementById){return;} ' .
" $('body').zoneclearFeedServer({blog_url:'" .
$blog_url . "',blog_id:'" . $blog_id . "'}); " .
" })\n" .
"//]]>\n</script>\n";
});
}
return true;
}
}

View File

@ -10,56 +10,75 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
if (!defined('DC_CONTEXT_ADMIN')) {
return null;
declare(strict_types=1);
namespace Dotclear\Plugin\zoneclearFeedServer;
use dcCore;
use dcNsProcess;
use Dotclear\Database\Structure;
use Exception;
/**
* Module installation.
*/
class Install extends dcNsProcess
{
public static function init(): bool
{
if (defined('DC_CONTEXT_ADMIN') && My::phpCompliant()) {
$version = dcCore::app()->plugins->moduleInfo(My::id(), 'version');
static::$init = is_string($version) ? dcCore::app()->newVersion(My::id(), $version) : true;
}
return static::$init;
}
public static function process(): bool
{
if (!static::$init) {
return false;
}
try {
// Check module version
if (!dcCore::app()->newVersion(
basename(__DIR__),
dcCore::app()->plugins->moduleInfo(basename(__DIR__), 'version')
)) {
return null;
}
// Upgrade existing install
zcfsUpgrade::preUpgrade();
Upgrade::preUpgrade();
// Tables
$t = new dbStruct(dcCore::app()->con, dcCore::app()->prefix);
$t->{initZoneclearFeedServer::TABLE_NAME}
->feed_id('bigint', 0, false)
->feed_creadt('timestamp', 0, false, 'now()')
->feed_upddt('timestamp', 0, false, 'now()')
->feed_type('varchar', 32, false, "'feed'")
->blog_id('varchar', 32, false)
->cat_id('bigint', 0, true)
->feed_upd_int('integer', 0, false, 3600)
->feed_upd_last('integer', 0, false, 0)
->feed_status('smallint', 0, false, 0)
->feed_name('varchar', 255, false)
->feed_desc('text', null, true) //!pgsql reserved 'desc'
->feed_url('varchar', 255, false)
->feed_feed('varchar', 255, false)
->feed_tags('varchar', 255, true)
->feed_get_tags('smallint', 0, false, 1)
->feed_owner('varchar', 255, false)
->feed_tweeter('varchar', 64, false) // tweeter ident
->feed_lang('varchar', 5, true)
->feed_nb_out('integer', 0, false, 0)
->feed_nb_in('integer', 0, false, 0)
$s = new Structure(dcCore::app()->con, dcCore::app()->prefix);
$s->__get(My::TABLE_NAME)
->field('feed_id', 'bigint', 0, false)
->field('feed_creadt', 'timestamp', 0, false, 'now()')
->field('feed_upddt', 'timestamp', 0, false, 'now()')
->field('feed_type', 'varchar', 32, false, "'feed'")
->field('blog_id', 'varchar', 32, false)
->field('cat_id', 'bigint', 0, true)
->field('feed_upd_int', 'integer', 0, false, 3600)
->field('feed_upd_last', 'integer', 0, false, 0)
->field('feed_status', 'smallint', 0, false, 0)
->field('feed_name', 'varchar', 255, false)
->field('feed_desc', 'text', null, true) //!pgsql reserved 'desc'
->field('feed_url', 'varchar', 255, false)
->field('feed_feed', 'varchar', 255, false)
->field('feed_tags', 'varchar', 255, true)
->field('feed_get_tags', 'smallint', 0, false, 1)
->field('feed_owner', 'varchar', 255, false)
->field('feed_tweeter', 'varchar', 64, false) // tweeter ident
->field('feed_lang', 'varchar', 5, true)
->field('feed_nb_out', 'integer', 0, false, 0)
->field('feed_nb_in', 'integer', 0, false, 0)
->primary('pk_zcfs', 'feed_id')
->index('idx_zcfs_type', 'btree', 'feed_type')
->index('idx_zcfs_blog', 'btree', 'blog_id');
$ti = new dbStruct(dcCore::app()->con, dcCore::app()->prefix);
$changes = $ti->synchronize($t);
(new Structure(dcCore::app()->con, dcCore::app()->prefix))->synchronize($s);
// Settings
dcCore::app()->blog->settings->addNamespace(basename(__DIR__));
$s = dcCore::app()->blog->settings->__get(basename(__DIR__));
$s = dcCore::app()->blog?->settings->get(My::id());
if (is_null($s)) {
return false;
}
$s->put('active', false, 'boolean', 'Enable zoneclearBlogServer', false, true);
$s->put('pub_active', false, 'boolean', 'Enable public page of list of feeds', false, true);
$s->put('post_status_new', true, 'boolean', 'Enable auto publish new posts', false, true);
@ -68,8 +87,8 @@ try {
$s->put('keep_empty_feed', false, 'boolean', 'Keep active empty feeds', false, true);
$s->put('tag_case', 0, 'integer', 'How to transform imported tags', false, true);
$s->put('user', '', 'string', 'User id that has right on post', false, true);
$s->put('post_full_tpl', json_encode(['post', 'category', 'tag', 'archive']), 'string', 'List of templates types for full feed', false, true);
$s->put('post_title_redir', json_encode(['feed']), 'string', 'List of templates types for redirection to original post', false, true);
$s->put('post_full_tpl', ['post', 'category', 'tag', 'archive'], 'array', 'List of templates types for full feed', false, true);
$s->put('post_title_redir', ['feed'], 'array', 'List of templates types for redirection to original post', false, true);
return true;
} catch (Exception $e) {
@ -77,3 +96,5 @@ try {
return false;
}
}
}

View File

@ -10,557 +10,204 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
if (!defined('DC_CONTEXT_ADMIN')) {
return null;
declare(strict_types=1);
namespace Dotclear\Plugin\zoneclearFeedServer;
use adminGenericFilterV2;
use dcAdminFilters;
use dcCore;
use dcNsProcess;
use dcPage;
use Dotclear\Helper\Html\Form\{
Div,
Form,
Label,
Link,
Para,
Select,
Submit,
Text
};
use Exception;
/**
* Backend feeds list manage page.
*/
class Manage extends dcNsProcess
{
public static function init(): bool
{
static::$init == defined('DC_CONTEXT_ADMIN')
&& My::phpCompliant()
&& !is_null(dcCore::app()->auth)
&& !is_null(dcCore::app()->blog)
&& dcCore::app()->auth->check(dcCore::app()->auth->makePermissions([
dcCore::app()->auth::PERMISSION_CONTENT_ADMIN,
]), dcCore::app()->blog->id);
// call period manage page
if (($_REQUEST['part'] ?? 'feeds') === 'feed') {
static::$init = ManageFeed::init();
}
if (0 !== dcCore::app()->testVersion(
basename(__DIR__),
dcCore::app()->plugins->moduleInfo(basename(__DIR__), 'version')
)) {
return null;
return static::$init;
}
dcPage::check(dcCore::app()->auth->makePermissions([
dcAuth::PERMISSION_CONTENT_ADMIN,
]));
public static function process(): bool
{
if (!static::$init) {
return false;
}
$zcfs = new zoneclearFeedServer();
$z = ZoneclearFeedServer::instance();
$s = $z->settings;
// not configured
if (!$s->active || !$s->user) {
dcCore::app()->error->add(__('Module is not wel configured'));
return true;
}
// call period manage page
if (($_REQUEST['part'] ?? 'feeds') === 'feed') {
return ManageFeed::process();
}
return true;
}
public static function render(): void
{
if (!static::$init) {
return;
}
if (is_null(dcCore::app()->adminurl)) {
return;
}
$z = ZoneclearFeedServer::instance();
$s = $z->settings;
// not configured
if (!$s->active || !$s->user) {
dcPage::openModule(My::id());
# Not configured
if (!dcCore::app()->blog->settings->__get(basename(__DIR__))->active
|| !dcCore::app()->blog->settings->__get(basename(__DIR__))->user
) {
echo
'<html><head><title>' . __('Feeds server') . '</title></head><body>' .
dcPage::breadcrumb([
__('Plugins') => '',
__('Feeds server') => '',
My::name() => '',
]) .
dcPage::notices();
############################################################
#
# One feed
#
############################################################
} elseif (isset($_REQUEST['part']) && $_REQUEST['part'] == 'feed') {
$feed_id = '';
$feed_name = '';
$feed_desc = '';
$feed_owner = '';
$feed_tweeter = '';
$feed_url = '';
$feed_feed = '';
$feed_lang = dcCore::app()->auth->getInfo('user_lang');
$feed_tags = '';
$feed_get_tags = '0';
$feed_cat_id = '';
$feed_status = '0';
$feed_upd_int = 3600;
dcPage::closeModule();
$can_view_page = true;
$feed_headlink = '<link rel="%s" title="%s" href="' . dcCore::app()->admin->getPageURL() . '&amp;part=feed&amp;feed_id=%s" />';
$feed_link = '<a href="' . dcCore::app()->admin->getPageURL() . '&amp;part=feed&amp;feed_id=%s" title="%s">%s</a>';
$next_link = $prev_link = $next_headlink = $prev_headlink = null;
# Combos
$combo_langs = l10n::getISOcodes(true);
$combo_status = $zcfs->getAllStatus();
$combo_upd_int = $zcfs->getAllUpdateInterval();
$combo_categories = ['-' => ''];
try {
$categories = dcCore::app()->blog->getCategories(['post_type' => 'post']);
while ($categories->fetch()) {
$combo_categories[
str_repeat('&nbsp;&nbsp;', $categories->level - 1) .
'&bull; ' . html::escapeHTML($categories->cat_title)
] = $categories->cat_id;
}
} catch (Exception $e) {
dcCore::app()->error->add($e->getMessage());
return;
}
# Get entry informations
if (!empty($_REQUEST['feed_id'])) {
$feed = $zcfs->getFeeds(['feed_id' => $_REQUEST['feed_id']]);
// call feed manage page
if (($_REQUEST['part'] ?? 'feeds') === 'feed') {
ManageFeed::render();
if ($feed->isEmpty()) {
dcCore::app()->error->add(__('This feed does not exist.'));
$can_view_page = false;
} else {
$feed_id = $feed->feed_id;
$feed_name = $feed->feed_name;
$feed_desc = $feed->feed_desc;
$feed_owner = $feed->feed_owner;
$feed_tweeter = $feed->feed_tweeter;
$feed_url = $feed->feed_url;
$feed_feed = $feed->feed_feed;
$feed_lang = $feed->feed_lang;
$feed_tags = $feed->feed_tags;
$feed_get_tags = $feed->feed_get_tags;
$feed_cat_id = $feed->cat_id;
$feed_status = $feed->feed_status;
$feed_upd_int = $feed->feed_upd_int;
$next_params = [
'sql' => 'AND feed_id < ' . $feed_id . ' ',
'limit' => 1,
];
$next_rs = $zcfs->getFeeds($next_params);
$prev_params = [
'sql' => 'AND feed_id > ' . $feed_id . ' ',
'limit' => 1,
];
$prev_rs = $zcfs->getFeeds($prev_params);
if (!$next_rs->isEmpty()) {
$next_link = sprintf(
$feed_link,
$next_rs->feed_id,
html::escapeHTML($next_rs->feed_name),
__('next feed') . '&nbsp;&#187;'
);
$next_headlink = sprintf(
$feed_headlink,
'next',
html::escapeHTML($next_rs->feed_name),
$next_rs->feed_id
);
return;
}
if (!$prev_rs->isEmpty()) {
$prev_link = sprintf(
$feed_link,
$prev_rs->feed_id,
html::escapeHTML($prev_rs->feed_name),
'&#171;&nbsp;' . __('previous feed')
);
$prev_headlink = sprintf(
$feed_headlink,
'previous',
html::escapeHTML($prev_rs->feed_name),
$prev_rs->feed_id
);
}
}
}
if (!empty($_POST['action']) && $_POST['action'] == 'savefeed') {
try {
$feed_name = $_POST['feed_name'];
$feed_desc = $_POST['feed_desc'];
$feed_owner = $_POST['feed_owner'];
$feed_tweeter = $_POST['feed_tweeter'];
$feed_url = $_POST['feed_url'];
$feed_feed = $_POST['feed_feed'];
$feed_lang = $_POST['feed_lang'];
$feed_tags = $_POST['feed_tags'];
$feed_get_tags = empty($_POST['feed_get_tags']) ? 0 : 1;
$feed_cat_id = $_POST['feed_cat_id'] !== '' ? $_POST['feed_cat_id'] : null;
$feed_upd_int = $_POST['feed_upd_int'];
if (isset($_POST['feed_status'])) {
$feed_status = (int) $_POST['feed_status'];
}
$testfeed_params['feed_feed'] = $feed_feed;
if ($feed_id) {
$testfeed_params['sql'] = 'AND feed_id <> ' . $feed_id . ' ';
}
if ($zcfs->getFeeds($testfeed_params, true)->f(0)) {
throw new Exception(__('Record with same feed URL already exists.'));
}
if (empty($feed_name)) {
throw new Exception(__('You must provide a name.'));
}
if (empty($feed_owner)) {
throw new Exception(__('You must provide an owner.'));
}
if (!zoneclearFeedServer::validateURL($feed_url)) {
throw new Exception(__('You must provide valid site URL.'));
}
if (!zoneclearFeedServer::validateURL($feed_feed)) {
throw new Exception(__('You must provide valid feed URL.'));
}
if (null === $feed_cat_id && !dcCore::app()->blog->getCategory((int) $feed_cat_id)) {
throw new Exception(__('You must provide valid category.'));
}
} catch (Exception $e) {
dcCore::app()->error->add($e->getMessage());
}
}
if (!empty($_POST['action']) && $_POST['action'] == 'savefeed' && !dcCore::app()->error->flag()) {
$cur = $zcfs->openCursor();
$cur->feed_name = $feed_name;
$cur->feed_desc = $feed_desc;
$cur->feed_owner = $feed_owner;
$cur->feed_tweeter = $feed_tweeter;
$cur->feed_url = $feed_url;
$cur->feed_feed = $feed_feed;
$cur->feed_lang = $feed_lang;
$cur->feed_tags = $feed_tags;
$cur->feed_get_tags = (int) $feed_get_tags;
$cur->cat_id = null === $feed_cat_id ? null : (int) $feed_cat_id;
$cur->feed_status = (int) $feed_status;
$cur->feed_upd_int = (int) $feed_upd_int;
# Update feed
if ($feed_id) {
try {
# --BEHAVIOR-- adminBeforeZoneclearFeedServerFeedUpdate
dcCore::app()->callBehavior('adminBeforeZoneclearFeedServerFeedUpdate', $cur, $feed_id);
$zcfs->updFeed($feed_id, $cur);
# --BEHAVIOR-- adminAfterZoneclearFeedServerFeedUpdate
dcCore::app()->callBehavior('adminAfterZoneclearFeedServerFeedUpdate', $cur, $feed_id);
dcAdminNotices::addSuccessNotice(
__('Feed successfully updated.')
);
dcCore::app()->adminurl->redirect(
'admin.plugin.' . basename(__DIR__),
['part' => 'feed', 'feed_id' => $feed_id]
);
} catch (Exception $e) {
dcCore::app()->error->add($e->getMessage());
}
} else {
try {
# --BEHAVIOR-- adminBeforeZoneclearFeedServerFeedCreate
dcCore::app()->callBehavior('adminBeforeZoneclearFeedServerFeedCreate', $cur);
$return_id = $zcfs->addFeed($cur);
# --BEHAVIOR-- adminAfterZoneclearFeedServerFeedCreate
dcCore::app()->callBehavior('adminAfterZoneclearFeedServerFeedCreate', $cur, $return_id);
dcAdminNotices::addSuccessNotice(
__('Feed successfully created.')
);
dcCore::app()->adminurl->redirect(
'admin.plugin.' . basename(__DIR__),
['part' => 'feed', 'feed_id' => $return_id]
);
} catch (Exception $e) {
dcCore::app()->error->add($e->getMessage());
}
}
}
# Prepared entries list
if ($feed_id && $can_view_page) {
# action
$posts_actions_page = new dcPostsActions(
// feeds actions
$feeds_actions_page = new FeedsActions(
'plugin.php',
[
'p' => basename(__DIR__),
'part' => 'feed',
'feed_id' => $feed_id,
'_ANCHOR' => 'entries',
]
);
if ($posts_actions_page->process()) {
return null;
}
# filters
$post_filter = new zcfsPostFilter();
$post_filter->add('part', 'feed');
$post_filter->add('feed_id', $feed_id);
$params = $post_filter->params();
# lexical sort
$sortby_lex = [
// key in sorty_combo (see above) => field in SQL request
'post_title' => 'post_title',
'cat_title' => 'cat_title',
'user_id' => 'P.user_id', ];
# --BEHAVIOR-- adminPostsSortbyLexCombo
dcCore::app()->callBehavior('adminPostsSortbyLexCombo', [& $sortby_lex]);
$params['no_content'] = true;
$params['feed_id'] = $feed_id;
$params['order'] = (array_key_exists($post_filter->sortby, $sortby_lex) ?
dcCore::app()->con->lexFields($sortby_lex[$post_filter->sortby]) :
$post_filter->sortby) . ' ' . $post_filter->order;
# posts
try {
$posts = $zcfs->getPostsByFeed($params);
$counter = $zcfs->getPostsByFeed($params, true);
$post_list = new zcfsEntriesList(
dcCore::app(),
$posts,
$counter->f(0)
);
} catch (Exception $e) {
dcCore::app()->error->add($e->getMessage());
}
}
# display
echo
'<html><head><title>' . __('Feeds server') . '</title>' .
($feed_id && isset($post_filter) && !dcCore::app()->error->flag() ?
$post_filter->js(dcCore::app()->adminurl->get('admin.plugin.' . basename(__DIR__), ['part' => 'feed', 'feed_id' => $feed_id], '&') . '#entries') .
dcPage::jsLoad(dcPage::getPF(basename(__DIR__) . '/js/list.js'))
: '') .
dcPage::jsPageTabs() .
$next_headlink . "\n" . $prev_headlink .
# --BEHAVIOR-- adminZoneclearFeedServerHeader
dcCore::app()->callBehavior('adminZoneclearFeedServerHeader') .
'</head><body>' .
dcPage::breadcrumb([
__('Plugins') => '',
__('Feeds server') => dcCore::app()->admin->getPageURL(),
($feed_id ? __('Edit feed') : __('New feed')) => '',
]) .
dcPage::notices() .
($feed_id ? '<h3>' . sprintf(__('Edit feed "%s"'), $feed_name) . '</h3>' : '');
# Feed
if ($can_view_page) {
# nav link
if ($feed_id && ($next_link || $prev_link)) {
echo '<p class="nav_prevnext">';
if ($prev_link) {
echo $prev_link;
}
if ($next_link && $prev_link) {
echo ' | ';
}
if ($next_link) {
echo $next_link;
}
echo '</p>';
}
echo '
<div' . ($feed_id ? ' class="multi-part" title="' . __('Feed') . '"' : '') . ' id="edit-entry">
<form method="post" action="plugin.php">
<div class="two-cols">' .
'<div class="col70">' .
'<h4>' . __('Feed information') . '</h4>' .
'<p><label for="feed_name" class="required">
<abbr title="' . __('Required field') . '">*</abbr>' .
__('Name:') . '</label>' .
form::field('feed_name', 60, 255, $feed_name, 'maximal') .
'</p>' .
'<p><label for="feed_owner" class="required">
<abbr title="' . __('Required field') . '">*</abbr>' .
__('Owner:') . '</label>' .
form::field('feed_owner', 60, 255, $feed_owner, 'maximal') .
'</p>' .
// move this away
'<p><label for="feed_tweeter">' .
__('Tweeter or Identica ident:') . '</label>' .
form::field('feed_tweeter', 60, 64, $feed_tweeter, 'maximal') .
'</p>' .
'<p><label for="feed_url" class="required">
<abbr title="' . __('Required field') . '">*</abbr>' .
__('Site URL:') . '</label>' .
form::field('feed_url', 60, 255, $feed_url, 'maximal') .
'</p>' .
'<p><label for="feed_feed" class="required">
<abbr title="' . __('Required field') . '">*</abbr>' .
__('Feed URL:') . '</label>' .
form::field('feed_feed', 60, 255, $feed_feed, 'maximal') .
'</p>' .
'<p><label for="feed_desc">' . __('Description:') . '</label>' .
form::field('feed_desc', 60, 255, $feed_desc, 'maximal') .
'</p>' .
'<p><label for="feed_tags">' . __('Tags:') . '</label>' .
form::field('feed_tags', 60, 255, $feed_tags, 'maximal') .
'</p>' .
# --BEHAVIOR-- adminZoneclearFeedServerFeedForm
dcCore::app()->callBehavior('adminZoneclearFeedServerFeedForm', $feed_id) .
'</div>' .
'<div class="col30">' .
'<h4>' . __('Local settings') . '</h4>' .
'<p><label for="feed_cat_id">' . __('Category:') . '</label>' .
form::combo('feed_cat_id', $combo_categories, $feed_cat_id, 'maximal') .
'</p>' .
'<p><label for="feed_status">' . __('Status:') . '</label>' .
form::combo('feed_status', $combo_status, $feed_status, 'maximal') .
'</p>' .
'<p><label for="feed_upd_int">' . __('Update:') . '</label>' .
form::combo('feed_upd_int', $combo_upd_int, $feed_upd_int, 'maximal') .
'</p>' .
'<p><label for="feed_lang">' . __('Lang:') . '</label>' .
form::combo('feed_lang', $combo_langs, $feed_lang, 'maximal') .
'</p>' .
'<p><label for="feed_get_tags" class="classic">' .
form::checkbox('feed_get_tags', 1, $feed_get_tags) .
__('Import tags from feed') . '</label></p>' .
'</div>' .
'</div>' .
'<p class="clear">
<input type="submit" name="save" value="' . __('Save') . ' (s)" accesskey="s"/>' .
dcCore::app()->adminurl->getHiddenFormFields('admin.plugin.' . basename(__DIR__), [
'part' => 'feed',
'feed_id' => $feed_id,
'action' => 'savefeed',
]) .
dcCore::app()->formNonce() .
'</p>
</form>
</div>';
}
# entries
if ($feed_id && $can_view_page && isset($post_filter) && isset($post_list) && isset($posts_actions_page) && !dcCore::app()->error->flag()) {
echo '<div class="multi-part" title="' . __('Entries') . '" id="entries">';
# show filters
$post_filter->display(
['admin.plugin.' . basename(__DIR__),'#entries'],
dcCore::app()->adminurl->getHiddenFormFields('admin.plugin.' . basename(__DIR__), [
'part' => 'feed',
'feed_id' => $feed_id,
])
);
# fix pager url
$args = $post_filter->values();
unset($args['page']);
$args['page'] = '%s';
# show posts
$post_list->display(
$post_filter->page,
$post_filter->nb,
dcCore::app()->adminurl->get('admin.plugin.' . basename(__DIR__), $args, '&') . '#entries',
'<form action="' . dcCore::app()->adminurl->get('admin.plugin.' . basename(__DIR__), ['part' => 'feed']) . '#entries" method="post" id="form-entries">' .
'%s' .
'<div class="two-cols">' .
'<p class="col checkboxes-helpers"></p>' .
'<p class="col right">' . __('Selected entries action:') . ' ' .
form::combo('action', $posts_actions_page->getCombo()) .
'<input type="submit" name="save" value="' . __('ok') . '" /></p>' .
dcCore::app()->adminurl->getHiddenFormFields('admin.plugin.' . basename(__DIR__), $post_filter->values()) .
form::hidden('redir', dcCore::app()->adminurl->get('admin.plugin.' . basename(__DIR__), $post_filter->values())) .
dcCore::app()->formNonce() .
'</div>' .
'</form>',
$post_filter->show()
);
echo '</div>';
}
############################################################
#
# All feeds
#
############################################################
} else {
# actions
$feeds_actions_page = new zcfsFeedsActions(
'plugin.php',
['p' => basename(__DIR__), 'part' => 'feeds']
['p' => My::id(), 'part' => 'feeds']
);
if ($feeds_actions_page->process()) {
return null;
return;
}
# filters
$feeds_filter = new adminGenericFilter(dcCore::app(), 'zcfs_feeds');
// feeds filters
$feeds_filter = new adminGenericFilterV2(My::id() . 'feeds');
$feeds_filter->add('part', 'feeds');
$feeds_filter->add(dcAdminFilters::getPageFilter());
$feeds_filter->add(dcAdminFilters::getSearchFilter());
$params = $feeds_filter->params();
# feeds
// feeds list
try {
$feeds = $zcfs->getFeeds($params);
$feeds_counter = $zcfs->getFeeds($params, true)->f(0);
$feeds_list = new zcfsFeedsList(
dcCore::app(),
$feeds,
$feeds_counter
);
$feeds = $z->getFeeds($params);
$feeds_counter = $z->getFeeds($params, true)->f(0);
$feeds_list = new FeedsList($feeds, $feeds_counter);
} catch (Exception $e) {
dcCore::app()->error->add($e->getMessage());
}
# display
dcPage::openModule(
My::id(),
(
isset($feeds_list) && !dcCore::app()->error->flag() ?
$feeds_filter->js(dcCore::app()->adminurl->get('admin.plugin.' . My::id(), ['part' => 'feeds'], '&')) .
dcPage::jsModuleLoad(My::id() . '/js/list.js')
: ''
) .
dcPage::jsPageTabs()
);
echo
'<html><head><title>' . __('Feeds server') . '</title>' .
$feeds_filter->js(dcCore::app()->adminurl->get('admin.plugin.' . basename(__DIR__), ['part' => 'feeds'], '&')) .
dcPage::jsLoad(dcPage::getPF(basename(__DIR__) . '/js/list.js')) .
dcPage::jsPageTabs() .
# --BEHAVIOR-- adminZoneclearFeedServerHeader
dcCore::app()->callBehavior('adminZoneclearFeedServerHeader') .
'</head><body>' .
dcPage::breadcrumb([
__('Plugins') => '',
__('Feeds server') => '',
My::name() => dcCore::app()->adminurl->get('admin.plugin.' . My::id()),
__('Feeds list') => '',
]) .
dcPage::notices();
if (isset($feeds_list)) {
echo
'<p class="top-add">' .
'<a class="button add" href="' . dcCore::app()->adminurl->get('admin.plugin.' . basename(__DIR__), ['part' => 'feed']) . '">' .
__('New feed') . '</a></p>';
(new Para())
->class('top-add')
->items([
(new Link())
->class('button add')
->text(__('New feed'))
->href((string) dcCore::app()->adminurl->get('admin.plugin.' . My::id(), ['part' => 'feed'])),
])
->render();
if (isset($feeds_list)) {
$feeds_filter->display(
'admin.plugin.' . basename(__DIR__),
dcCore::app()->adminurl->getHiddenFormFields('admin.plugin.' . basename(__DIR__), ['part' => 'feeds'])
'admin.plugin.' . My::id(),
dcCore::app()->adminurl->getHiddenFormFields('admin.plugin.' . My::id(), ['part' => 'feeds'])
);
$feeds_list->feedsDisplay(
$feeds_filter->page,
$feeds_filter->nb,
'<form action="' . dcCore::app()->adminurl->get('admin.plugin.' . basename(__DIR__), ['part' => 'feeds']) . '" method="post" id="form-actions">' .
'%s' .
'<div class="two-cols">' .
'<p class="col checkboxes-helpers"></p>' .
'<p class="col right">' . __('Selected feeds action:') . ' ' .
form::combo(['action'], $feeds_actions_page->getCombo()) .
'<input type="submit" value="' . __('ok') . '" />' .
dcCore::app()->adminurl->getHiddenFormFields('admin.plugin.' . basename(__DIR__), $feeds_filter->values(true)) .
dcCore::app()->formNonce() .
'</p>' .
'</div>' .
'</form>',
$feeds_filter->show()
$feeds_list->display(
$feeds_filter,
(new Form('form-feeds'))
->method('post')
->action(dcCore::app()->adminurl->get('admin.plugin.' . My::id(), ['part' => 'feeds']))
->fields([
(new Text('', '%s')),
(new Div())
->class('two-cols')
->items([
(new Para())
->class('col checkboxes-helpers'),
(new Para())
->class('col right')
->items(array_merge(
dcCore::app()->adminurl->hiddenFormFields('admin.plugin.' . My::id(), $feeds_filter->values(true)),
[
(new Label(__('Selected feeds action:'), Label::OUTSIDE_LABEL_BEFORE))
->for('action'),
(new Select('action'))
->items($feeds_actions_page->getCombo()),
(new Submit('feeds-action'))
->value(__('ok')),
dcCore::app()->formNonce(false),
]
)),
]),
])
->render()
);
}
}
echo '</body></html>';
dcPage::closeModule();
}
}

463
src/ManageFeed.php Normal file
View File

@ -0,0 +1,463 @@
<?php
/**
* @brief zoneclearFeedServer, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugin
*
* @author Jean-Christian Denis, BG, Pierre Van Glabeke
*
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
declare(strict_types=1);
namespace Dotclear\Plugin\zoneclearFeedServer;
use dcCore;
use dcNsProcess;
use dcPage;
use dcPostsActions;
use Dotclear\Helper\Html\Form\{
Checkbox,
Div,
Form,
Hidden,
Input,
Label,
Para,
Select,
Submit,
Text
};
use Dotclear\Helper\Html\Html;
use Dotclear\Helper\L10n;
use Exception;
/**
* Backend feed and feed posts manage page.
*/
class ManageFeed extends dcNsProcess
{
public static function init(): bool
{
static::$init == defined('DC_CONTEXT_ADMIN')
&& My::phpCompliant()
&& !is_null(dcCore::app()->auth) && !is_null(dcCore::app()->blog)
&& dcCore::app()->auth->check(dcCore::app()->auth->makePermissions([
dcCore::app()->auth::PERMISSION_CONTENT_ADMIN,
]), dcCore::app()->blog->id)
&& ($_REQUEST['part'] ?? 'feeds') === 'feed';
return static::$init;
}
public static function process(): bool
{
if (!static::$init) {
return false;
}
// no action
if (empty($_POST['action'])) {
return true;
}
$z = ZoneclearFeedServer::instance();
$s = $z->settings;
$v = ManageFeedVars::instance();
// save feed
if ($_POST['action'] == 'savefeed') {
// check values
$testfeed_params['feed_feed'] = $v->feed;
if ($v->id) {
$testfeed_params['sql'] = 'AND feed_id <> ' . $v->id . ' ';
}
if ($z->getFeeds($testfeed_params, true)->f(0)) {
dcCore::app()->error->add(__('Record with same feed URL already exists.'));
}
if (empty($v->name)) {
dcCore::app()->error->add(__('You must provide a name.'));
}
if (empty($v->owner)) {
dcCore::app()->error->add(__('You must provide an owner.'));
}
if (!$z::validateURL($v->url)) {
dcCore::app()->error->add(__('You must provide valid site URL.'));
}
if (!$z::validateURL($v->feed)) {
dcCore::app()->error->add(__('You must provide valid feed URL.'));
}
if (null !== $v->cat_id && !dcCore::app()->blog?->getCategory($v->cat_id)) {
dcCore::app()->error->add(__('You must provide valid category.'));
}
// check failed
if (dcCore::app()->error->flag()) {
return true;
}
// save feed
try {
$id = $v->save();
if (!$id) {
throw new Exception(__('Failed to save feed.'));
}
dcPage::addSuccessNotice(
__('Feed successfully created.')
);
dcCore::app()->adminurl?->redirect(
'admin.plugin.' . My::id(),
['part' => 'feed', 'feed_id' => $id]
);
} catch (Exception $e) {
dcCore::app()->error->add($e->getMessage());
return true;
}
return true;
}
return true;
}
public static function render(): void
{
if (!static::$init) {
return;
}
if (is_null(dcCore::app()->adminurl)) {
return;
}
$z = ZoneclearFeedServer::instance();
$s = $z->settings;
$v = ManageFeedVars::instance();
// Prepared entries list
if ($v->id && $v->can_view_page) {
// posts actions
$posts_actions_page = new dcPostsActions(
'plugin.php',
[
'p' => My::id(),
'part' => 'feed',
'feed_id' => $v->id,
'_ANCHOR' => 'entries',
]
);
if ($posts_actions_page->process()) {
return;
}
// posts filters
$post_filter = new PostsFilter();
$post_filter->add('part', 'feed');
$post_filter->add('feed_id', $v->id);
$params = $post_filter->params();
// typehint
$sortby = is_string($post_filter->value('sortby')) ? $post_filter->value('sortby') : 'post_creadt';
$order = is_string($post_filter->value('order')) ? $post_filter->value('order') : 'DESC';
# lexical sort
$sortby_lex = [
// key in sorty_combo (see above) => field in SQL request
'post_title' => 'post_title',
'cat_title' => 'cat_title',
'user_id' => 'P.user_id', ];
# --BEHAVIOR-- adminPostsSortbyLexCombo
dcCore::app()->callBehavior('adminPostsSortbyLexCombo', [& $sortby_lex]);
$params['no_content'] = true;
$params['feed_id'] = $v->id;
$params['order'] = (
array_key_exists($sortby, $sortby_lex) ?
dcCore::app()->con->lexFields($sortby_lex[$sortby]) :
$sortby
) . ' ' . $order;
# posts
try {
$posts = $z->getPostsByFeed($params);
$counter = $z->getPostsByFeed($params, true);
$post_list = new PostsList($posts, $counter->f(0));
} catch (Exception $e) {
dcCore::app()->error->add($e->getMessage());
}
}
// display
dcPage::openModule(
My::id(),
(
$v->id && isset($post_filter) && !dcCore::app()->error->flag() ?
$post_filter->js(dcCore::app()->adminurl->get('admin.plugin.' . My::id(), ['part' => 'feed', 'feed_id' => $v->id], '&') . '#entries') .
dcPage::jsModuleLoad(My::id() . '/js/list.js')
: ''
) .
dcPage::jsPageTabs() .
$v->next_headlink . "\n" . $v->prev_headlink
);
echo
dcPage::breadcrumb([
__('Plugins') => '',
My::name() => dcCore::app()->adminurl->get('admin.plugin.' . My::id()),
($v->id ? __('Edit feed') : __('New feed')) => '',
]) .
dcPage::notices() .
(new Text('h3', ($v->id ? sprintf(__('Edit feed "%s"'), Html::escapeHTML($v->name)) : __('New feed'))))->render();
if ($v->can_view_page) {
# nav link
if ($v->id && ($v->next_link || $v->prev_link)) {
$text = '';
if ($v->prev_link) {
$text .= $v->prev_link;
}
if ($v->next_link && $v->prev_link) {
$text .= ' | ';
}
if ($v->next_link) {
$text .= $v->next_link;
}
echo (new Text('p', $text))->class('nav_prevnext')->render();
}
echo
(new Div('edit-entry'))
->class($v->id ? 'multi-part' : '')
->title($v->id ? __('Feed') : '')
->items([
(new Form('edit-entry-form'))
->method('post')
->action(dcCore::app()->adminurl->get('admin.plugin.' . My::id()))
->fields([
(new Div())
->class('two-cols')
->items([
(new Div())
->class('col70')
->items([
(new Text('h4', __('Feed information'))),
// feed_name
(new Para())
->items([
(new Label(__('Name:'), Label::OUTSIDE_LABEL_BEFORE))
->class('required')
->for('feed_name'),
(new Input('feed_name'))
->class('maximal')
->size(60)
->maxlenght(255)
->value($v->name),
]),
// feed_owner
(new Para())
->items([
(new Label(__('Owner:'), Label::OUTSIDE_LABEL_BEFORE))
->class('required')
->for('feed_owner'),
(new Input('feed_owner'))
->class('maximal')
->size(60)
->maxlenght(255)
->value($v->owner),
]),
// feed_url
(new Para())
->items([
(new Label(__('Site URL:'), Label::OUTSIDE_LABEL_BEFORE))
->class('required')
->for('feed_url'),
(new Input('feed_url'))
->class('maximal')
->size(60)
->maxlenght(255)
->value($v->url),
]),
// feed_feed
(new Para())
->items([
(new Label(__('Feed URL:'), Label::OUTSIDE_LABEL_BEFORE))
->class('required')
->for('feed_feed'),
(new Input('feed_feed'))
->class('maximal')
->size(60)
->maxlenght(255)
->value($v->feed),
]),
// feed_desc
(new Para())
->items([
(new Label(__('Description:'), Label::OUTSIDE_LABEL_BEFORE))
->for('feed_desc'),
(new Input('feed_desc'))
->class('maximal')
->size(60)
->maxlenght(255)
->value($v->desc),
]),
// feed_tags
(new Para())
->items([
(new Label(__('Tags:'), Label::OUTSIDE_LABEL_BEFORE))
->for('feed_tags'),
(new Input('feed_tags'))
->class('maximal')
->size(60)
->maxlenght(255)
->value($v->tags),
]),
// feed_tweeter
(new Para())
->items([
(new Label(__('Tweeter or Identica ident:'), Label::OUTSIDE_LABEL_BEFORE))
->for('feed_tweeter'),
(new Input('feed_tweeter'))
->class('maximal')
->size(60)
->maxlenght(255)
->value($v->tweeter),
]),
]),
(new Div())
->class('col30')
->items([
(new Text('h4', __('Local settings'))),
// feed_cat_id
(new Para())
->items([
(new Label(__('Category:'), Label::OUTSIDE_LABEL_BEFORE))
->for('feed_cat_id'),
(new Select('feed_cat_id'))
->class('maximal')
->items(Combo::postCategories())
->default((string) $v->cat_id),
]),
// feed_status
(new Para())
->items([
(new Label(__('Status:'), Label::OUTSIDE_LABEL_BEFORE))
->for('feed_status'),
(new Select('feed_status'))
->class('maximal')
->items(Combo::feedsStatus())
->default((string) $v->status),
]),
// feed_upd_int
(new Para())
->items([
(new Label(__('Update:'), Label::OUTSIDE_LABEL_BEFORE))
->for('feed_upd_int'),
(new Select('feed_upd_int'))
->class('maximal')
->items(Combo::updateInterval())
->default((string) $v->upd_int),
]),
// feed_lang
(new Para())
->items([
(new Label(__('Lang:'), Label::OUTSIDE_LABEL_BEFORE))
->for('feed_lang'),
(new Select('feed_lang'))
->class('maximal')
->items(L10n::getISOcodes(true))
->default((string) $v->lang),
]),
// feed_get_tags
(new Para())->items([
(new Checkbox('feed_get_tags', $v->get_tags))
->value(1),
(new Label(__('Import tags from feed'), Label::OUTSIDE_LABEL_AFTER))
->class('classic')
->for('feed_get_tags'),
]),
]),
]),
(new Para())
->class('clear')
->items(array_merge(
dcCore::app()->adminurl->hiddenFormFields('admin.plugin.' . My::id(), [
'part' => 'feed',
'feed_id' => $v->id,
'action' => 'savefeed',
]),
[
(new Submit(['save']))
->value(__('Save') . ' (s)')
->accesskey('s'),
dcCore::app()->formNonce(false),
]
)),
]),
])
->render();
}
if ($v->id && $v->can_view_page && isset($post_filter) && isset($post_list) && isset($posts_actions_page) && !dcCore::app()->error->flag()) {
echo '<div class="multi-part" title="' . __('Entries') . '" id="entries">';
# show posts filters
$post_filter->display(
['admin.plugin.' . My::id(),'#entries'],
dcCore::app()->adminurl->getHiddenFormFields('admin.plugin.' . My::id(), [
'part' => 'feed',
'feed_id' => $v->id,
])
);
# fix pager url
$args = $post_filter->values();
unset($args['page']);
$args['page'] = '%s';
# show posts
$post_list->display(
$post_filter,
dcCore::app()->adminurl->get('admin.plugin.' . My::id(), $args, '&') . '#entries',
(new Form('form-entries'))
->method('post')
->action(dcCore::app()->adminurl->get('admin.plugin.' . My::id(), ['part' => 'feed']) . '#entries')
->fields([
(new Text('', '%s')),
(new Div())
->class('two-cols')
->items([
(new Para())
->class('col checkboxes-helpers'),
(new Para())
->class('col right')
->items(array_merge(
dcCore::app()->adminurl->hiddenFormFields('admin.plugin.' . My::id(), $post_filter->values()),
[
(new Hidden('redir', dcCore::app()->adminurl->get('admin.plugin.' . My::id(), $post_filter->values()))),
(new Label(__('Selected entries action:'), Label::OUTSIDE_LABEL_BEFORE))
->for('action'),
(new Select('action'))
->items($posts_actions_page->getCombo()),
(new Submit('feed-action'))
->value(__('ok')),
dcCore::app()->formNonce(false),
]
)),
]),
])
->render()
);
echo '</div>';
}
dcPage::closeModule();
}
}

242
src/ManageFeedVars.php Normal file
View File

@ -0,0 +1,242 @@
<?php
/**
* @brief zoneclearFeedServer, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugin
*
* @author Jean-Christian Denis, BG, Pierre Van Glabeke
*
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
declare(strict_types=1);
namespace Dotclear\Plugin\zoneclearFeedServer;
use dcCore;
use Dotclear\Helper\Html\Html;
/**
* Backend feed manage page vars (type hinting).
*/
class ManageFeedVars
{
/** @var ManageFeedVars $container Self instance */
private static $container;
public readonly int $id;
public readonly string $name;
public readonly string $desc;
public readonly string $owner;
public readonly string $tweeter;
public readonly string $url;
public readonly string $feed;
public readonly string $lang;
public readonly string $tags;
public readonly bool $get_tags;
public readonly ?int $cat_id;
public readonly int $status;
public readonly int $upd_int;
public readonly bool $can_view_page;
public readonly string $next_link;
public readonly string $prev_link;
public readonly string $next_headlink;
public readonly string $prev_headlink;
/**
* Constructor sets properties.
*/
protected function __construct()
{
$z = ZoneclearFeedServer::instance();
$feed_headlink = '<link rel="%s" title="%s" href="' . dcCore::app()->admin->getPageURL() . '&amp;part=feed&amp;feed_id=%s" />';
$feed_link = '<a href="' . dcCore::app()->admin->getPageURL() . '&amp;part=feed&amp;feed_id=%s" title="%s">%s</a>';
$lang = dcCore::app()->auth?->getInfo('user_lang');
// default values
$feed_id = 0;
$feed_name = '';
$feed_desc = '';
$feed_owner = '';
$feed_tweeter = '';
$feed_url = '';
$feed_feed = '';
$feed_lang = is_string($lang) ? $lang : 'en';
$feed_tags = '';
$feed_get_tags = false;
$feed_cat_id = null;
$feed_status = 0;
$feed_upd_int = 86400;
$can_view_page = true;
$next_link = '';
$prev_link = '';
$next_headlink = '';
$prev_headlink = '';
// database values
if (!empty($_REQUEST['feed_id'])) {
$feed = $z->getFeeds(['feed_id' => $_REQUEST['feed_id']]);
if ($feed->isEmpty()) {
dcCore::app()->error->add(__('This feed does not exist.'));
$can_view_page = false;
} else {
$row = new FeedRow($feed);
$feed_id = $row->id;
$feed_name = $row->name;
$feed_desc = $row->desc;
$feed_owner = $row->owner;
$feed_tweeter = $row->tweeter;
$feed_url = $row->url;
$feed_feed = $row->feed;
$feed_lang = $row->lang;
$feed_tags = $row->tags;
$feed_get_tags = $row->get_tags;
$feed_cat_id = $row->cat_id;
$feed_status = $row->status;
$feed_upd_int = $row->upd_int;
$next_params = [
'sql' => 'AND feed_id < ' . $feed_id . ' ',
'limit' => 1,
];
$next_rs = $z->getFeeds($next_params);
if (!$next_rs->isEmpty()) {
$next_row = new FeedRow($next_rs);
$next_link = sprintf(
$feed_link,
$next_row->id,
Html::escapeHTML($next_row->name),
__('next feed') . '&nbsp;&#187;'
);
$next_headlink = sprintf(
$feed_headlink,
'next',
Html::escapeHTML($next_row->name),
$next_row->id
);
}
$prev_params = [
'sql' => 'AND feed_id > ' . $feed_id . ' ',
'limit' => 1,
];
$prev_rs = $z->getFeeds($prev_params);
if (!$prev_rs->isEmpty()) {
$prev_row = new FeedRow($prev_rs);
$prev_link = sprintf(
$feed_link,
$prev_row->id,
Html::escapeHTML($prev_row->name),
'&#171;&nbsp;' . __('previous feed')
);
$prev_headlink = sprintf(
$feed_headlink,
'previous',
Html::escapeHTML($prev_row->name),
$prev_row->id
);
}
}
}
// form values
if (!empty($_POST)) {
$feed_name = !empty($_POST['feed_name']) && is_string($_POST['feed_name']) ? $_POST['feed_name'] : $feed_name;
$feed_desc = !empty($_POST['feed_desc']) && is_string($_POST['feed_desc']) ? $_POST['feed_desc'] : $feed_desc;
$feed_owner = !empty($_POST['feed_owner']) && is_string($_POST['feed_owner']) ? $_POST['feed_owner'] : $feed_owner;
$feed_tweeter = !empty($_POST['feed_tweeter']) && is_string($_POST['feed_tweeter']) ? $_POST['feed_tweeter'] : $feed_tweeter;
$feed_url = !empty($_POST['feed_url']) && is_string($_POST['feed_url']) ? $_POST['feed_url'] : $feed_url;
$feed_feed = !empty($_POST['feed_feed']) && is_string($_POST['feed_feed']) ? $_POST['feed_feed'] : $feed_feed;
$feed_lang = !empty($_POST['feed_lang']) && is_string($_POST['feed_lang']) ? $_POST['feed_lang'] : $feed_lang;
$feed_tags = !empty($_POST['feed_tags']) && is_string($_POST['feed_tags']) ? $_POST['feed_tags'] : $feed_tags;
$feed_get_tags = empty($_POST['feed_get_tags']) ? $feed_get_tags : true;
$feed_cat_id = empty($_POST['feed_cat_id']) ? $feed_cat_id : $_POST['feed_cat_id'];
$feed_upd_int = !empty($_POST['feed_upd_int']) && is_numeric($_POST['feed_upd_int']) ? (int) $_POST['feed_upd_int'] : $feed_upd_int;
$feed_status = empty($_POST['feed_status']) ? $feed_status : 1;
}
// class values
$this->id = $feed_id;
$this->name = $feed_name;
$this->desc = $feed_desc;
$this->owner = $feed_owner;
$this->tweeter = $feed_tweeter;
$this->url = $feed_url;
$this->feed = $feed_feed;
$this->lang = $feed_lang;
$this->tags = $feed_tags;
$this->get_tags = $feed_get_tags;
$this->cat_id = $feed_cat_id;
$this->status = $feed_status;
$this->upd_int = $feed_upd_int;
$this->can_view_page = $can_view_page;
$this->next_link = $next_link;
$this->prev_link = $prev_link;
$this->next_headlink = $next_headlink;
$this->prev_headlink = $prev_headlink;
}
/**
* Get self instance.
*
* @return ManageFeedVars Self instance
*/
public static function instance(): ManageFeedVars
{
if (!(self::$container instanceof self)) {
self::$container = new self();
}
return self::$container;
}
/**
* Create or update feed.
*
* @return int The feed ID
*/
public function save()
{
$z = ZoneclearFeedServer::instance();
$id = $this->id;
// prepare cursor
$cur = $z->openCursor();
$cur->setField('feed_name', $this->name);
$cur->setField('feed_desc', $this->desc);
$cur->setField('feed_owner', $this->owner);
$cur->setField('feed_tweeter', $this->tweeter);
$cur->setField('feed_url', $this->url);
$cur->setField('feed_feed', $this->feed);
$cur->setField('feed_lang', $this->lang);
$cur->setField('feed_tags', $this->tags);
$cur->setField('feed_get_tags', $this->get_tags);
$cur->setField('cat_id', $this->cat_id);
$cur->setField('feed_status', $this->status);
$cur->setField('feed_upd_int', $this->upd_int);
# --BEHAVIOR-- adminBeforeZoneclearFeedServerFeedSave - Cursor, int
dcCore::app()->callBehavior('adminBeforeZoneclearFeedServerFeedSave', $cur, $id);
if (!$id) {
// create feed
$id = $z->addFeed($cur);
} else {
// update feed
$z->updateFeed($id, $cur);
}
# --BEHAVIOR-- adminAfterZoneclearFeedServerFeedSave - Cursor - int
dcCore::app()->callBehavior('adminAfterZoneclearFeedServerFeedSave', $cur, $id);
return $id;
}
}

94
src/My.php Normal file
View File

@ -0,0 +1,94 @@
<?php
/**
* @brief zoneclearFeedServer, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugin
*
* @author Jean-Christian Denis, BG, Pierre Van Glabeke
*
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
declare(strict_types=1);
namespace Dotclear\Plugin\zoneclearFeedServer;
use dcCore;
/**
* Module definitions.
*/
class My
{
/** @var string This module required php version */
public const PHP_MIN = '8.1';
/** @var string This module database table name */
public const TABLE_NAME = 'zc_feed';
/** @var string This module meta prefix */
public const META_PREFIX = 'zoneclearfeed_';
/** @var array<int,string> This module template blocks */
public const TPL_BLOCKS = [
'Feeds',
'FeedsFooter',
'FeedsHeader',
'FeedIf',
];
/** @var array<int,string> This module template values */
public const TPL_VALUES = [
'FeedsCount',
'FeedsEntriesCount',
'FeedEntriesCount',
'FeedCategory',
'FeedCategoryID',
'FeedCategoryURL',
'FeedCategoryShortURL',
'FeedID',
'FeedIfFirst',
'FeedIfOdd',
'FeedLang',
'FeedName',
'FeedOwner',
'FeedDesc',
'FeedSiteURL',
'FeedFeedURL',
];
/**
* @return string This module id
*/
public static function id(): string
{
return basename(dirname(__DIR__));
}
/**
* @return string This module name
*/
public static function name(): string
{
$name = dcCore::app()->plugins->moduleInfo(self::id(), 'name');
return __(is_string($name) ? $name : self::id());
}
/**
* @return string This module path
*/
public static function path(): string
{
return dirname(__DIR__);
}
/**
* @return bool True on this module php version complied
*/
public static function phpCompliant(): bool
{
return version_compare(phpversion(), self::PHP_MIN, '>=');
}
}

View File

@ -10,23 +10,31 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
if (!defined('DC_CONTEXT_ADMIN')) {
return null;
}
declare(strict_types=1);
namespace Dotclear\Plugin\zoneclearFeedServer;
use ArrayObject;
use adminGenericFilterV2;
use dcAdminCombos;
use dcAdminFilter;
use dcAdminFilters;
use dcCore;
use dcUtils;
use Dotclear\Helper\Html\Html;
use Exception;
/**
* @ingroup DC_PLUGIN_ZONECLEARFEEDSERVER
* @brief Feeds server - Posts list filters methods
* @since 2.20
* @see adminGenericFilter for more info
* Backend feed posts list filters.
*/
class zcfsPostFilter extends adminGenericFilter
class PostsFilter extends adminGenericFilterV2
{
public function __construct()
{
parent::__construct(dcCore::app(), 'zcfs_entries');
// use user posts pref
parent::__construct('posts');
$filters = new arrayObject([
$filters = new ArrayObject([
dcAdminFilters::getPageFilter(),
$this->getPostUserFilter(),
$this->getPostCategoriesFilter(),
@ -50,8 +58,8 @@ class zcfsPostFilter extends adminGenericFilter
$users = null;
try {
$users = dcCore::app()->blog->getPostsUsers();
if ($users->isEmpty()) {
$users = dcCore::app()->blog?->getPostsUsers();
if (is_null($users) || $users->isEmpty()) {
return null;
}
} catch (Exception $e) {
@ -81,8 +89,8 @@ class zcfsPostFilter extends adminGenericFilter
$categories = null;
try {
$categories = dcCore::app()->blog->getCategories();
if ($categories->isEmpty()) {
$categories = dcCore::app()->blog?->getCategories();
if (is_null($categories) || $categories->isEmpty()) {
return null;
}
} catch (Exception $e) {
@ -96,10 +104,12 @@ class zcfsPostFilter extends adminGenericFilter
__('(No cat)') => 'NULL',
];
while ($categories->fetch()) {
if (is_numeric($categories->f('level')) && is_string($categories->f('cat_title'))) {
$combo[
str_repeat('&nbsp;', ($categories->level - 1) * 4) .
html::escapeHTML($categories->cat_title) . ' (' . $categories->nb_post . ')'
] = $categories->cat_id;
str_repeat('&nbsp;', ((int) $categories->f('level') - 1) * 4) .
Html::escapeHTML($categories->f('cat_title')) . ' (' . $categories->f('nb_post') . ')'
] = $categories->f('cat_id');
}
}
return (new dcAdminFilter('cat_id'))
@ -131,8 +141,8 @@ class zcfsPostFilter extends adminGenericFilter
$dates = null;
try {
$dates = dcCore::app()->blog->getDates(['type' => 'month']);
if ($dates->isEmpty()) {
$dates = dcCore::app()->blog?->getDates(['type' => 'month']);
if (is_null($dates) || $dates->isEmpty()) {
return null;
}
} catch (Exception $e) {

View File

@ -10,139 +10,173 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
if (!defined('DC_CONTEXT_ADMIN')) {
return null;
}
declare(strict_types=1);
namespace Dotclear\Plugin\zoneclearFeedServer;
use ArrayObject;
use adminGenericListV2;
use dcCore;
use dcPager;
use Dotclear\Helper\Date;
use Dotclear\Helper\Html\Form\{
Checkbox,
Div,
Link,
Para,
Text
};
use Dotclear\Helper\Html\Html;
/**
* @ingroup DC_PLUGIN_ZONECLEARFEEDSERVER
* @brief Feeds server - Posts list methods
* @since 2.6
* @see adminGenericList for more info
* Backend feed posts lists.
*/
class zcfsEntriesList extends adminGenericList
class PostsList extends adminGenericListV2
{
public function display($page, $nb_per_page, $base_url, $enclose_block = '', $filter = false)
public function display(PostsFilter $filter, string $base_url, string $enclose_block = ''): void
{
if ($this->rs->isEmpty()) {
echo '<p><strong>' . (
$filter ?
echo
(new Text(
'p',
$filter->show() ?
__('No entries matches the filter') :
__('No entries')
) . '</strong></p>';
} else {
$pager = new dcPager($page, $this->rs_count, $nb_per_page, 10);
))
->class('info')
->render();
return;
}
$page = is_numeric($filter->value('page')) ? (int) $filter->value('page') : 1;
$nbpp = is_numeric($filter->value('nb')) ? (int) $filter->value('nb') : 10;
$count = (int) $this->rs_count;
$pager = new dcPager($page, $count, $nbpp, 10);
$pager->base_url = $base_url;
$entries = [];
if (isset($_REQUEST['feeds'])) {
foreach ($_REQUEST['feeds'] as $v) {
$entries[(int) $v] = true;
}
}
$cols = new ArrayObject([
'title' => (new Text('th', __('Title')))
->class('first')
->extra('colspan="2"'),
'date' => (new Text('th', __('Date')))
->extra('scope="col"'),
'author' => (new Text('th', __('Author')))
->extra('scope="col"'),
'category' => (new Text('th', __('Category')))
->extra('scope="col"'),
'status' => (new Text('th', __('Status')))
->extra('scope="col"'),
]);
$html_block = '<div class="table-outer clear">' .
'<table>' .
'<caption>' . (
$filter ?
sprintf(__('List of %s entries matching the filter.'), $this->rs_count) :
sprintf(__('List of entries (%s)'), $this->rs_count)
) . '</caption>';
$cols = [
'title' => '<th scope="col" colspan="2" class="first">' . __('Title') . '</th>',
'date' => '<th scope="col">' . __('Date') . '</th>',
'author' => '<th scope="col">' . __('Author') . '</th>',
'category' => '<th scope="col">' . __('Category') . '</th>',
'status' => '<th scope="col">' . __('Status') . '</th>',
];
$cols = new ArrayObject($cols);
dcCore::app()->callBehavior('adminZcfsPostListHeader', $this->rs, $cols);
$this->userColumns('zcfs_entries', $cols);
$html_block .= '<tr>' . implode(iterator_to_array($cols)) . '</tr>%s</table></div>';
if ($enclose_block) {
$html_block = sprintf($enclose_block, $html_block);
}
echo $pager->getLinks();
$blocks = explode('%s', $html_block);
echo $blocks[0];
$this->userColumns(My::id() . 'posts', $cols);
$lines = [];
while ($this->rs->fetch()) {
echo $this->postLine();
$lines[] = $this->line(isset($_POST['entries']) && in_array($this->rs->post_id, $_POST['entries']));
}
echo $blocks[1];
echo $pager->getLinks();
}
echo
$pager->getLinks() .
sprintf(
$enclose_block,
(new Div())
->class('table-outer')
->items([
(new Para(null, 'table'))
->items([
(new Text(
'caption',
$filter->show() ?
sprintf(__('List of %s entries matching the filter.'), $this->rs_count) :
sprintf(__('List of entries. (%s)'), $this->rs_count)
)),
(new Para(null, 'tr'))
->items(iterator_to_array($cols)),
(new Para(null, 'tbody'))
->items($lines),
]),
])
->render()
) .
$pager->getLinks();
}
private function postLine()
private function line(bool $checked): Para
{
$cat_link = dcCore::app()->auth->check(dcCore::app()->auth->makePermissions([dcAuth::PERMISSION_CATEGORIES]), dcCore::app()->blog->id) ?
'<a href="category.php?id=%s" title="' . __('Edit category') . '">%s</a>'
: '%2$s';
$cat_title = (new Text('', __('None')));
if ($this->rs->cat_title
&& dcCore::app()->auth?->check(dcCore::app()->auth->makePermissions([dcCore::app()->auth::PERMISSION_CATEGORIES]), dcCore::app()->blog?->id)
) {
$cat_title = (new Link())
->href('category.php?id=' . $this->rs->cat_id)
->title(Html::escapeHTML(__('Edit category')))
->text(Html::escapeHTML($this->rs->cat_title));
}
$cat_title = $this->rs->cat_title ?
sprintf($cat_link, $this->rs->cat_id, html::escapeHTML($this->rs->cat_title))
: __('None');
$img = '<img alt="%1$s" title="%1$s" src="images/%2$s" />';
$img_status = '';
$sts_class = '';
switch ($this->rs->post_status) {
case 1:
$img_status = sprintf($img, __('Published'), 'check-on.png');
$img_title = __('Published');
$img_src = 'check-on.png';
$sts_class = ' sts-online';
break;
case 0:
$img_status = sprintf($img, __('Unpublished'), 'check-off.png');
$sts_class = 'sts-offline';
break;
case -1:
$img_status = sprintf($img, __('Scheduled'), 'scheduled.png');
$img_title = __('Scheduled');
$img_src = 'scheduled.png';
$sts_class = ' sts-scheduled';
break;
case -2:
$img_status = sprintf($img, __('Pending'), 'check-wrn.png');
$img_title = __('Pending');
$img_src = 'check-wrn.png';
$sts_class = ' sts-pending';
break;
default:
$img_title = __('Unpublished');
$img_src = 'check-off.png';
$sts_class = ' sts-offline';
break;
}
$res = '<tr class="line ' . ($this->rs->post_status != 1 ? 'offline ' : '') . $sts_class . '"' .
' id="p' . $this->rs->post_id . '">';
$cols = new ArrayObject([
'check' => (new Para(null, 'td'))
->class('nowrap minimal')
->items([
(new Checkbox(['entries[]'], $checked))
->value($this->rs->post_id)
->disabled(!$this->rs->isEditable()),
]),
'title' => (new Para(null, 'td'))
->class('maximal')
->items([
(new Link())
->href(dcCore::app()->getPostAdminURL($this->rs->post_type, $this->rs->post_id))
->title(Html::escapeHTML($this->rs->getURL()))
->text(Html::escapeHTML(trim(Html::clean($this->rs->post_title)))),
]),
'date' => (new Text('td', Date::dt2str(__('%Y-%m-%d %H:%M'), $this->rs->post_dt)))
->class('nowrap count'),
'author' => (new Text('td', Html::escapeHTML($this->rs->user_id)))
->class('nowrap'),
'category' => (new Para(null, 'td'))
->class('nowrap')
->items([$cat_title]),
'status' => (new Para(null, 'td'))
->class('nowrap status')
->items([
(new Text('img', ''))
->title($img_title)
->extra('src="images/' . $img_src . '"'),
]),
]);
$cols = [
'check' => '<td class="nowrap minimal">' .
form::checkbox(['entries[]'], $this->rs->post_id, '', '', '', !$this->rs->isEditable()) . '</td>',
'title' => '<td scope="row" class="maximal"><a href="' .
dcCore::app()->getPostAdminURL($this->rs->post_type, $this->rs->post_id) . '" ' .
'title="' . html::escapeHTML($this->rs->getURL()) . '">' .
html::escapeHTML(trim(html::clean($this->rs->post_title))) . '</a></td>',
'date' => '<td class="nowrap count">' . dt::dt2str(__('%Y-%m-%d %H:%M'), $this->rs->post_dt) . '</td>',
'author' => '<td class="nowrap">' . html::escapeHTML($this->rs->user_id) . '</td>',
'category' => '<td class="nowrap">' . $cat_title . '</td>',
'status' => '<td class="nowrap status">' . $img_status . '</td>',
];
$this->userColumns(My::id() . 'posts', $cols);
$cols = new ArrayObject($cols);
dcCore::app()->callBehavior('adminZcfsPostListValue', $this->rs, $cols);
$this->userColumns('zcfs_entries', $cols);
$res .= implode(iterator_to_array($cols));
$res .= '</tr>';
return $res;
return (new Para('p' . $this->rs->post_id, 'tr'))
->class('line' . ($this->rs->post_status != 1 ? ' offline ' : '') . $sts_class)
->items(iterator_to_array($cols));
}
}

View File

@ -10,35 +10,44 @@
* @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);
namespace Dotclear\Plugin\zoneclearFeedServer;
use dcCore;
use dcNsProcess;
/**
* Module prepend.
*/
class Prepend extends dcNsProcess
{
public static function init(): bool
{
static::$init = My::phpCompliant();
return static::$init;
}
Clearbricks::lib()->autoload([
'zoneclearFeedServer' => __DIR__ . '/inc/class.zoneclearfeedserver.php',
'zcfsAdminBehaviors' => __DIR__ . '/inc/class.zcfsadminbehaviors.php',
'zcfsPostFilter' => __DIR__ . '/inc/class.zcfspostfilter.php',
'zcfsEntriesList' => __DIR__ . '/inc/class.zcfsentrieslist.php',
'zcfsFeedsList' => __DIR__ . '/inc/class.zcfsfeedslist.php',
'zcfsFeedsActions' => __DIR__ . '/inc/class.zcfsfeedsactions.php',
'zcfsDefaultFeedsActions' => __DIR__ . '/inc/class.zcfsdefaultfeedsactions.php',
'zcfsTemplate' => __DIR__ . '/inc/class.zcfstemplate.php',
'zcfsPublicBehaviors' => __DIR__ . '/inc/class.zcfspublicbehaviors.php',
'zcfsRsExtPosts' => __DIR__ . '/inc/class.zcfsrsextposts.php',
'zcfsUrlHandler' => __DIR__ . '/inc/class.zcfsurlhandler.php',
'zcfsActivityReportBehaviors' => __DIR__ . '/inc/class.zcfsactivityreportbehaviors.php',
'zcfsUpgrade' => __DIR__ . '/inc/class.zcfsupgrade.php',
]);
public static function process(): bool
{
if (!static::$init) {
return false;
}
// public url for page of description of the flux
dcCore::app()->url->register(
'zoneclearFeedsPage',
'zcfeeds',
'^zcfeeds(.*?)$',
['zcfsUrlHandler', 'zcFeedsPage']
[UrlHandler::class, 'zoneclearFeedsPage']
);
// Add to report on plugin activityReport
if (defined('ACTIVITY_REPORT_V2')) {
zcfsActivityReportBehaviors::init();
// report zoneclearFeedServer activities
if (defined('ACTIVITY_REPORT') && ACTIVITY_REPORT == 3) {
ActivityReportActions::init();
}
return true;
}
}

View File

@ -10,74 +10,83 @@
* @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);
namespace Dotclear\Plugin\zoneclearFeedServer;
use context;
use dcCore;
use rsExtPost;
use rsExtPostPublic;
use Dotclear\Database\MetaRecord;
use Dotclear\Helper\Html\Html;
/**
* @ingroup DC_PLUGIN_ZONECLEARFEEDSERVER
* @brief Mix your blog with a feeds planet - rs methods.
* @since 2.6
* Posts record extension to integrate feed info.
*/
class zcfsRsExtPosts extends rsExtPost
class RsExtPosts extends rsExtPost
{
public static function zc()
{
return new zoneclearFeedServer();
}
/** @var array<string,mixed> $brother_extensions Stack posts record extensions */
public static array $brother_extensions = [];
/**
* Get feed meta.
*
* @param dcRecord $rs record instance
* @param string $info Feed info key
* @return string Feed info value
* @param MetaRecord $rs The record instance
* @param string $info The feed info key
*
* @return null|string Feed info value
*/
public static function zcFeed($rs, $info)
public static function zcFeed(MetaRecord $rs, string $info): ?string
{
$meta = dcCore::app()->meta->getMetadata([
'post_id' => $rs->post_id,
'meta_type' => 'zoneclearfeed_' . $info,
'post_id' => $rs->f('post_id'),
'meta_type' => My::META_PREFIX . $info,
'limit' => 1,
]);
return $meta->isEmpty() ? null : $meta->meta_id;
return $meta->isEmpty() || !is_string($meta->f('meta_id')) ? null : $meta->f('meta_id');
}
/**
* Call other rs extension.
*
* @param string $type Type of extension
* @param array $args Arguments
* @return mixed record extension ressource
* @param string $type The type of extension
* @param array<string|int,mixed> $args The arguments
*
* @return string The record extension ressource result
*/
public static function zcFeedBrother($type, $args)
public static function zcFeedBrother(string $type, array $args): string
{
$ext = dcCore::app()->__get('beforeZcFeedRsExt');
if (null !== $ext && !empty($ext[$type])) {
$ext = static::$brother_extensions;
if (isset($ext[$type]) && is_callable($ext[$type])) {
$func = $ext[$type];
} elseif (is_callable(['rsExtPostPublic', $type])) {
$func = ['rsExtPostPublic', $type];
} elseif (is_callable([rsExtPostPublic::class, $type])) {
$func = [rsExtPostPublic::class, $type];
} elseif (is_callable([rsExtPost::class, $type])) {
$func = [rsExtPost::class, $type];
} else {
$func = ['rsExtPost', $type];
return '';
}
return call_user_func_array($func, $args);
$cb = call_user_func_array($func, $args);
return is_string($cb) ? $cb : '';
}
/**
* Get author link from post to feed.
*
* @param dcRecord $rs record instance
* @return string Author link
* @param MetaRecord $rs The record instance
* @return string The author link
*/
public static function getAuthorLink(dcRecord $rs): string
public static function getAuthorLink(MetaRecord $rs): string
{
$author = $rs->zcFeed('author');
$site = $rs->zcFeed('site');
$sitename = $rs->zcFeed('sitename');
$author = $rs->__call('zcFeed', ['author']);
$site = $rs->__call('zcFeed', ['site']);
$sitename = $rs->__call('zcFeed', ['sitename']);
return $author && $sitename ?
return is_string($author) && is_string($site) && is_string($sitename) ?
sprintf('%s (<a href="%s">%s</a>)', $author, $site, $sitename) :
self::zcFeedBrother('getAuthorLink', [&$rs]);
}
@ -85,14 +94,15 @@ class zcfsRsExtPosts extends rsExtPost
/**
* Get author CN from post to feed.
*
* @param dcRecord $rs record instance
* @return string Author CN
* @param MetaRecord $rs The record instance
*
* @return string The author CN
*/
public static function getAuthorCN(dcRecord $rs): string
public static function getAuthorCN(MetaRecord $rs): string
{
$author = $rs->zcFeed('author');
$author = $rs->__call('zcFeed', ['author']);
return $author ?
return is_string($author) ?
$author :
self::zcFeedBrother('getAuthorCN', [&$rs]);
}
@ -100,36 +110,37 @@ class zcfsRsExtPosts extends rsExtPost
/**
* Get post link from post to feed.
*
* @param dcRecord $rs record instance
* @return string Post link
* @param MetaRecord $rs The record instance
*
* @return string The post link
*/
public static function getURL(dcRecord $rs): string
public static function getURL(MetaRecord $rs): string
{
$url = $rs->zcFeed('url');
$types = json_decode(dcCore::app()->blog->settings->__get(basename(dirname('../' . __DIR__)))->post_title_redir);
$full = is_array($types) && in_array(dcCore::app()->url->type, $types);
$url = $rs->__call('zcFeed', ['url']);
$site = $rs->__call('zcFeed', ['site']);
$full = in_array(dcCore::app()->url->type, ZoneclearFeedServer::instance()->settings->post_title_redir);
return $url && $full ?
zoneclearFeedServer::absoluteURL($rs->zcFeed('site'), $url) :
return is_string($site) && is_string($url) && $full ?
ZoneclearFeedServer::instance()::absoluteURL($site, $url) :
self::zcFeedBrother('getURL', [&$rs]);
}
/**
* Get post content from post to feed.
*
* @param dcRecord $rs record instance
* @return string Post content
* @param MetaRecord $rs The record instance
* @param mixed $absolute_urls Serve absolute URL (type "mixed" from rsExtPost)
*
* @return string The post content
*/
public static function getContent(dcRecord $rs, bool $absolute_urls = false): string
public static function getContent(MetaRecord $rs, mixed $absolute_urls = false): string
{
$url = $rs->zcFeed('url');
$sitename = $rs->zcFeed('sitename');
$url = $rs->__call('zcFeed', ['url']);
$sitename = $rs->__call('zcFeed', ['sitename']);
$content = self::zcFeedBrother('getContent', [&$rs, $absolute_urls]);
if ($url && $sitename && $rs->post_type == 'post') {
$types = json_decode(dcCore::app()->blog->settings->__get(basename(dirname('../' . __DIR__)))->post_full_tpl);
if (is_array($types) && in_array(dcCore::app()->url->type, $types)) {
if (is_string($url) && is_string($sitename) && $rs->f('post_type') == 'post') {
if (in_array(dcCore::app()->url->type, ZoneclearFeedServer::instance()->settings->post_full_tpl)) {
return $content . sprintf(
'<p class="zoneclear-original"><em>%s</em></p>',
sprintf(__('Original post on <a href="%s">%s</a>'), $url, $sitename)
@ -137,7 +148,7 @@ class zcfsRsExtPosts extends rsExtPost
}
$content = context::remove_html($content);
$content = context::cut_string($content, 350);
$content = html::escapeHTML($content);
$content = Html::escapeHTML($content);
return sprintf(
'<p>%s... <em><a href="%s" title="%s">%s</a></em></p>',

125
src/Settings.php Normal file
View File

@ -0,0 +1,125 @@
<?php
/**
* @brief zoneclearFeedServer, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugin
*
* @author Jean-Christian Denis, BG, Pierre Van Glabeke
*
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
declare(strict_types=1);
namespace Dotclear\Plugin\zoneclearFeedServer;
use dcCore;
use Exception;
/**
* Module settings (type hinting).
*/
class Settings
{
/** @var Settings Self instance */
private static $instance;
public readonly bool $active;
public readonly bool $pub_active;
public readonly bool $post_status_new;
public readonly int $bhv_pub_upd;
public readonly int $update_limit;
public readonly bool $keep_empty_feed;
public readonly int $tag_case;
/** @var array<int,string> */
public readonly array $post_full_tpl;
/** @var array<int,string> */
public readonly array $post_title_redir;
public readonly string $user;
/**
* Constructor set up plugin settings.
*/
protected function __construct()
{
if (is_null(dcCore::app()->blog)) {
throw new Exception(__('Blog is not defined'));
}
$s = dcCore::app()->blog->settings->get(My::id());
$update_limit = is_numeric($s->get('update_limit')) ? (int) $s->get('update_limit') : 1;
$this->active = !empty($s->get('active'));
$this->pub_active = !empty($s->get('pub_active'));
$this->post_status_new = !empty($s->get('post_status_new'));
$this->bhv_pub_upd = is_numeric($s->get('bhv_pub_upd')) ? (int) $s->get('bhv_pub_upd') : 1;
$this->update_limit = $update_limit < 1 ? 10 : $update_limit;
$this->keep_empty_feed = !empty($s->get('keep_empty_feed'));
$this->tag_case = is_numeric($s->get('tag_case')) ? (int) $s->get('tag_case') : 0;
$this->post_full_tpl = is_array($s->get('post_full_tpl')) ? $s->get('post_full_tpl') : [];
$this->post_title_redir = is_array($s->get('post_title_redir')) ? $s->get('post_title_redir') : [];
$this->user = is_string($s->get('user')) ? $s->get('user') : '';
}
public static function instance(): Settings
{
if (!(self::$instance instanceof Settings)) {
self::$instance = new Settings();
}
return self::$instance;
}
public function isset(string $key): bool
{
return property_exists($this, $key);
}
public function get(string $key): mixed
{
return $this->{$key} ?? null;
}
/**
* Overwrite a plugin settings (in db).
*
* @param string $key The setting ID
* @param mixed $value The setting value
*
* @return bool True on success
*/
public function set(string $key, mixed $value): bool
{
$s = dcCore::app()->blog?->settings->get(My::id());
if (!is_null($s) && property_exists($this, $key) && settype($value, gettype($this->{$key})) === true) {
$s->drop($key);
$s->put($key, $value, gettype($this->{$key}), '', true, true);
return true;
}
return false;
}
/**
* List defined settings keys.
*
* @return array<string,mixed> The settings keys
*/
public function dump(): array
{
return get_object_vars($this);
}
}

View File

@ -10,44 +10,47 @@
* @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);
namespace Dotclear\Plugin\zoneclearFeedServer;
use ArrayObject;
use dcCore;
use dcTemplate;
use Dotclear\Helper\Html\Html;
/**
* @ingroup DC_PLUGIN_ZONECLEARFEEDSERVER
* @brief Mix your blog with a feeds planet - template methods.
* @since 2.6
* Frontend template blocks and values.
*/
class zcfsTemplate
class Template
{
public static function Feeds($a, $c)
public static function Feeds(ArrayObject $a, string $c): string
{
$lastn = -1;
$p = '';
if (isset($a['lastn'])) {
if (isset($a['lastn']) && is_numeric($a['lastn'])) {
$lastn = abs((int) $a['lastn']) + 0;
$p .= "\$zcfs_params['limit'] = " . $lastn . ";\n";
}
if (isset($a['cat_id'])) {
if (isset($a['cat_id']) && is_string($a['cat_id'])) {
$p .= "@\$zcfs_params['sql'] .= 'AND Z.cat_id = " . addslashes($a['cat_id']) . " ';\n";
}
if (isset($a['no_category'])) {
$p .= "@\$zcfs_params['sql'] .= 'AND Z.cat_id IS NULL ';\n";
}
if (!empty($a['site_url'])) {
if (!empty($a['site_url']) && is_string($a['site_url'])) {
$p .= "\$zcfs_params['feed_url'] = '" . addslashes($a['site_url']) . "';\n";
}
if (isset($a['feed_status'])) {
if (isset($a['feed_status']) && is_numeric($a['feed_status'])) {
$p .= "\$zcfs_params['feed_status'] = " . ((int) $a['feed_status']) . ";\n";
} else {
$p .= "\$zcfs_params['feed_status'] = 1;\n";
}
if (!empty($a['feed_url'])) {
if (!empty($a['feed_url']) && is_string($a['feed_url'])) {
$p .= "\$zcfs_params['feed_feed'] = '" . addslashes($a['feed_url']) . "';\n";
}
if (isset($a['feed_owner'])) {
$p .= "@\$zcfs_params['sql'] .= \"AND Z.feed_owner = '" . addslashes($a['author']) . "' \";\n";
if (isset($a['feed_owner']) && is_string($a['feed_owner'])) {
$p .= "@\$zcfs_params['sql'] .= \"AND Z.feed_owner = '" . addslashes($a['feed_owner']) . "' \";\n";
}
$sortby = 'feed_creadt';
@ -71,7 +74,7 @@ class zcfsTemplate
break;
}
}
if (isset($a['order']) && preg_match('/^(desc|asc)$/i', $a['order'])) {
if (isset($a['order']) && is_string($a['order']) && preg_match('/^(desc|asc)$/i', $a['order'])) {
$order = $a['order'];
}
$p .= "\$zcfs_params['order'] = '" . $sortby . ' ' . $order . "';\n";
@ -79,26 +82,26 @@ class zcfsTemplate
return
'<?php ' . $p .
'dcCore::app()->ctx->feeds_params = $zcfs_params;' . "\n" .
'$zcfs = new zoneclearFeedServer();' . "\n" .
'$zcfs = ' . ZoneclearFeedServer::class . '::instance();' . "\n" .
'dcCore::app()->ctx->feeds = $zcfs->getFeeds($zcfs_params); unset($zcfs_params,$zcfs);' . "\n" .
"?>\n" .
'<?php while (dcCore::app()->ctx->feeds->fetch()) : ?>' . $c . '<?php endwhile; ' .
'dcCore::app()->ctx->feeds = null; dcCore::app()->ctx->feeds_params = null; ?>';
}
public static function FeedIf($a, $c)
public static function FeedIf(ArrayObject $a, string $c): string
{
$if = [];
$operator = isset($a['operator']) ? self::getOperator($a['operator']) : '&&';
$operator = isset($a['operator']) && is_string($a['operator']) ? dcTemplate::getOperator($a['operator']) : '&&';
if (isset($a['type'])) {
if (isset($a['type']) && is_string($a['type'])) {
$type = trim($a['type']);
$type = !empty($type) ? $type : 'feed';
$if[] = 'dcCore::app()->ctx->feeds->feed_type == "' . addslashes($type) . '"';
}
if (isset($a['site_url'])) {
$url = trim($a['feed_url']);
if (isset($a['site_url']) && is_string($a['site_url'])) {
$url = trim($a['site_url']);
if (substr($url, 0, 1) == '!') {
$url = substr($url, 1);
$if[] = 'dcCore::app()->ctx->feeds->feed_url != "' . addslashes($url) . '"';
@ -106,8 +109,8 @@ class zcfsTemplate
$if[] = 'dcCore::app()->ctx->feeds->feed_url == "' . addslashes($url) . '"';
}
}
if (isset($a['feed_url'])) {
$url = trim($a['feed_feed']);
if (isset($a['feed_url']) && is_string($a['feed_url'])) {
$url = trim($a['feed_url']);
if (substr($url, 0, 1) == '!') {
$url = substr($url, 1);
$if[] = 'dcCore::app()->ctx->feeds->feed_feed != "' . addslashes($url) . '"';
@ -115,7 +118,7 @@ class zcfsTemplate
$if[] = 'dcCore::app()->ctx->feeds->feed_feed == "' . addslashes($url) . '"';
}
}
if (isset($a['category'])) {
if (isset($a['category']) && is_string($a['category'])) {
$category = addslashes(trim($a['category']));
if (substr($category, 0, 1) == '!') {
$category = substr($category, 1);
@ -146,112 +149,100 @@ class zcfsTemplate
'<?php if(' . implode(' ' . $operator . ' ', $if) . ') : ?>' . $c . '<?php endif; ?>';
}
public static function FeedIfFirst($a)
public static function FeedIfFirst(ArrayObject $a): string
{
$ret = $a['return'] ?? 'first';
$ret = html::escapeHTML($ret);
$ret = Html::escapeHTML(isset($a['return']) && is_string($a['return']) ? $a['return'] : 'first');
return
'<?php if (dcCore::app()->ctx->feeds->index() == 0) { ' .
"echo '" . addslashes($ret) . "'; } ?>";
}
public static function FeedIfOdd($a)
public static function FeedIfOdd(ArrayObject $a): string
{
$ret = $a['return'] ?? 'odd';
$ret = html::escapeHTML($ret);
$ret = Html::escapeHTML(isset($a['return']) && is_string($a['return']) ? $a['return'] : 'odd');
return
'<?php if ((dcCore::app()->ctx->feeds->index()+1)%2 == 1) { ' .
"echo '" . addslashes($ret) . "'; } ?>";
}
public static function FeedDesc($a)
public static function FeedDesc(ArrayObject $a): string
{
return self::getValue($a, 'dcCore::app()->ctx->feeds->feed_desc');
}
public static function FeedOwner($a)
public static function FeedOwner(ArrayObject $a): string
{
return self::getValue($a, 'dcCore::app()->ctx->feeds->feed_owner');
}
public static function FeedCategory($a)
public static function FeedCategory(ArrayObject $a): string
{
return self::getValue($a, 'dcCore::app()->ctx->feeds->cat_title');
}
public static function FeedCategoryID($a)
public static function FeedCategoryID(ArrayObject $a): string
{
return self::getValue($a, 'dcCore::app()->ctx->feeds->cat_id');
}
public static function FeedCategoryURL($a)
public static function FeedCategoryURL(ArrayObject $a): string
{
return self::getValue($a, 'dcCore::app()->blog->url.dcCore::app()->url->getBase(\'category\').\'/\'.html::sanitizeURL(dcCore::app()->ctx->feeds->cat_url)');
return self::getValue($a, 'dcCore::app()->blog->url.dcCore::app()->url->getBase(\'category\').\'/\'.Html::sanitizeURL(dcCore::app()->ctx->feeds->cat_url)');
}
public static function FeedCategoryShortURL($a)
public static function FeedCategoryShortURL(ArrayObject $a): string
{
return self::getValue($a, 'dcCore::app()->ctx->feeds->cat_url');
}
public static function FeedID($a)
public static function FeedID(ArrayObject $a): string
{
return self::getValue($a, 'dcCore::app()->ctx->feeds->feed_id');
}
public static function FeedLang($a)
public static function FeedLang(ArrayObject $a): string
{
$f = dcCore::app()->tpl->getFilters($a);
return empty($a['full']) ?
'<?php echo ' . sprintf($f, 'dcCore::app()->ctx->feeds->feed_lang') . '; ?>' :
'<?php $langs = l10n::getISOcodes(); if (isset($langs[dcCore::app()->ctx->feeds->feed_lang])) { echo ' .
sprintf($f, '$langs[dcCore::app()->ctx->feeds->feed_lang]') . '; } else { echo ' .
sprintf($f, 'dcCore::app()->ctx->feeds->feed_lang') . '; } unset($langs); ?>';
self::getValue($a, 'dcCore::app()->ctx->feeds->feed_lang') :
'<?php $langs = l10n::getISOcodes(); if (isset($langs[dcCore::app()->ctx->feeds->feed_lang])) { ?>' .
self::getValue($a, '$langs[dcCore::app()->ctx->feeds->feed_lang]') .
'<?php } else { ?>' .
self::getValue($a, 'dcCore::app()->ctx->feeds->feed_lang') .
'<?php ; } unset($langs); ?>';
}
public static function FeedName($a)
public static function FeedName(ArrayObject $a): string
{
return self::getValue($a, 'dcCore::app()->ctx->feeds->feed_name');
}
public static function FeedSiteURL($a)
public static function FeedSiteURL(ArrayObject $a): string
{
return self::getValue($a, 'dcCore::app()->ctx->feeds->feed_url');
}
public static function FeedFeedURL($a)
public static function FeedFeedURL(ArrayObject $a): string
{
return self::getValue($a, 'dcCore::app()->ctx->feeds->feed_feed');
}
public static function FeedsHeader($a, $c)
public static function FeedsHeader(ArrayObject $a, string $c): string
{
return '<?php if (dcCore::app()->ctx->feeds->isStart()) : ?>' . $c . '<?php endif; ?>';
}
public static function FeedsFooter($a, $c)
public static function FeedsFooter(ArrayObject $a, string $c): string
{
return '<?php if (dcCore::app()->ctx->feeds->isEnd()) : ?>' . $c . '<?php endif; ?>';
}
public static function FeedsCount($a)
public static function FeedsCount(ArrayObject $a): string
{
$none = 'no sources';
$one = 'one source';
$more = '%d sources';
if (isset($a['none'])) {
$none = addslashes($a['none']);
}
if (isset($a['one'])) {
$one = addslashes($a['one']);
}
if (isset($a['more'])) {
$more = addslashes($a['more']);
}
$none = isset($a['none']) && is_string($a['none']) ? addslashes($a['none']) : 'no sources';
$one = isset($a['one']) && is_string($a['one']) ? addslashes($a['one']) : 'one source';
$more = isset($a['more']) && is_string($a['more']) ? addslashes($a['more']) : '%d sources';
return
"<?php \$fcount = dcCore::app()->ctx->feeds->count(); \n" .
@ -264,25 +255,15 @@ class zcfsTemplate
'} unset($fcount); ?>';
}
public static function FeedsEntriesCount($a)
public static function FeedsEntriesCount(ArrayObject $a): string
{
$none = __('no entries');
$one = __('one entry');
$more = __('%d entries');
if (isset($a['none'])) {
$none = addslashes($a['none']);
}
if (isset($a['one'])) {
$one = addslashes($a['one']);
}
if (isset($a['more'])) {
$more = addslashes($a['more']);
}
$none = isset($a['none']) && is_string($a['none']) ? addslashes($a['none']) : 'no entries';
$one = isset($a['one']) && is_string($a['one']) ? addslashes($a['one']) : 'one entry';
$more = isset($a['more']) && is_string($a['more']) ? addslashes($a['more']) : '%d entries';
return
"<?php \$fcount = 0; \n" .
'$zc = new zoneclearFeedServer();' . "\n" .
'$zc = ' . ZoneclearFeedServer::class . '::instance();' . "\n" .
"\$allfeeds = \$zc->getFeeds(); \n" .
"if (!\$allfeeds->isEmpty()) { \n" .
' while ($allfeeds->fetch()) { ' .
@ -298,24 +279,14 @@ class zcfsTemplate
'} unset($allfeeds,$fcount); ?>';
}
public static function FeedEntriesCount($a)
public static function FeedEntriesCount(ArrayObject $a): string
{
$none = 'no entries';
$one = 'one entry';
$more = '%d entries';
if (isset($a['none'])) {
$none = addslashes($a['none']);
}
if (isset($a['one'])) {
$one = addslashes($a['one']);
}
if (isset($a['more'])) {
$more = addslashes($a['more']);
}
$none = isset($a['none']) && is_string($a['none']) ? addslashes($a['none']) : 'no entries';
$one = isset($a['one']) && is_string($a['one']) ? addslashes($a['one']) : 'one entry';
$more = isset($a['more']) && is_string($a['more']) ? addslashes($a['more']) : '%d entries';
return
"<?php \$zcfs = new zoneclearFeedServer(); \n" .
'<?php $zcfs = ' . ZoneclearFeedServer::class . "::instance(); \n" .
"\$fcount = \$zc->getPostsByFeed(array('feed_id'=>dcCore::app()->ctx->feeds->feed_id),true)->f(0); \n" .
"if (\$fcount == 0) {\n" .
" printf(__('" . $none . "'),\$fcount);\n" .
@ -326,21 +297,8 @@ class zcfsTemplate
'} unset($fcount); ?>';
}
protected static function getValue($a, $v)
protected static function getValue(ArrayObject $a, string $v): string
{
return '<?php echo ' . sprintf(dcCore::app()->tpl->getFilters($a), $v) . '; ?>';
}
protected static function getOperator($op)
{
switch (strtolower($op)) {
case 'or':
case '||':
return '||';
case 'and':
case '&&':
default:
return '&&';
}
}
}

View File

@ -10,102 +10,89 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
if (!defined('DC_CONTEXT_ADMIN')) {
return null;
}
declare(strict_types=1);
$this->addUserAction(
/* type */
'settings',
/* action */
'delete_all',
/* ns */
basename(__DIR__),
/* desc */
__('delete all settings')
);
$this->addUserAction(
/* type */
'tables',
/* action */
'delete',
/* ns */
initZoneclearFeedServer::TABLE_NAME,
/* desc */
__('delete table')
);
$this->addUserAction(
/* type */
'plugins',
/* action */
'delete',
/* ns */
basename(__DIR__),
/* desc */
__('delete plugin files')
);
$this->addUserAction(
/* type */
'versions',
/* action */
'delete',
/* ns */
basename(__DIR__),
/* desc */
__('delete the version number')
);
namespace Dotclear\Plugin\zoneclearFeedServer;
$this->addDirectAction(
/* type */
'settings',
/* action */
'delete_all',
/* ns */
basename(__DIR__),
/* desc */
sprintf(__('delete all %s settings'), basename(__DIR__))
);
$this->addDirectAction(
/* type */
'tables',
/* action */
'delete',
/* ns */
initZoneclearFeedServer::TABLE_NAME,
/* desc */
sprintf(__('delete %s table'), basename(__DIR__))
);
$this->addDirectAction(
/* type */
'plugins',
/* action */
'delete',
/* ns */
basename(__DIR__),
/* desc */
sprintf(__('delete %s plugin files'), basename(__DIR__))
);
$this->addDirectAction(
/* type */
'versions',
/* action */
'delete',
/* ns */
basename(__DIR__),
/* desc */
sprintf(__('delete %s version number'), basename(__DIR__))
);
$this->addDirectCallback(
/* function */
'zoneclearfeedServerUninstall',
/* desc */
'delete feeds relations'
);
use dcCore;
use dcNsProcess;
use Dotclear\Plugin\Uninstaller\Uninstaller;
function zoneclearfeedServerUninstall($id)
/**
* Plugin Uninstaller actions.
*/
class Uninstall extends dcNsProcess
{
if ($id != basename(__DIR__)) {
return null;
public static function init(): bool
{
static::$init = defined('DC_CONTEXT_ADMIN');
return static::$init;
}
public static function process(): bool
{
if (!static::$init || !dcCore::app()->plugins->moduleExists('Uninstaller')) {
return false;
}
if (!empty($_POST[My::id() . 'DeletePostsMeta'])) {
ZoneclearFeedServer::instance()::deletePostsMeta(null);
}
Uninstaller::instance()
->addUserAction(
'settings',
'delete_all',
My::id()
)
->addUserAction(
My::id() . 'DeletePostsMeta',
'delete_all',
My::id()
)
->addUserAction(
'tables',
'delete',
My::TABLE_NAME
)
->addUserAction(
'plugins',
'delete',
My::id()
)
->addUserAction(
'versions',
'delete',
My::id()
)
->addDirectAction(
'settings',
'delete_all',
My::id()
)
->addDirectAction(
My::id() . 'DeletePostsMeta',
'delete_all',
My::id()
)
->addDirectAction(
'tables',
'delete',
My::TABLE_NAME
)
->addDirectAction(
'plugins',
'delete',
My::id()
)
->addDirectAction(
'versions',
'delete',
My::id()
)
;
return false;
}
//...
}

104
src/UninstallCleaner.php Normal file
View File

@ -0,0 +1,104 @@
<?php
/**
* @brief zoneclearFeedServer, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugin
*
* @author Jean-Christian Denis, BG, Pierre Van Glabeke
*
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
declare(strict_types=1);
namespace Dotclear\Plugin\zoneclearFeedServer;
use dcCore;
use dcMeta;
use Dotclear\Database\Statement\{
DeleteStatement,
SelectStatement
};
use Dotclear\Plugin\Uninstaller\{
ActionDescriptor,
CleanerDescriptor,
CleanerParent,
CleanersStack,
ValueDescriptor
};
/**
* Plugin Uninstaller Cleaner object.
*
* This add special action for feed posts metadata.
*/
class UninstallCleaner extends CleanerParent
{
public static function init(CleanersStack $cleaners): void
{
$cleaners->set(new UninstallCleaner());
}
public function __construct()
{
parent::__construct(new CleanerDescriptor(
id: My::id() . 'DeletePostsMeta',
name: __('Feed Server'),
desc: __('Feed server posts feed metadata'),
actions: [
new ActionDescriptor(
id: 'delete_all',
select: __('delete selected posts feed metadata'),
query: __('delete "%s" posts feed metadata'),
success: __('"%s" posts feed metadata deleted'),
error: __('Failed to delete "%s" posts feed metadata')
),
]
));
}
public function distributed(): array
{
return [];
}
public function values(): array
{
$sql = new SelectStatement();
$sql->from(dcCore::app()->prefix . dcMeta::META_TABLE_NAME)
->columns([
$sql->as($sql->count('*'), 'counter'),
])
->where($sql->like('meta_type', My::META_PREFIX . '%'));
$rs = $sql->select();
if (is_null($rs) || $rs->isEmpty() || !is_numeric($rs->f('counter'))) {
return [];
}
$res = [];
while ($rs->fetch()) {
$res[] = new ValueDescriptor(
ns: My::id(),
count: (int) $rs->f('counter')
);
}
return $res;
}
public function execute(string $action, string $ns): bool
{
if ($action == 'delete_all') {
$sql = new DeleteStatement();
$sql->from(dcCore::app()->prefix . dcMeta::META_TABLE_NAME)
->where($sql->like('meta_type', My::META_PREFIX . '%'))
->delete();
return true;
}
return false;
}
}

View File

@ -10,27 +10,46 @@
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
if (!defined('DC_CONTEXT_ADMIN')) {
return null;
declare(strict_types=1);
namespace Dotclear\Plugin\zoneclearFeedServer;
use dcCore;
use dcNamespace;
use Dotclear\Database\Statement\{
SelectStatement,
UpdateStatement
};
use Exception;
/**
* Module versions upgrades.
*/
class Upgrade
{
public static function preUpgrade(): void
{
$current = dcCore::app()->getVersion(My::id());
if (!is_string($current) || empty($current)) {
return;
}
class zcfsUpgrade
{
public static function preUpgrade()
{
$current = dcCore::app()->getVersion(basename(dirname('../' . __DIR__)));
if ($current && version_compare($current, '2022.12.10', '<')) {
if (version_compare($current, '2022.12.10', '<')) {
self::preUpgrade20221210();
}
if (version_compare($current, '2023.05.05', '<')) {
self::preUpgrade20230505();
}
}
protected static function preUpgrade20221210()
protected static function preUpgrade20221210(): void
{
// Rename settings
$setting_ids = [
'zoneclearFeedServer_active' => 'active',
'zoneclearFeedServer_pub_active' => 'pub_active',
'zoneclearFeedServer_post_status_new' => 'psot_new_status',
'zoneclearFeedServer_post_status_new' => 'post_new_status',
'zoneclearFeedServer_bhv_pub_upd' => 'bhv_pub_upd',
'zoneclearFeedServer_update_limit' => 'update_limit',
'zoneclearFeedServer_keep_empty_feed' => 'keep_empty_feed',
@ -40,23 +59,36 @@ class zcfsUpgrade
'zoneclearFeedServer_post_title_redir' => 'post_title_redir',
];
foreach ($setting_ids as $old => $new) {
$cur = dcCore::app()->con->openCursor(dcCore::app()->prefix . dcNamespace::NS_TABLE_NAME);
$cur->setting_id = $new;
$cur->update("WHERE setting_id = '" . $old . "' and setting_ns = 'zoneclearFeedServer' ");
foreach ($setting_ids as $old => $new) {
$cur->clean();
$cur->setField('setting_id', $new);
$cur->setField('setting_ns', My::id());
$sql = new UpdateStatement();
$sql
->where('setting_id = ' . $sql->quote($old))
->and('setting_ns = ' . $sql->quote('zoneclearFeedServer'))
->update();
}
// use json rather than serialise for settings array
$sql = new SelectStatement();
$record = $sql
->from(dcCore::app()->prefix . dcNamespace::NS_TABLE_NAME)
->where('setting_ns = ' . $sql->quote(My::id()))
->select();
if (is_null($record)) {
return;
}
$setting_values = [
'post_full_tpl' => ['post', 'category', 'tag', 'archive'],
'post_title_redir' => ['feed'],
];
$record = dcCore::app()->con->select(
'SELECT * FROM ' . dcCore::app()->prefix . dcNamespace::NS_TABLE_NAME . ' ' .
"WHERE setting_ns = '" . dcCore::app()->con->escape(basename(dirname('../' . __DIR__))) . "' "
);
$cur = dcCore::app()->con->openCursor(dcCore::app()->prefix . dcNamespace::NS_TABLE_NAME);
while ($record->fetch()) {
foreach ($setting_values as $key => $default) {
try {
@ -65,13 +97,31 @@ class zcfsUpgrade
$value = $default;
}
$cur = dcCore::app()->con->openCursor(dcCore::app()->prefix . dcNamespace::NS_TABLE_NAME);
$cur->setting_value = json_encode(!is_array($value) ? $default : $value);
$cur->update(
"WHERE setting_id = '" . $key . "' and setting_ns = '" . dcCore::app()->con->escape($record->setting_ns) . "' " .
'AND blog_id ' . (null === $record->blog_id ? 'IS NULL ' : ("= '" . dcCore::app()->con->escape($record->blog_id) . "' "))
);
$cur->clean();
$cur->setField('setting_value', json_encode(!is_array($value) ? $default : $value));
$sql = new UpdateStatement();
$sql
->where('setting_id = ' . $sql->quote($key))
->and('setting_ns = ' . $sql->quote($record->f('setting_ns')))
->and('blog_id ' . (null === $record->f('blog_id') ? 'IS NULL ' : ('= ' . $sql->quote($record->f('blog_id')))))
->update();
}
}
}
protected static function preUpgrade20230505(): void
{
// change settings type of json string to array
$sql = new UpdateStatement();
$sql
->ref(dcCore::app()->prefix . dcNamespace::NS_TABLE_NAME)
->column('setting_type')
->value('array')
->where('setting_id ' . $sql->in([
'post_full_tpl',
'post_title_redir',
]))
->update();
}
}

View File

@ -10,41 +10,44 @@
* @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);
namespace Dotclear\Plugin\zoneclearFeedServer;
use dcCore;
use dcUrlHandlers;
use Dotclear\Helper\Html\Html;
use Exception;
/**
* @ingroup DC_PLUGIN_ZONECLEARFEEDSERVER
* @brief Mix your blog with a feeds planet - url handler methods.
* @since 2.6
* Frontend URL handler.
*
* This adds public page that list feeds.
* And serve an endpoint to update feeds through js.
*/
class zcfsUrlHandler extends dcUrlHandlers
class UrlHandler extends dcUrlHandlers
{
/**
* Feeds source page and update methods.
*
* @param array $args Page arguments
* @return mixed
* @param string $args The page arguments
*/
public static function zcFeedsPage($args)
public static function zoneclearFeedsPage(string $args): void
{
$s = dcCore::app()->blog->settings->__get(basename(dirname('../' . __DIR__)));
$z = ZoneclearFeedServer::instance();
$s = $z->settings;
# Not active
if (!$s->active) {
if (is_null(dcCore::app()->blog) || !$s->active) {
self::p404();
return null;
}
# Update feeds (from ajax or other post resquest)
if ($args == '/zcfsupd' && 3 == $s->bhv_pub_upd) {
$msg = '';
if (!empty($_POST['blogId']) && html::escapeJS(dcCore::app()->blog->id) == $_POST['blogId']) {
if (!empty($_POST['blogId']) && Html::escapeJS(dcCore::app()->blog->id) == $_POST['blogId']) {
try {
$zc = new zoneclearFeedServer();
if ($zc->checkFeedsUpdate()) {
if ($z->checkFeedsUpdate()) {
$msg = sprintf(
'<status>%s</status><message>%s</message>',
'ok',
@ -73,7 +76,7 @@ class zcfsUrlHandler extends dcUrlHandlers
# Server js
} elseif ($args == '/zcfsupd.js' && 3 == $s->bhv_pub_upd) {
dcCore::app()->tpl->setPath(dcCore::app()->tpl->getPath(), __DIR__ . '/default-templates');
dcCore::app()->tpl->setPath(dcCore::app()->tpl->getPath(), My::path() . '/default-templates');
self::serveDocument(
'zcfsupd.js',
'text/javascript',
@ -83,8 +86,12 @@ class zcfsUrlHandler extends dcUrlHandlers
# Server feeds description page
} elseif (in_array($args, ['', '/']) && $s->pub_active) {
$tplset = dcCore::app()->themes->moduleInfo(dcCore::app()->blog->settings->system->theme, 'tplset');
$path = __DIR__ . '/default-templates/';
$theme = dcCore::app()->blog->settings->get('system')->get('theme');
if (!is_string($theme)) {
self::p404();
}
$tplset = dcCore::app()->themes->moduleInfo($theme, 'tplset');
$path = My::path() . '/default-templates/';
if (!empty($tplset) && is_dir($path . $tplset)) {
dcCore::app()->tpl->setPath(dcCore::app()->tpl->getPath(), $path . $tplset);
} else {
@ -96,7 +103,5 @@ class zcfsUrlHandler extends dcUrlHandlers
else {
self::p404();
}
return null;
}
}

View File

@ -10,38 +10,33 @@
* @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',
['zoneclearFeedServerWidget', 'adminSource']
);
dcCore::app()->addBehavior(
'initWidgets',
['zoneclearFeedServerWidget', 'adminNumber']
);
namespace Dotclear\Plugin\zoneclearFeedServer;
use dcCore;
use Dotclear\Helper\Html\Html;
use Dotclear\Plugin\widgets\WidgetsStack;
use Dotclear\Plugin\widgets\WidgetsElement;
/**
* @ingroup DC_PLUGIN_ZONECLEARFEEDSERVER
* @brief Mix your blog with a feeds planet - widgets methods.
* @since 2.6
* Widgets.
*
* A widget to list feeds source.
* A widget to list feeds statistics.
*/
class zoneclearFeedServerWidget
class Widgets
{
/**
* Widget configuration for sources list.
*
* @param dcWidgets $w dcWidgets instance
* @param WidgetsStack $w WidgetsStack instance
*/
public static function adminSource($w)
public static function init(WidgetsStack $w): void
{
$w
->create(
'zcfssource',
__('Feeds server: sources'),
['zoneclearFeedServerWidget', 'publicSource'],
[self::class, 'publicSource'],
null,
__('List sources of feeds')
)
@ -85,20 +80,12 @@ class zoneclearFeedServerWidget
->addContentOnly()
->addClass()
->addOffline();
}
/**
* Widget configuration for feeds info.
*
* @param dcWidgets $w dcWidgets instance
*/
public static function adminNumber($w)
{
$w
->create(
'zcfsnumber',
__('Feeds server: numbers'),
['zoneclearFeedServerWidget', 'publicNumber'],
[self::class, 'publicNumber'],
null,
__('Show some numbers about feeds')
)
@ -144,59 +131,58 @@ class zoneclearFeedServerWidget
/**
* Widget for sources list.
*
* @param dcWidget $w dcWidget instance
* @param WidgetsElement $w Widgets Element instance
*/
public static function publicSource($w)
public static function publicSource(WidgetsElement $w): string
{
if ($w->offline) {
return null;
}
$z = ZoneclearFeedServer::instance();
$s = $z->settings;
if (!dcCore::app()->blog->settings->__get(basename(__DIR__))->active
if ($w->__get('offline')
|| !$s->active
|| !$w->checkHomeOnly(dcCore::app()->url->type)
) {
return null;
return '';
}
$p = [];
$p['order'] = ($w->sortby && in_array($w->sortby, ['feed_upd_last', 'lowername', 'feed_creadt'])) ?
$w->sortby . ' ' : 'feed_upd_last ';
$p['order'] .= $w->sort == 'desc' ? 'DESC' : 'ASC';
$p['limit'] = abs((int) $w->limit);
$p['order'] = ($w->__get('sortby') && in_array($w->__get('sortby'), ['feed_upd_last', 'lowername', 'feed_creadt'])) ?
$w->__get('sortby') . ' ' : 'feed_upd_last ';
$p['order'] .= $w->__get('sort') == 'desc' ? 'DESC' : 'ASC';
$p['limit'] = is_numeric($w->__get('limit')) ? abs((int) $w->__get('limit')) : 10;
$p['feed_status'] = 1;
$zc = new zoneclearFeedServer();
$rs = $zc->getFeeds($p);
$rs = $z->getFeeds($p);
if ($rs->isEmpty()) {
return null;
return '';
}
$lines = [];
$i = 1;
while ($rs->fetch()) {
$row = new FeedRow($rs);
$lines[] = sprintf(
'<li><a href="%s" title="%s">%s</a></li>',
$rs->feed_url,
$rs->feed_owner,
$rs->feed_name
$row->url,
$row->owner,
$row->name
);
$i++;
}
$pub = '';
if ($w->pagelink && dcCore::app()->blog->settings->__get(basename(__DIR__))->pub_active) {
if ($w->__get('pagelink') && $s->pub_active) {
$pub = sprintf(
'<p><strong><a href="%s">%s</a></strong></p>',
dcCore::app()->blog->url . dcCore::app()->url->getBase('zoneclearFeedsPage'),
html::escapeHTML($w->pagelink)
dcCore::app()->blog?->url . dcCore::app()->url->getBase('zoneclearFeedsPage'),
Html::escapeHTML(is_string($w->__get('pagelink')) ? $w->__get('pagelink') : '')
);
}
return $w->renderDiv(
$w->content_only,
'zoneclear-sources ' . $w->class,
(bool) $w->__get('content_only'),
'zoneclear-sources ' . $w->__get('class'),
'',
($w->title ? $w->renderTitle(html::escapeHTML($w->title)) : '') .
($w->__get('title') ? $w->renderTitle(Html::escapeHTML(is_string($w->__get('title')) ? $w->__get('title') : '')) : '') .
sprintf('<ul>%s</ul>', implode('', $lines)) . $pub
);
}
@ -204,38 +190,38 @@ class zoneclearFeedServerWidget
/**
* Widget for feeds info.
*
* @param dcWidget $w dcWidget instance
* @param WidgetsElement $w Widgets Element instance
*/
public static function publicNumber($w)
public static function publicNumber(WidgetsElement $w): string
{
if ($w->offline) {
return;
}
$z = ZoneclearFeedServer::instance();
$s = $z->settings;
if (!dcCore::app()->blog->settings->__get(basename(__DIR__))->active
if ($w->__get('offline')
|| !$s->active
|| !$w->checkHomeOnly(dcCore::app()->url->type)
) {
return null;
return '';
}
$zc = new zoneclearFeedServer();
$content = '';
# Feed
if ($w->feed_show) {
$title = ($w->feed_title ? sprintf(
if ($w->__get('feed_show')) {
$title = ($w->__get('feed_title') ? sprintf(
'<strong>%s</strong> ',
html::escapeHTML($w->feed_title)
Html::escapeHTML(is_string($w->__get('feed_title')) ? $w->__get('feed_title') : '')
) : '');
$count = $zc->getFeeds([], true)->f(0);
$count = $z->getFeeds([], true)->f(0);
$count = is_numeric($count) ? (int) $count : 0;
$text = $count ? sprintf(__('one source', '%d sources', $count), $count) : __('no sources');
if (dcCore::app()->blog->settings->__get(basename(__DIR__))->pub_active) {
if ($s->pub_active) {
$text = sprintf(
'<a href="%s">%s</a>',
dcCore::app()->blog->url . dcCore::app()->url->getBase('zoneclearFeedsPage'),
dcCore::app()->blog?->url . dcCore::app()->url->getBase('zoneclearFeedsPage'),
$text
);
}
@ -244,18 +230,21 @@ class zoneclearFeedServerWidget
}
# Entry
if ($w->entry_show) {
if ($w->__get('entry_show')) {
$count = 0;
$feeds = $zc->getFeeds();
$feeds = $z->getFeeds();
if (!$feeds->isEmpty()) {
while ($feeds->fetch()) {
$count += (int) $zc->getPostsByFeed(['feed_id' => $feeds->feed_id], true)->f(0);
$fid = is_numeric($feeds->f('feed_id')) ? (int) $feeds->f('feed_id') : 0;
$c = $z->getPostsByFeed(['feed_id' => $fid], true)->f(0);
$c = is_numeric($c) ? (int) $c : 0;
$count += $c;
}
}
$title = ($w->entry_title ? sprintf(
$title = ($w->__get('entry_title') ? sprintf(
'<strong>%s</strong> ',
html::escapeHTML($w->entry_title)
Html::escapeHTML(is_string($w->__get('entry_title')) ? $w->__get('entry_title') : '')
) : '');
$text = $count ? sprintf(__('one entry', '%d entries', $count), $count) : __('no entries');
@ -264,15 +253,15 @@ class zoneclearFeedServerWidget
}
if (!$content) {
return null;
return '';
}
# Display
return $w->renderDiv(
$w->content_only,
'zoneclear-number ' . $w->class,
(bool) $w->__get('content_only'),
'zoneclear-number ' . $w->__get('class'),
'',
($w->title ? $w->renderTitle(html::escapeHTML($w->title)) : '') .
($w->__get('title') ? $w->renderTitle(Html::escapeHTML(is_string($w->__get('title')) ? $w->__get('title') : '')) : '') .
sprintf('<ul>%s</ul>', $content)
);
}

976
src/ZoneclearFeedServer.php Normal file
View File

@ -0,0 +1,976 @@
<?php
/**
* @brief zoneclearFeedServer, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugin
*
* @author Jean-Christian Denis, BG, Pierre Van Glabeke
*
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
declare(strict_types=1);
namespace Dotclear\Plugin\zoneclearFeedServer;
use ArrayObject;
use dcAuth;
use dcBlog;
use dcCategories;
use dcCore;
use dcMeta;
use dcUtils;
use Dotclear\Database\{
Cursor,
MetaRecord
};
use Dotclear\Database\Statement\{
DeleteStatement,
JoinStatement,
SelectStatement
};
use Dotclear\Helper\Date;
use Dotclear\Helper\File\{
Files,
Path
};
use Dotclear\Helper\Html\Html;
use Dotclear\Helper\Network\Feed\{
Parser,
Reader
};
use Dotclear\Helper\Text;
use Exception;
/**
* Main module class.
*/
class ZoneclearFeedServer
{
/** @var int Net HTTP feed reader timeout */
public const NET_HTTP_TIMEOUT = 5;
/** @var string Net HTTP feed reader agent */
public const NET_HTTP_AGENT = 'zoneclearFeedServer - http://zoneclear.org';
/** @var int Net HTTP feed reader max redirect */
public const NET_HTTP_MAX_REDIRECT = 2;
/** @var ZoneclearFeedServer Self instance */
private static $instance;
/** @var Settings The settings instance */
public readonly Settings $settings;
/** @var null|resource $lock File lock for update */
private static $lock = null;
/** @var null|string $user Affiliate user ID */
private $user = null;
/**
* Constructor load settings.
*/
protected function __construct()
{
$this->settings = Settings::instance();
}
/**
* Get class instance.
*
* @return ZoneclearFeedServer Self instacne
*/
public static function instance(): ZoneclearFeedServer
{
if (!(self::$instance instanceof ZoneclearFeedServer)) {
self::$instance = new ZoneclearFeedServer();
}
return self::$instance;
}
/**
* Open database table cursor.
*
* @return Cursor The cursor
*/
public function openCursor(): Cursor
{
return dcCore::app()->con->openCursor(dcCore::app()->prefix . My::TABLE_NAME);
}
/**
* Update feed record.
*
* @param int $id The feed ID
* @param Cursor $cur The cursor instance
*/
public function updateFeed(int $id, Cursor $cur): void
{
dcCore::app()->con->writeLock(dcCore::app()->prefix . My::TABLE_NAME);
try {
if ($id < 1) {
throw new Exception(__('No such ID'));
}
$cur->setField('feed_upddt', date('Y-m-d H:i:s'));
$cur->update(sprintf(
"WHERE feed_id = %s AND blog_id = '%s' ",
$id,
dcCore::app()->con->escapeStr((string) dcCore::app()->blog?->id)
));
dcCore::app()->con->unlock();
$this->trigger();
} catch (Exception $e) {
dcCore::app()->con->unlock();
throw $e;
}
# --BEHAVIOR-- zoneclearFeedServerAfterUpdateFeed -- Cursor, int
dcCore::app()->callBehavior('zoneclearFeedServerAfterUpdateFeed', $cur, $id);
}
/**
* Add feed record.
*
* @param Cursor $cur The cursor
*
* @return int The new feed ID
*/
public function addFeed(Cursor $cur): int
{
dcCore::app()->con->writeLock(dcCore::app()->prefix . My::TABLE_NAME);
try {
$cur->setField('feed_id', $this->getNextId());
$cur->setField('blog_id', dcCore::app()->con->escapeStr((string) dcCore::app()->blog?->id));
$cur->setField('feed_creadt', date('Y-m-d H:i:s'));
$cur->setField('feed_upddt', date('Y-m-d H:i:s'));
//add getFeedCursor here
$cur->insert();
dcCore::app()->con->unlock();
$this->trigger();
} catch (Exception $e) {
dcCore::app()->con->unlock();
throw $e;
}
$id = is_numeric($cur->getField('feed_id')) ? (int) $cur->getField('feed_id') : 0;
# --BEHAVIOR-- zoneclearFeedServerAfterAddFeed -- Cursor, int
dcCore::app()->callBehavior('zoneclearFeedServerAfterAddFeed', $cur, $id);
return $id;
}
/**
* Quick enable / disable feed.
*
* @param int $id The feed ID
* @param bool $enable Enable feed
* @param int $time Force update time
*/
public function enableFeed(int $id, bool $enable = true, int $time = 0): void
{
try {
if ($id < 1) {
throw new Exception(__('No such ID'));
}
$cur = $this->openCursor();
dcCore::app()->con->writeLock(dcCore::app()->prefix . My::TABLE_NAME);
$cur->setField('feed_upddt', date('Y-m-d H:i:s'));
$cur->setField('feed_status', (int) $enable);
if (0 < $time) {
$cur->setField('feed_upd_last', $time);
}
$cur->update(sprintf(
"WHERE feed_id = %s AND blog_id = '%s' ",
$id,
dcCore::app()->con->escapeStr((string) dcCore::app()->blog?->id)
));
dcCore::app()->con->unlock();
$this->trigger();
} catch (Exception $e) {
dcCore::app()->con->unlock();
throw $e;
}
# --BEHAVIOR-- zoneclearFeedServerAfterEnableFeed -- int, bool, int
dcCore::app()->callBehavior('zoneclearFeedServerAfterEnableFeed', $id, $enable, $time);
}
#
/**
* Delete record (this not deletes post).
*
* @param int $id The feed ID
*/
public function deleteFeed(int $id): void
{
if ($id < 1) {
throw new Exception(__('No such ID'));
}
# --BEHAVIOR-- zoneclearFeedServerBeforeDeleteFeed -- int
dcCore::app()->callBehavior('zoneclearFeedServerBeforeDeleteFeed', $id);
$sql = new DeleteStatement();
$sql->from(dcCore::app()->prefix . My::TABLE_NAME)
->where('feed_id ' . $sql->in($id))
->and('blog_id = ' . $sql->quote((string) dcCore::app()->blog?->id))
->delete();
$this->trigger();
}
/**
* Delete all post(s) meta.
*
* This deletes all post(s) related meta,
* the post from the planet become an ordinary post.
*
* @param null|int $id The post ID (or null for all!)
*/
public static function deletePostsMeta(?int $id): void
{
$sql = new DeleteStatement();
$sql->from(dcCore::app()->prefix . dcMeta::META_TABLE_NAME)
->where('meta_type ' . $sql->in([
My::META_PREFIX . 'url',
My::META_PREFIX . 'author',
My::META_PREFIX . 'site',
My::META_PREFIX . 'sitename',
My::META_PREFIX . 'id',
]));
if (!is_null($post_id)) {
$sql->and('post_id = ' . $id);
}
$sql->delete();
}
/**
* Get related posts.
*
* @param array<string,int|string|array> $params The query params
* @param bool $count_only Return only result count
*
* @return MetaRecord The record instance
*/
public function getPostsByFeed(array $params = [], bool $count_only = false): MetaRecord
{
if (!isset($params['feed_id']) || !is_numeric($params['feed_id'])) {
return MetaRecord::newFromArray([]);
}
$sql = new SelectStatement();
$sql->join(
(new JoinStatement())
->left()
->from(dcCore::app()->prefix . dcMeta::META_TABLE_NAME . ' F')
->on('P.post_id = F.post_id')
->statement()
);
$params['sql'] = "AND P.blog_id = '" . dcCore::app()->con->escapeStr((string) dcCore::app()->blog?->id) . "' " .
"AND F.meta_type = '" . My::META_PREFIX . "id' " .
"AND F.meta_id = '" . dcCore::app()->con->escapeStr((string) $params['feed_id']) . "' ";
unset($params['feed_id']);
$rs = dcCore::app()->blog?->getPosts($params, $count_only, $sql);
return is_null($rs) ? MetaRecord::newFromArray([]) : $rs;
}
/**
* Get feed record.
*
* @param array<string,int|string|array> $params The query params
* @param bool $count_only Return only result count
*
* @return MetaRecord The record instance
*/
public function getFeeds(array $params = [], bool $count_only = false): MetaRecord
{
if ($count_only) {
$strReq = 'SELECT count(Z.feed_id) ';
} else {
$content_req = '';
if (!empty($params['columns']) && is_array($params['columns'])) {
$content_req .= implode(', ', $params['columns']) . ', ';
}
$strReq = 'SELECT Z.feed_id, Z.feed_creadt, Z.feed_upddt, Z.feed_type, ' .
'Z.blog_id, Z.cat_id, ' .
'Z.feed_upd_int, Z.feed_upd_last, Z.feed_status, ' .
$content_req .
'LOWER(Z.feed_name) as lowername, Z.feed_name, Z.feed_desc, ' .
'Z.feed_url, Z.feed_feed, Z.feed_get_tags, ' .
'Z.feed_tags, Z.feed_owner, Z.feed_tweeter, Z.feed_lang, ' .
'Z.feed_nb_out, Z.feed_nb_in, ' .
'C.cat_title, C.cat_url, C.cat_desc ';
}
$strReq .= 'FROM ' . dcCore::app()->prefix . My::TABLE_NAME . ' Z ' .
'LEFT OUTER JOIN ' . dcCore::app()->prefix . dcCategories::CATEGORY_TABLE_NAME . ' C ON Z.cat_id = C.cat_id ';
if (!empty($params['from']) && is_string($params['from'])) {
$strReq .= $params['from'] . ' ';
}
$strReq .= "WHERE Z.blog_id = '" . dcCore::app()->con->escapeStr((string) dcCore::app()->blog?->id) . "' ";
if (isset($params['feed_type']) && is_string($params['feed_type'])) {
$strReq .= "AND Z.feed_type = '" . dcCore::app()->con->escapeStr((string) $params['feed_type']) . "' ";
} else {
$strReq .= "AND Z.feed_type = 'feed' ";
}
if (!empty($params['feed_id'])) {
if (is_array($params['feed_id'])) {
array_walk($params['feed_id'], function (&$v, $k) { if ($v !== null) { $v = (int) $v; }});
} elseif (is_numeric($params['feed_id'])) {
$params['feed_id'] = [(int) $params['feed_id']];
}
$strReq .= 'AND Z.feed_id ' . dcCore::app()->con->in($params['feed_id']);
}
if (isset($params['feed_feed']) && is_string($params['feed_feed'])) {
$strReq .= "AND Z.feed_feed = '" . dcCore::app()->con->escapeStr((string) $params['feed_feed']) . "' ";
}
if (isset($params['feed_url']) && is_string($params['feed_url'])) {
$strReq .= "AND Z.feed_url = '" . dcCore::app()->con->escapeStr((string) $params['feed_url']) . "' ";
}
if (isset($params['feed_status'])) {
$strReq .= 'AND Z.feed_status = ' . ((int) $params['feed_status']) . ' ';
}
if (!empty($params['q']) && is_string($params['q'])) {
$q = dcCore::app()->con->escapeStr((string) str_replace('*', '%', strtolower($params['q'])));
$strReq .= "AND LOWER(Z.feed_name) LIKE '" . $q . "' ";
}
if (!empty($params['sql']) && is_string($params['sql'])) {
$strReq .= $params['sql'] . ' ';
}
if (!$count_only) {
if (!empty($params['order']) && is_string($params['order'])) {
$strReq .= 'ORDER BY ' . dcCore::app()->con->escapeStr((string) $params['order']) . ' ';
} else {
$strReq .= 'ORDER BY Z.feed_upddt DESC ';
}
}
if (!$count_only && isset($params['limit'])) {
if (is_numeric($params['limit'])) {
$params['limit'] = (int) $params['limit'];
}
if (is_int($params['limit']) || is_array($params['limit'])) {
$strReq .= dcCore::app()->con->limit($params['limit']);
}
}
return new MetaRecord(dcCore::app()->con->select($strReq));
}
/**
* Get next table id.
*
* @return int THe next ID
*/
private function getNextId(): int
{
$sql = new SelectStatement();
$rs = $sql
->column($sql->max('feed_id'))
->from(dcCore::app()->prefix . My::TABLE_NAME)
->select();
return (int) $rs?->f(0) + 1;
}
/**
* Lock a file to see if an update is ongoing.
*
* @return bool True if file is locked
*/
public function lockUpdate()
{
try {
# Need flock function
if (!function_exists('flock')) {
throw new Exception("Can't call php function named flock");
}
# Cache writable ?
if (!is_writable(DC_TPL_CACHE)) {
throw new Exception("Can't write in cache fodler");
}
# Set file path
$f_md5 = md5((string) dcCore::app()->blog?->id);
$cached_file = sprintf(
'%s/%s/%s/%s/%s.txt',
DC_TPL_CACHE,
'periodical',
substr($f_md5, 0, 2),
substr($f_md5, 2, 2),
$f_md5
);
# Real path
$cached_file = Path::real($cached_file, false);
if (false === $cached_file) {
throw new Exception("Can't get cache file path");
}
# Make dir
if (!is_dir(dirname($cached_file))) {
Files::makeDir(dirname($cached_file), true);
}
# Make file
if (!file_exists($cached_file)) {
!$fp = @fopen($cached_file, 'w');
if ($fp === false) {
throw new Exception("Can't create file");
}
fwrite($fp, '1', strlen('1'));
fclose($fp);
}
# Open file
if (!($fp = @fopen($cached_file, 'r+'))) {
throw new Exception("Can't open file");
}
# Lock file
if (!flock($fp, LOCK_EX)) {
throw new Exception("Can't lock file");
}
self::$lock = $fp;
return true;
} catch (Exception $e) {
throw $e;
}
}
/**
* Unlock file of update process.
*/
public function unlockUpdate(): void
{
if (!is_null(self::$lock)) {
@fclose(self::$lock);
self::$lock = null;
}
}
/**
* Check and add/update post related to record if needed.
*
* @param int $id The feed ID
* @param bool $throw Throw exception or end silently
*
* @return bool True on success
*/
public function checkFeedsUpdate(int $id = 0, bool $throw = false): bool
{
$s = $this->settings;
# Not configured
if (is_null(dcCore::app()->auth) || !$s->active || !$s->user) {
return false;
}
# Limit to one update at a time
try {
$this->lockUpdate();
} catch (Exception $e) {
if ($throw) {
throw $e;
}
return false;
}
$tz = dcCore::app()->blog?->settings->get('system')->get('blog_timezone');
Date::setTZ(is_string($tz) ? $tz : 'UTC');
$time = time();
# All feeds or only one (from admin)
$f = !$id ?
$this->getFeeds(['feed_status' => 1, 'order' => 'feed_upd_last ASC']) :
$this->getFeeds(['feed_id' => $id]);
# No feed
if ($f->isEmpty()) {
return false;
}
$enabled = false;
$updates = false;
$loop_mem = [];
$i = 0;
$cur_post = dcCore::app()->con->openCursor(dcCore::app()->prefix . dcBlog::POST_TABLE_NAME);
$cur_meta = dcCore::app()->con->openCursor(dcCore::app()->prefix . dcMeta::META_TABLE_NAME);
while ($f->fetch()) {
$row = new FeedRow($f);
# Check if feed need update
if ($id
|| $i < $s->update_limit && $row->status == 1 && ($time > $row->upd_last + $row->upd_int)
) {
if (!$enabled) {
# Set feeds user
$this->enableUser(true);
$enabled = true;
}
$i++;
$feed = self::readFeed($row->feed);
# Nothing to parse
if (!$feed) {
# Keep active empty feed or disable it ?
if (!$s->keep_empty_feed) {
$this->enableFeed($row->id, false);
} else {
# Set update time of this feed
$this->enableFeed($row->id, true, $time);
}
$i++;
# Not updated since last visit
} elseif (!$id
&& '' != $feed->pubdate
&& strtotime($feed->pubdate) < $row->upd_last
) {
# Set update time of this feed
$this->enableFeed($row->id, true, $time);
$i++;
} else {
# Set update time of this feed
$this->enableFeed($row->id, (bool) $row->status, $time);
dcCore::app()->con->begin();
foreach ($feed->items as $item) {
$item_TS = $item->TS ? $item->TS : $time;
// I found that mercurial atom feed did not repect standard
$item_link = @$item->link;
if (!$item_link) {
$item_link = @$item->guid;
}
# Unknow feed item link
if (!$item_link) {
continue;
}
$item_link = dcCore::app()->con->escapeStr((string) $item_link);
$is_new_published_entry = false;
# Not updated since last visit
if (!$id && $item_TS < $row->upd_last) {
continue;
}
# Fix loop twin
if (in_array($item_link, $loop_mem)) {
continue;
}
$loop_mem[] = $item_link;
# Check if entry exists
$sql = new SelectStatement();
$old_post = $sql
->columns([
'P.post_id',
'P.post_status',
])
->from($sql->as(dcCore::app()->prefix . dcBlog::POST_TABLE_NAME, 'P'))
->join(
(new JoinStatement())
->inner()
->from($sql->as(dcCore::app()->prefix . dcMeta::META_TABLE_NAME, 'M'))
->on('P.post_id = M.post_id')
->statement()
)
->where('blog_id = ' . $sql->quote((string) dcCore::app()->blog?->id))
->and("meta_type = '" . My::META_PREFIX . "url'")
->and('meta_id = ' . $sql->quote($item_link))
->select();
if (is_null($old_post)) {
$old_post = MetaRecord::newFromArray([]);
}
# Prepare entry Cursor
$cur_post->clean();
$cur_post->setField('post_dt', date('Y-m-d H:i:s', $item_TS));
if ($row->cat_id) {
$cur_post->setField('cat_id', $row->cat_id);
}
$post_content = $item->content ? $item->content : $item->description;
$cur_post->setField('post_format', 'xhtml');
$cur_post->setField('post_content', Html::absoluteURLs($post_content, $feed->link));
$cur_post->setField('post_title', $item->title ? $item->title : Text::cutString(Html::clean(is_string($cur_post->getField('post_content')) ? $cur_post->getField('post_content') : ''), 60));
$creator = $item->creator ? $item->creator : $row->owner;
try {
# Create entry
if ($old_post->isEmpty()) {
# Post
$cur_post->setField('user_id', dcCore::app()->auth->userID());
$cur_post->setField('post_format', 'xhtml');
$cur_post->setField('post_status', $s->post_status_new);
$cur_post->setField('post_open_comment', 0);
$cur_post->setField('post_open_tb', 0);
$post_id = dcCore::app()->auth->sudo(
[dcCore::app()->blog, 'addPost'],
$cur_post
);
# Auto tweet new post
if (!empty($cur_post->getField('post_status'))) {
$is_new_published_entry = true;
}
# Update entry
} else {
$post_id = is_numeric($old_post->f('post_id')) ? (int) $old_post->f('post_id') : 0;
dcCore::app()->auth->sudo(
[dcCore::app()->blog, 'updPost'],
$post_id,
$cur_post
);
# Quick delete old meta
$sql = new DeleteStatement();
$sql->from(dcCore::app()->prefix . dcMeta::META_TABLE_NAME)
->where('post_id = ' . $post_id)
->and($sql->like('meta_type', My::META_PREFIX . '%'))
->delete();
# Delete old tags
dcCore::app()->auth->sudo(
[dcCore::app()->meta, 'delPostMeta'],
$post_id,
'tag'
);
}
# Quick add new meta
$cur_meta->clean();
$cur_meta->setField('post_id', $post_id);
$cur_meta->setField('meta_type', My::META_PREFIX . 'url');
$cur_meta->setField('meta_id', $item_link);
$cur_meta->insert();
$cur_meta->clean();
$cur_meta->setField('post_id', $post_id);
$cur_meta->setField('meta_type', My::META_PREFIX . 'author');
$cur_meta->setField('meta_id', $creator);
$cur_meta->insert();
$cur_meta->clean();
$cur_meta->setField('post_id', $post_id);
$cur_meta->setField('meta_type', My::META_PREFIX . 'site');
$cur_meta->setField('meta_id', $row->url);
$cur_meta->insert();
$cur_meta->clean();
$cur_meta->setField('post_id', $post_id);
$cur_meta->setField('meta_type', My::META_PREFIX . 'sitename');
$cur_meta->setField('meta_id', $row->name);
$cur_meta->insert();
$cur_meta->clean();
$cur_meta->setField('post_id', $post_id);
$cur_meta->setField('meta_type', My::META_PREFIX . 'id');
$cur_meta->setField('meta_id', $row->id);
$cur_meta->insert();
# Add new tags
$tags = dcCore::app()->meta->splitMetaValues($row->tags);
if ($row->get_tags) {
# Some feed subjects contains more than one tag
foreach ($item->subject as $subjects) {
$tmp = dcCore::app()->meta->splitMetaValues($subjects);
$tags = array_merge($tags, $tmp);
}
$tags = array_unique($tags);
}
$formated_tags = [];
foreach ($tags as $tag) {
# Change tags case
switch ((int) $s->tag_case) {
case 3: $tag = strtoupper($tag);
break;
case 2: $tag = strtolower($tag);
break;
case 1: $tag = ucfirst(strtolower($tag));
break;
default: /* do nothing */ break;
}
if (!in_array($tag, $formated_tags)) {
$formated_tags[] = $tag;
dcCore::app()->auth->sudo(
[dcCore::app()->meta, 'delPostMeta'],
$post_id,
'tag',
dcMeta::sanitizeMetaID($tag)
);
dcCore::app()->auth->sudo(
[dcCore::app()->meta, 'setPostMeta'],
$post_id,
'tag',
dcMeta::sanitizeMetaID($tag)
);
}
}
} catch (Exception $e) {
dcCore::app()->con->rollback();
$this->enableUser(false);
$this->unlockUpdate();
throw $e;
}
# --BEHAVIOR-- zoneclearFeedServerAfterCheckFeedUpdate -- FeedRow
dcCore::app()->callBehavior('zoneclearFeedServerAfterCheckFeedUpdate', $row);
}
dcCore::app()->con->commit();
}
}
}
if ($enabled) {
$this->enableUser(false);
}
$this->unlockUpdate();
return true;
}
/**
* Set permission to update post table.
*
* @param boolean $enable Enable or disable perm
*/
public function enableUser(bool $enable = false): void
{
if (is_null(dcCore::app()->auth)) {
return;
}
# Enable
if ($enable) {
// backup current user
if (!is_null(dcCore::app()->auth->userID()) && !is_string(dcCore::app()->auth->userID())) {
throw new Exception('Unable to backup user');
}
$this->user = dcCore::app()->auth->userID();
// set zcfs posts user
if (!dcCore::app()->auth->checkUser($this->settings->user)) {
throw new Exception('Unable to set user');
}
# Disable
} else {
dcCore::app()->auth = null;
dcCore::app()->auth = new dcAuth();
// restore current user
dcCore::app()->auth->checkUser($this->user ?? '');
}
}
/**
* Read and parse external feeds.
*
* @param string $url The feed URL
*
* @return Parser|false The parsed feed
*/
public static function readFeed(string $url): false|Parser
{
try {
$feed_reader = new Reader();
$feed_reader->setCacheDir(DC_TPL_CACHE);
$feed_reader->setTimeout(self::NET_HTTP_TIMEOUT);
$feed_reader->setMaxRedirects(self::NET_HTTP_MAX_REDIRECT);
$feed_reader->setUserAgent(self::NET_HTTP_AGENT);
return $feed_reader->parse($url);
} catch (Exception $e) {
return false;
}
}
/**
* Trigger blog.
*/
private function trigger(): void
{
dcCore::app()->blog?->triggerBlog();
}
/**
* Check if an URL is well formed.
*
* @param string $url The URL
*
* @return bool True if URL is allowed
*/
public static function validateURL(string $url): bool
{
return false !== strpos($url, 'http://')
|| false !== strpos($url, 'https://');
}
/**
* Get full URL.
*
* Know bugs: anchor is not well parsed.
*
* @param string $root The root URL
* @param string $url A URL
*
* @return string The parse URL
*/
public static function absoluteURL(string $root, string $url): string
{
$host = preg_replace(
'|^([a-z]{3,}://)(.*?)/(.*)$|',
'$1$2',
$root
);
$parse = parse_url($url);
if (empty($parse['scheme'])) {
if (strpos($url, '/') === 0) {
$url = $host . $url;
} elseif (strpos($url, '#') === 0) {
$url = $root . $url;
} elseif (preg_match('|/$|', $root)) {
$url = $root . $url;
} else {
$url = dirname($root) . '/' . $url;
}
}
return $url;
}
/**
* Get list of (super)admins of current blog.
*
* @return array<string,string> List of UserCNs/UserIds
*/
public function getAllBlogAdmins(): array
{
$admins = [];
# Get super admins
$sql = new SelectStatement();
$rs = $sql
->from(dcCore::app()->prefix . dcAuth::USER_TABLE_NAME)
->columns([
'user_id',
'user_super',
'user_name',
'user_firstname',
'user_displayname',
])
->where('user_super = 1')
->and('user_status = 1')
->select();
if (!is_null($rs) && !$rs->isEmpty()) {
while ($rs->fetch()) {
$user_cn = dcUtils::getUserCN(
$rs->f('user_id'),
$rs->f('user_name'),
$rs->f('user_firstname'),
$rs->f('user_displayname')
);
$admins[$user_cn . ' (super admin)'] = $rs->f('user_id');
}
}
# Get admins
$sql = new SelectStatement();
$rs = $sql
->columns([
'U.user_id',
'U.user_super',
'U.user_name',
'U.user_firstname',
'U.user_displayname',
])
->from($sql->as(dcCore::app()->prefix . dcAuth::USER_TABLE_NAME, 'U'))
->join(
(new JoinStatement())
->left()
->from($sql->as(dcCore::app()->prefix . dcAuth::PERMISSIONS_TABLE_NAME, 'P'))
->on('U.user_id = P.user_id')
->statement()
)
->where('U.user_status = 1')
->and('P.blog_id = ' . $sql->quote((string) dcCore::app()->blog?->id))
->and($sql->like('P.permissions', '%|admin|%'))
->select();
if (!is_null($rs) && !$rs->isEmpty()) {
while ($rs->fetch()) {
$user_cn = dcUtils::getUserCN(
$rs->f('user_id'),
$rs->f('user_name'),
$rs->f('user_firstname'),
$rs->f('user_displayname')
);
$admins[$user_cn . ' (admin)'] = $rs->f('user_id');
}
}
return $admins;
}
/**
* Get list of urls where entries could be hacked.
*
* @return array<string,string> List of names/types of URLs
*/
public static function getPublicUrlTypes(): array
{
$types = new ArrayObject([
__('Home page') => 'default',
__('Entries pages') => 'post',
__('Tags pages') => 'tag',
__('Archives pages') => 'archive',
__('Category pages') => 'category',
__('Entries feed') => 'feed',
]);
# --BEHAVIOR-- zoneclearFeedServerPublicUrlTypes -- ArrayObject
dcCore::app()->callBehavior('zoneclearFeedServerPublicUrlTypes', $types);
return $types->getArrayCopy();
}
}

View File

@ -1,925 +0,0 @@
<?php
/**
* @brief zoneclearFeedServer, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugin
*
* @author Jean-Christian Denis, BG, Pierre Van Glabeke
*
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
if (!defined('DC_RC_PATH')) {
return null;
}
/**
* @ingroup DC_PLUGIN_ZONECLEARFEEDSERVER
* @brief Feeds server - main methods
* @since 2.6
*/
class zoneclearFeedServer
{
public static $nethttp_timeout = 5;
public static $nethttp_agent = 'zoneclearFeedServer - http://zoneclear.org';
public static $nethttp_maxredirect = 2;
public $con;
private $blog;
private $table;
private $lock = null;
private $user = null;
/**
* Constructor.
*/
public function __construct()
{
$this->con = dcCore::app()->con;
$this->blog = dcCore::app()->con->escape(dcCore::app()->blog->id);
$this->table = dcCore::app()->prefix . initZoneclearFeedServer::TABLE_NAME;
}
/**
* Short openCursor.
*
* @return cursor cursor instance
*/
public function openCursor()
{
return $this->con->openCursor($this->table);
}
/**
* Update feed record.
*
* @param integer $id Feed id
* @param cursor $cur cursor instance
*/
public function updFeed($id, cursor $cur)
{
$this->con->writeLock($this->table);
try {
$id = (int) $id;
if ($id < 1) {
throw new Exception(__('No such ID'));
}
$cur->feed_upddt = date('Y-m-d H:i:s');
$cur->update(sprintf(
"WHERE feed_id = %s AND blog_id = '%s' ",
$id,
$this->blog
));
$this->con->unlock();
$this->trigger();
} catch (Exception $e) {
$this->con->unlock();
throw $e;
}
# --BEHAVIOR-- zoneclearFeedServerAfterUpdFeed
dcCore::app()->callBehavior('zoneclearFeedServerAfterUpdFeed', $cur, $id);
}
/**
* Add feed record.
*
* @param cursor $cur cursor instance
*/
public function addFeed(cursor $cur)
{
$this->con->writeLock($this->table);
try {
$cur->feed_id = $this->getNextId();
$cur->blog_id = $this->blog;
$cur->feed_creadt = date('Y-m-d H:i:s');
$cur->feed_upddt = date('Y-m-d H:i:s');
//add getFeedCursor here
$cur->insert();
$this->con->unlock();
$this->trigger();
} catch (Exception $e) {
$this->con->unlock();
throw $e;
}
# --BEHAVIOR-- zoneclearFeedServerAfterAddFeed
dcCore::app()->callBehavior('zoneclearFeedServerAfterAddFeed', $cur);
return $cur->feed_id;
}
/**
* Quick enable / disable feed.
*
* @param integer $id Feed Id
* @param boolean $enable Enable or disable feed
* @param integer $time Force update time
*/
public function enableFeed($id, $enable = true, $time = null)
{
try {
$id = (int) $id;
if ($id < 1) {
throw new Exception(__('No such ID'));
}
$cur = $this->openCursor();
$this->con->writeLock($this->table);
$cur->feed_upddt = date('Y-m-d H:i:s');
$cur->feed_status = (int) $enable;
if (null !== $time) {
$cur->feed_upd_last = (int) $time;
}
$cur->update(sprintf(
"WHERE feed_id = %s AND blog_id = '%s' ",
$id,
$this->blog
));
$this->con->unlock();
$this->trigger();
} catch (Exception $e) {
$this->con->unlock();
throw $e;
}
# --BEHAVIOR-- zoneclearFeedServerAfterEnableFeed
dcCore::app()->callBehavior('zoneclearFeedServerAfterEnableFeed', $id, $enable, $time);
}
#
/**
* Delete record (this not deletes post).
*
* @param integer $id Feed Id
*/
public function delFeed($id)
{
$id = (int) $id;
if ($id < 1) {
throw new Exception(__('No such ID'));
}
# --BEHAVIOR-- zoneclearFeedServerBeforeDelFeed
dcCore::app()->callBehavior('zoneclearFeedServerBeforeDelFeed', $id);
$this->con->execute(sprintf(
"DELETE FROM %s WHERE feed_id = %s AND blog_id = '%s' ",
$this->table,
$id,
$this->blog
));
$this->trigger();
}
/**
* Get related posts.
*
* @param array $params Query params
* @param boolean $count_only Return only result count
* @return null|dcRecord record instance
*/
public function getPostsByFeed($params = [], $count_only = false)
{
if (!isset($params['feed_id'])) {
return null;
}
$sql = new dcSelectStatement();
$sql->join(
(new dcJoinStatement())
->type('LEFT')
->from(dcCore::app()->prefix . dcMeta::META_TABLE_NAME . ' F')
->on('P.post_id = F.post_id')
->statement()
);
$params['sql'] = "AND P.blog_id = '" . $this->blog . "' " .
"AND F.meta_type = 'zoneclearfeed_id' " .
"AND F.meta_id = '" . $this->con->escape($params['feed_id']) . "' ";
unset($params['feed_id']);
return dcCore::app()->blog->getPosts($params, $count_only, $sql);
}
/**
* Get feed record.
*
* @param array $params Query params
* @param boolean $count_only Return only result count
* @return dcRecord record instance
*/
public function getFeeds($params = [], $count_only = false)
{
if ($count_only) {
$strReq = 'SELECT count(Z.feed_id) ';
} else {
$content_req = '';
if (!empty($params['columns']) && is_array($params['columns'])) {
$content_req .= implode(', ', $params['columns']) . ', ';
}
$strReq = 'SELECT Z.feed_id, Z.feed_creadt, Z.feed_upddt, Z.feed_type, ' .
'Z.blog_id, Z.cat_id, ' .
'Z.feed_upd_int, Z.feed_upd_last, Z.feed_status, ' .
$content_req .
'LOWER(Z.feed_name) as lowername, Z.feed_name, Z.feed_desc, ' .
'Z.feed_url, Z.feed_feed, Z.feed_get_tags, ' .
'Z.feed_tags, Z.feed_owner, Z.feed_tweeter, Z.feed_lang, ' .
'Z.feed_nb_out, Z.feed_nb_in, ' .
'C.cat_title, C.cat_url, C.cat_desc ';
}
$strReq .= 'FROM ' . $this->table . ' Z ' .
'LEFT OUTER JOIN ' . dcCore::app()->prefix . dcCategories::CATEGORY_TABLE_NAME . ' C ON Z.cat_id = C.cat_id ';
if (!empty($params['from'])) {
$strReq .= $params['from'] . ' ';
}
$strReq .= "WHERE Z.blog_id = '" . $this->blog . "' ";
if (isset($params['feed_type'])) {
$strReq .= "AND Z.feed_type = '" . $this->con->escape($params['type']) . "' ";
} else {
$strReq .= "AND Z.feed_type = 'feed' ";
}
if (!empty($params['feed_id'])) {
if (is_array($params['feed_id'])) {
array_walk($params['feed_id'], function (&$v, $k) { if ($v !== null) { $v = (int) $v; }});
} else {
$params['feed_id'] = [(int) $params['feed_id']];
}
$strReq .= 'AND Z.feed_id ' . $this->con->in($params['feed_id']);
}
if (isset($params['feed_feed'])) {
$strReq .= "AND Z.feed_feed = '" . $this->con->escape($params['feed_feed']) . "' ";
}
if (isset($params['feed_url'])) {
$strReq .= "AND Z.feed_url = '" . $this->con->escape($params['feed_url']) . "' ";
}
if (isset($params['feed_status'])) {
$strReq .= 'AND Z.feed_status = ' . ((int) $params['feed_status']) . ' ';
}
if (!empty($params['q'])) {
$q = $this->con->escape(str_replace('*', '%', strtolower($params['q'])));
$strReq .= "AND LOWER(Z.feed_name) LIKE '" . $q . "' ";
}
if (!empty($params['sql'])) {
$strReq .= $params['sql'] . ' ';
}
if (!$count_only) {
if (!empty($params['order'])) {
$strReq .= 'ORDER BY ' . $this->con->escape($params['order']) . ' ';
} else {
$strReq .= 'ORDER BY Z.feed_upddt DESC ';
}
}
if (!$count_only && !empty($params['limit'])) {
$strReq .= $this->con->limit($params['limit']);
}
$rs = $this->con->select($strReq);
return $rs;
}
/**
* Get next table id.
*
* @return integer Next id
*/
private function getNextId()
{
return $this->con->select(
'SELECT MAX(feed_id) FROM ' . $this->table
)->f(0) + 1;
}
/**
* Lock a file to see if an update is ongoing.
*
* @return boolean True if file is locked
*/
public function lockUpdate()
{
try {
# Need flock function
if (!function_exists('flock')) {
throw new Exception("Can't call php function named flock");
}
# Cache writable ?
if (!is_writable(DC_TPL_CACHE)) {
throw new Exception("Can't write in cache fodler");
}
# Set file path
$f_md5 = md5($this->blog);
$cached_file = sprintf(
'%s/%s/%s/%s/%s.txt',
DC_TPL_CACHE,
'periodical',
substr($f_md5, 0, 2),
substr($f_md5, 2, 2),
$f_md5
);
# Real path
$cached_file = path::real($cached_file, false);
# Make dir
if (!is_dir(dirname($cached_file))) {
files::makeDir(dirname($cached_file), true);
}
# Make file
if (!file_exists($cached_file)) {
!$fp = @fopen($cached_file, 'w');
if ($fp === false) {
throw new Exception("Can't create file");
}
fwrite($fp, '1', strlen('1'));
fclose($fp);
}
# Open file
if (!($fp = @fopen($cached_file, 'r+'))) {
throw new Exception("Can't open file");
}
# Lock file
if (!flock($fp, LOCK_EX)) {
throw new Exception("Can't lock file");
}
$this->lock = $fp;
return true;
} catch (Exception $e) {
throw $e;
}
return false;
}
/**
* Unlock file of update process.
*/
public function unlockUpdate()
{
@fclose($this->lock);
$this->lock = null;
}
/**
* Check and add/update post related to record if needed.
*
* @param integer $id Feed Id
* @param boolean $throw Throw exception or end silently
* @return boolean True if process succeed
*/
public function checkFeedsUpdate($id = null, $throw = false)
{
$s = dcCore::app()->blog->settings->__get(basename(dirname('../' . __DIR__)));
# Not configured
if (!$s->active || !$s->user) {
return false;
}
# Limit to one update at a time
try {
$this->lockUpdate();
} catch (Exception $e) {
if ($throw) {
throw $e;
}
return false;
}
dt::setTZ(dcCore::app()->blog->settings->system->blog_timezone);
$time = time();
# All feeds or only one (from admin)
$f = !$id ?
$this->getFeeds(['feed_status' => 1, 'order' => 'feed_upd_last ASC']) :
$this->getFeeds(['feed_id' => $id]);
# No feed
if ($f->isEmpty()) {
return false;
}
$enabled = false;
$updates = false;
$loop_mem = [];
$limit = abs((int) $s->update_limit);
if ($limit < 1) {
$limit = 10;
}
$i = 0;
$cur_post = $this->con->openCursor(dcCore::app()->prefix . dcBlog::POST_TABLE_NAME);
$cur_meta = $this->con->openCursor(dcCore::app()->prefix . dcMeta::META_TABLE_NAME);
while ($f->fetch()) {
# Check if feed need update
if ($id
|| $i < $limit && $f->feed_status == 1 && ($time > $f->feed_upd_last + $f->feed_upd_int)
) {
if (!$enabled) {
# Set feeds user
$this->enableUser(true);
$enabled = true;
}
$i++;
$feed = self::readFeed($f->feed_feed);
# Nothing to parse
if (!$feed) {
# Keep active empty feed or disable it ?
if (!$s->keep_empty_feed) {
$this->enableFeed($f->feed_id, false);
} else {
# Set update time of this feed
$this->enableFeed($f->feed_id, true, $time);
}
$i++;
# Not updated since last visit
} elseif (!$id
&& '' != $feed->pubdate
&& strtotime($feed->pubdate) < $f->feed_upd_last
) {
# Set update time of this feed
$this->enableFeed($f->feed_id, true, $time);
$i++;
} else {
# Set update time of this feed
$this->enableFeed($f->feed_id, $f->feed_status, $time);
$this->con->begin();
foreach ($feed->items as $item) {
$item_TS = $item->TS ? $item->TS : $time;
// I found that mercurial atom feed did not repect standard
$item_link = @$item->link;
if (!$item_link) {
$item_link = @$item->guid;
}
# Unknow feed item link
if (!$item_link) {
continue;
}
$item_link = $this->con->escape($item_link);
$is_new_published_entry = false;
# Not updated since last visit
if (!$id && $item_TS < $f->feed_upd_last) {
continue;
}
# Fix loop twin
if (in_array($item_link, $loop_mem)) {
continue;
}
$loop_mem[] = $item_link;
# Check if entry exists
$old_post = $this->con->select(
'SELECT P.post_id, P.post_status ' .
'FROM ' . dcCore::app()->prefix . dcBlog::POST_TABLE_NAME . ' P ' .
'INNER JOIN ' . dcCore::app()->prefix . dcMeta::META_TABLE_NAME . ' M ' .
'ON P.post_id = M.post_id ' .
"WHERE blog_id='" . $this->blog . "' " .
"AND meta_type = 'zoneclearfeed_url' " .
"AND meta_id = '" . $item_link . "' "
);
# Prepare entry cursor
$cur_post->clean();
$cur_post->post_dt = date('Y-m-d H:i:s', $item_TS);
if ($f->cat_id) {
$cur_post->cat_id = $f->cat_id;
}
$post_content = $item->content ? $item->content : $item->description;
$cur_post->post_format = 'xhtml';
$cur_post->post_content = html::absoluteURLs($post_content, $feed->link);
$cur_post->post_title = $item->title ? $item->title : text::cutString(html::clean($cur_post->post_content), 60);
$creator = $item->creator ? $item->creator : $f->feed_owner;
try {
# Create entry
if ($old_post->isEmpty()) {
# Post
$cur_post->user_id = dcCore::app()->auth->userID();
$cur_post->post_format = 'xhtml';
$cur_post->post_status = (int) $s->post_status_new;
$cur_post->post_open_comment = 0;
$cur_post->post_open_tb = 0;
# --BEHAVIOR-- zoneclearFeedServerBeforePostCreate
dcCore::app()->callBehavior(
'zoneclearFeedServerBeforePostCreate',
$cur_post
);
$post_id = dcCore::app()->auth->sudo(
[dcCore::app()->blog, 'addPost'],
$cur_post
);
# --BEHAVIOR-- zoneclearFeedServerAfterPostCreate
dcCore::app()->callBehavior(
'zoneclearFeedServerAfterPostCreate',
$cur_post,
$post_id
);
# Auto tweet new post
if ($cur_post->post_status == 1) {
$is_new_published_entry = true;
}
# Update entry
} else {
$post_id = $old_post->post_id;
# --BEHAVIOR-- zoneclearFeedServerBeforePostUpdate
dcCore::app()->callBehavior(
'zoneclearFeedServerBeforePostUpdate',
$cur_post,
$post_id
);
dcCore::app()->auth->sudo(
[dcCore::app()->blog, 'updPost'],
$post_id,
$cur_post
);
# Quick delete old meta
$this->con->execute(
'DELETE FROM ' . dcCore::app()->prefix . dcMeta::META_TABLE_NAME . ' ' .
'WHERE post_id = ' . $post_id . ' ' .
"AND meta_type LIKE 'zoneclearfeed_%' "
);
# Delete old tags
dcCore::app()->auth->sudo(
[dcCore::app()->meta, 'delPostMeta'],
$post_id,
'tag'
);
# --BEHAVIOR-- zoneclearFeedServerAfterPostUpdate
dcCore::app()->callBehavior(
'zoneclearFeedServerAfterPostUpdate',
$cur_post,
$post_id
);
}
# Quick add new meta
$meta = new ArrayObject();
$meta->tweeter = $f->feed_tweeter;
$cur_meta->clean();
$cur_meta->post_id = $post_id;
$cur_meta->meta_type = 'zoneclearfeed_url';
$cur_meta->meta_id = $meta->url = $item_link;
$cur_meta->insert();
$cur_meta->clean();
$cur_meta->post_id = $post_id;
$cur_meta->meta_type = 'zoneclearfeed_author';
$cur_meta->meta_id = $meta->author = $creator;
$cur_meta->insert();
$cur_meta->clean();
$cur_meta->post_id = $post_id;
$cur_meta->meta_type = 'zoneclearfeed_site';
$cur_meta->meta_id = $meta->site = $f->feed_url;
$cur_meta->insert();
$cur_meta->clean();
$cur_meta->post_id = $post_id;
$cur_meta->meta_type = 'zoneclearfeed_sitename';
$cur_meta->meta_id = $meta->sitename = $f->feed_name;
$cur_meta->insert();
$cur_meta->clean();
$cur_meta->post_id = $post_id;
$cur_meta->meta_type = 'zoneclearfeed_id';
$cur_meta->meta_id = $meta->id = $f->feed_id;
$cur_meta->insert();
# Add new tags
$tags = dcCore::app()->meta->splitMetaValues($f->feed_tags);
if ($f->feed_get_tags) {
# Some feed subjects contains more than one tag
foreach ($item->subject as $subjects) {
$tmp = dcCore::app()->meta->splitMetaValues($subjects);
$tags = array_merge($tags, $tmp);
}
$tags = array_unique($tags);
}
$formated_tags = [];
foreach ($tags as $tag) {
# Change tags case
switch ((int) $s->tag_case) {
case 3: $tag = strtoupper($tag);
break;
case 2: $tag = strtolower($tag);
break;
case 1: $tag = ucfirst(strtolower($tag));
break;
default: /* do nothing */ break;
}
if (!in_array($tag, $formated_tags)) {
$formated_tags[] = $tag;
dcCore::app()->auth->sudo(
[dcCore::app()->meta, 'delPostMeta'],
$post_id,
'tag',
dcMeta::sanitizeMetaID($tag)
);
dcCore::app()->auth->sudo(
[dcCore::app()->meta, 'setPostMeta'],
$post_id,
'tag',
dcMeta::sanitizeMetaID($tag)
);
}
}
$meta->tags = $formated_tags;
# --BEHAVIOR-- zoneclearFeedServerAfterFeedUpdate
dcCore::app()->callBehavior(
'zoneclearFeedServerAfterFeedUpdate',
$is_new_published_entry,
$cur_post,
$meta
);
} catch (Exception $e) {
$this->con->rollback();
$this->enableUser(false);
$this->unlockUpdate();
throw $e;
}
$updates = true;
}
$this->con->commit();
}
}
}
if ($enabled) {
$this->enableUser(false);
}
$this->unlockUpdate();
return true;
}
/**
* Set permission to update post table.
*
* @param boolean $enable Enable or disable perm
*/
public function enableUser($enable = false)
{
# Enable
if ($enable) {
// backup current user
$this->user = dcCore::app()->auth->userID();
// set zcfs posts user
if (!dcCore::app()->auth->checkUser((string) dcCore::app()->blog->settings->__get(basename(dirname('../' . __DIR__)))->user)) {
throw new Exception('Unable to set user');
}
# Disable
} else {
dcCore::app()->auth = null;
dcCore::app()->auth = new dcAuth();
// restore current user
dcCore::app()->auth->checkUser($this->user ?? '');
}
}
/**
* Read and parse external feeds.
*
* @param string $f Feed URL
* @return feedParser|false Parsed feed
*/
public static function readFeed($f)
{
try {
$feed_reader = new feedReader();
$feed_reader->setCacheDir(DC_TPL_CACHE);
$feed_reader->setTimeout(self::$nethttp_timeout);
$feed_reader->setMaxRedirects(self::$nethttp_maxredirect);
$feed_reader->setUserAgent(self::$nethttp_agent);
return $feed_reader->parse($f);
} catch (Exception $e) {
return false;
}
}
/**
* Trigger.
*/
private function trigger()
{
dcCore::app()->blog->triggerBlog();
}
/**
* Check if an URL is well formed
*
* @param string $url URL
* @return Boolean True if URL is allowed
*/
public static function validateURL($url)
{
return false !== strpos($url, 'http://')
|| false !== strpos($url, 'https://');
}
/**
* Get full URL.
*
* Know bugs: anchor is not well parsed.
*
* @param string $root Root URL
* @param string $url An URL
* @return string Parse URL
*/
public static function absoluteURL($root, $url)
{
$host = preg_replace(
'|^([a-z]{3,}://)(.*?)/(.*)$|',
'$1$2',
$root
);
$parse = parse_url($url);
if (empty($parse['scheme'])) {
if (strpos($url, '/') === 0) {
$url = $host . $url;
} elseif (strpos($url, '#') === 0) {
$url = $root . $url;
} elseif (preg_match('|/$|', $root)) {
$url = $root . $url;
} else {
$url = dirname($root) . '/' . $url;
}
}
return $url;
}
/**
* Get list of feeds status.
*
* @return array List of names/values of feeds status
*/
public static function getAllStatus()
{
return [
__('Disabled') => '0',
__('Enabled') => '1',
];
}
/**
* Get list of predefined interval.
*
* @return array List of Name/time of intervals
*/
public static function getAllUpdateInterval()
{
return [
__('Every hour') => 3600,
__('Every two hours') => 7200,
__('Two times per day') => 43200,
__('Every day') => 86400,
__('Every two days') => 172800,
__('Every week') => 604800,
];
}
/**
* Get list of (super)admins of current blog.
*
* @return array List of UserCNs/UserIds
*/
public function getAllBlogAdmins()
{
$admins = [];
# Get super admins
$rs = $this->con->select(
'SELECT user_id, user_super, user_name, user_firstname, user_displayname ' .
'FROM ' . $this->con->escapeSystem(dcCore::app()->prefix . dcAuth::USER_TABLE_NAME) . ' ' .
'WHERE user_super = 1 AND user_status = 1 '
);
if (!$rs->isEmpty()) {
while ($rs->fetch()) {
$user_cn = dcUtils::getUserCN(
$rs->user_id,
$rs->user_name,
$rs->user_firstname,
$rs->user_displayname
);
$admins[$user_cn . ' (super admin)'] = $rs->user_id;
}
}
# Get admins
$rs = $this->con->select(
'SELECT U.user_id, U.user_super, U.user_name, U.user_firstname, U.user_displayname ' .
'FROM ' . $this->con->escapeSystem(dcCore::app()->prefix . dcAuth::USER_TABLE_NAME) . ' U ' .
'LEFT JOIN ' . $this->con->escapeSystem(dcCore::app()->prefix . dcAuth::PERMISSIONS_TABLE_NAME) . ' P ' .
'ON U.user_id=P.user_id ' .
'WHERE U.user_status = 1 ' .
"AND P.blog_id = '" . $this->blog . "' " .
"AND P.permissions LIKE '%|admin|%' "
);
if (!$rs->isEmpty()) {
while ($rs->fetch()) {
$user_cn = dcUtils::getUserCN(
$rs->user_id,
$rs->user_name,
$rs->user_firstname,
$rs->user_displayname
);
$admins[$user_cn . ' (admin)'] = $rs->user_id;
}
}
return $admins;
}
/**
* Get list of urls where entries could be hacked.
*
* @return array List of names/types of URLs
*/
public static function getPublicUrlTypes()
{
$types = [];
# --BEHAVIOR-- zoneclearFeedServerPublicUrlTypes
dcCore::app()->callBehavior('zoneclearFeedServerPublicUrlTypes', $types);
$types[__('Home page')] = 'default';
$types[__('Entries pages')] = 'post';
$types[__('Tags pages')] = 'tag';
$types[__('Archives pages')] = 'archive';
$types[__('Category pages')] = 'category';
$types[__('Entries feed')] = 'feed';
return $types;
}
/**
* Take care about plugin tweakurls (thanks Mathieu M.).
*
* @param cursor $cur cursor instance
* @param integer $id Post Id
*/
public static function tweakurlsAfterPostCreate(cursor $cur, $id)
{
if (version_compare(dcCore::app()->plugins->moduleInfo('tweakurls', 'version'), '0.8', '>=')) {
$cur->post_url = tweakUrls::tweakBlogURL($cur->post_url);
dcCore::app()->auth->sudo([dcCore::app()->blog, 'updPost'], $id, $cur);
}
}
}