diff --git a/cron-script.php b/cron-script.php index e1faca5..62f69f9 100644 --- a/cron-script.php +++ b/cron-script.php @@ -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"); diff --git a/inc/class.zcfsadminbehaviors.php b/inc/class.zcfsadminbehaviors.php deleted file mode 100644 index 8d4f294..0000000 --- a/inc/class.zcfsadminbehaviors.php +++ /dev/null @@ -1,243 +0,0 @@ - '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'] .= '
' . 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( - '

%s

', - 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'] = '
' . - '
' . __('Feed source') . '
' . - '

' . - '' . __('feed URL') . ' - ' . - '' . __('site URL') . '' . - '

' . - $edit . - '
'; - } - - /** - * 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', - ]) . ' ' - ); - } -} diff --git a/inc/class.zcfspublicbehaviors.php b/inc/class.zcfspublicbehaviors.php deleted file mode 100644 index 8a25212..0000000 --- a/inc/class.zcfspublicbehaviors.php +++ /dev/null @@ -1,87 +0,0 @@ -__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 \n" . - dcUtils::jsLoad(dcCore::app()->blog->url . dcCore::app()->url->getBase('zoneclearFeedsPage') . '/zcfsupd.js') . - "\n"; - } -} diff --git a/js/list.js b/js/list.js index 062e010..2886045 100644 --- a/js/list.js +++ b/js/list.js @@ -1,3 +1,8 @@ -$(function(){ - $('.checkboxes-helpers').each(function(){dotclear.checkboxesHelpers(this);}); +/*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'); }); \ No newline at end of file diff --git a/js/post.js b/js/post.js index 4b3e492..21f7365 100644 --- a/js/post.js +++ b/js/post.js @@ -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} ); }); \ No newline at end of file diff --git a/locales/fr/main.lang.php b/locales/fr/main.lang.php new file mode 100644 index 0000000..9d49796 --- /dev/null +++ b/locales/fr/main.lang.php @@ -0,0 +1,160 @@ +%s'] = 'Billet original sur %s'; +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'; diff --git a/locales/fr/main.po b/locales/fr/main.po index 6a6d015..2f8aa57 100644 --- a/locales/fr/main.po +++ b/locales/fr/main.po @@ -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 %s" -msgstr "Billet original sur %s" +#: 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 %s" +msgstr "Billet original sur %s" + +#: 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" diff --git a/src/ActivityReportActions.php b/src/ActivityReportActions.php index 03d244a..0238089 100644 --- a/src/ActivityReportActions.php +++ b/src/ActivityReportActions.php @@ -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); -class zcfsActivityReportBehaviors +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() + public static function init(): void { - # This file is used with plugin activityReport - dcCore::app()->activityReport->addGroup( - 'zoneclearFeedServer', - __('Plugin zoneclearFeedServer') - ); + $group = new Group(My::id(), My::name()); - # from BEHAVIOR zoneclearFeedServerAfterAddFeed in zoneclearFeedServer/inc/class.zoneclear.feed.server.php - dcCore::app()->activityReport->addAction( - 'zoneclearFeedServer', - 'create', - __('feed creation'), + $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; + } + + $rs = ZoneclearFeedServer::instance()->getFeeds(['feed_id' => $id]); + if ($rs->isEmpty()) { + return; + } + $row = new FeedRow($rs); + + $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); } } diff --git a/src/Backend.php b/src/Backend.php index 698d0f2..a8fd0a2 100644 --- a/src/Backend.php +++ b/src/Backend.php @@ -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); -dcCore::app()->blog->settings->addNamespace(basename(__DIR__)); +namespace Dotclear\Plugin\zoneclearFeedServer; -require_once __DIR__ . '/_widgets.php'; +use dcAdmin; +use dcCore; +use dcPage; +use dcMenu; +use dcNsProcess; -$perm = dcCore::app()->auth->check(dcCore::app()->auth->makePermissions([ - dcAuth::PERMISSION_CONTENT_ADMIN, -]), dcCore::app()->blog->id); +/** + * Backend prepend. + */ +class Backend extends dcNsProcess +{ + public static function init(): bool + { + static::$init = defined('DC_CONTEXT_ADMIN') + && My::phpCompliant(); -if (dcCore::app()->blog->settings->__get(basename(__DIR__))->active - && '' != dcCore::app()->blog->settings->__get(basename(__DIR__))->user -) { - dcCore::app()->menu[dcAdmin::MENU_PLUGINS]->addItem( - __('Feeds server'), - dcCore::app()->adminurl->get('admin.plugin.' . basename(__DIR__)), - dcPage::getPF(basename(__DIR__) . '/icon.svg'), - preg_match( - '/' . preg_quote(dcCore::app()->adminurl->get('admin.plugin.' . basename(__DIR__))) . '(&.*)?$/', - $_SERVER['REQUEST_URI'] - ), - $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']); + return static::$init; } - # Take care about tweakurls (thanks Mathieu M.) - dcCore::app()->addbehavior('zcfsAfterPostCreate', ['zoneclearFeedServer', 'tweakurlsAfterPostCreate']); -} + public static function process(): bool + { + if (!static::$init) { + return false; + } -# Delete related info about feed post in meta table -dcCore::app()->addBehavior('adminBeforePostDelete', ['zcfsAdminBehaviors', 'adminBeforePostDelete']); + // 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'], + ]); + + // 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); + + // add sidebar menu icon + if ((dcCore::app()->menu[dcAdmin::MENU_PLUGINS] instanceof dcMenu)) { + dcCore::app()->menu[dcAdmin::MENU_PLUGINS]->addItem( + 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.' . My::id())) . '(&.*)?$/', + $_SERVER['REQUEST_URI'] + ), + $has_perm + ); + } + + // no perm + if (!$has_perm) { + return true; + } + + // 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; + } +} diff --git a/src/BackendBehaviors.php b/src/BackendBehaviors.php new file mode 100644 index 0000000..4c69bf1 --- /dev/null +++ b/src/BackendBehaviors.php @@ -0,0 +1,248 @@ +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'] .= '
' . 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'] = '' . __('Feed') . ''; + } + + /** + * 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('
') + ->items([ + (new Link()) + ->href($url) + ->title($author . ' - ' . $url) + ->text(__('feed URL')), + (new Link()) + ->href($site) + ->title($sitename . ' - ' . $site) + ->text(__('site URL')), + $edit, + ]), + ]) + ->render(); + } +} diff --git a/src/Combo.php b/src/Combo.php new file mode 100644 index 0000000..48adb77 --- /dev/null +++ b/src/Combo.php @@ -0,0 +1,143 @@ + + */ + 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 + */ + public static function postsSortby(): array + { + return [ + __('Date') => 'post_dt', + __('Title') => 'post_title', + __('Category') => 'cat_title', + __('Author') => 'user_id', + __('Status') => 'post_status', + ]; + } + + /** + * @return array + */ + public static function feedsStatus(): array + { + return [ + __('Disabled') => '0', + __('Enabled') => '1', + ]; + } + + /** + * @return array + */ + public static function postsStatus(): array + { + return [ + __('Unpublished') => 0, + __('Published') => 1, + ]; + } + + /** + * @return array + */ + 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 + */ + public static function tagCase(): array + { + return [ + __('Keep source case') => 0, + __('First upper case') => 1, + __('All lower case') => 2, + __('All upper case') => 3, + ]; + } + + /** + * @return array + */ + public static function pubUpdate(): array + { + return [ + __('Disable') => 0, + __('Before display') => 1, + __('After display') => 2, + __('Through Ajax') => 3, + ]; + } + + /** + * @return array + */ + 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('  ', $level - 1) . + '• ' . Html::escapeHTML($cat_title) + ] = $cat_id; + } + } + } catch (Exception $e) { + } + + return $combo; + } +} diff --git a/src/Config.php b/src/Config.php index 15ac429..16e0e8f 100644 --- a/src/Config.php +++ b/src/Config.php @@ -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); -$redir = empty($_REQUEST['redir']) ? dcCore::app()->admin->list->getURL() . '#plugins' : $_REQUEST['redir']; +namespace Dotclear\Plugin\zoneclearFeedServer; -# -- Get settings -- -dcCore::app()->blog->settings->addNamespace(basename(__DIR__)); -$s = dcCore::app()->blog->settings->__get(basename(__DIR__)); +use adminModulesList; +use dcCore; +use dcPage; +use dcNsProcess; +use Dotclear\Helper\Html\Form\{ + Checkbox, + Div, + Input, + Label, + Link, + Number, + Para, + Select, + Text +}; +use Exception; -$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; +/** + * Backend module configuration. + */ +class Config extends dcNsProcess +{ + public static function init(): bool + { + static::$init == defined('DC_CONTEXT_ADMIN') + && dcCore::app()->auth?->isSuperAdmin() + && My::phpCompliant(); -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 = []; -} + return static::$init; + } -$zc = new zoneclearFeedServer(); - -# -- Set settings -- -if (!empty($_POST['save'])) { - 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; + public static function process(): bool + { + if (!static::$init) { + return false; } - $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); + // no action + if (empty($_POST['save'])) { + return true; + } - dcCore::app()->blog->triggerBlog(); + // read settings + $s = ZoneclearFeedServer::instance()->settings; - dcAdminNotices::addSuccessNotice( - __('Configuration successfully updated.') - ); - dcCore::app()->adminurl->redirect( - 'admin.plugins', - ['module' => basename(__DIR__), 'conf' => 1, 'redir' => dcCore::app()->admin->list->getRedir()] - ); - } catch (Exception $e) { - dcCore::app()->error->add($e->getMessage()); + // write settings + try { + foreach ($s->dump() as $key => $value) { + $s->set($key, $_POST[$key] ?? $value); + } + + 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; + } + + public static function render(): void + { + if (!static::$init) { + return; + } + + // nullsafe + if (is_null(dcCore::app()->blog)) { + return; + } + + $z = ZoneclearFeedServer::instance(); + $s = $z->settings; + + $msg = []; + if (!is_writable(DC_TPL_CACHE)) { + $msg[] = (new Para()) + ->class('error') + ->text(__('Dotclear cache is not writable or not well configured!')); + } + if ($s->pub_active) { + $msg[] = (new Para()) + ->items([ + (new Link()) + ->class('onblog_link outgoing') + ->text(__('View the public list of feeds') . ' ') + ->href(dcCore::app()->blog->url . dcCore::app()->url->getBase('zoneclearFeedsPage')), + ]); + } + + $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), + ]); + } + + $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'); } } - -# -- 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, -]; - -$pub_page_url = dcCore::app()->blog->url . dcCore::app()->url->getBase('zoneclearFeedsPage'); - -# -- Display form -- - -if (!is_writable(DC_TPL_CACHE)) { - echo '

' . __('Dotclear cache is not writable or not well configured!') . '

'; -} - -echo ' -
-

' . __('Activation') . '

- -

-
- -
'; - -if ($s->pub_active) { - echo sprintf( - '

%s

', - $pub_page_url, - $pub_page_url, - __('View the public list of feeds') - ); -} - -echo ' -

' . __('Rules') . '

- -
- -

' . -form::combo('post_status_new', $combo_status, $post_status_new) . '

- -

' . -form::combo('feeduser', $combo_admins, $feeduser) . '

- -

' . -form::combo('tag_case', $combo_tagcase, $tag_case) . '

- -
- -

' . -form::combo('bhv_pub_upd', $combo_pubupd, $bhv_pub_upd) . '

- -

- -

- -

- -
- -

' . __('Redirect to original post on:') . '

    '; - -foreach ($zc->getPublicUrlTypes() as $k => $v) { - echo sprintf( - '
  • ', - $v, - form::checkbox(['post_title_redir[]', 'post_title_redir_' . $v], $v, in_array($v, $post_title_redir)), - __($k) - ); -} -echo ' -
- -
- -

' . __('Show full content on:') . '

    '; - -foreach ($zc->getPublicUrlTypes() as $k => $v) { - echo sprintf( - '
  • ', - $v, - form::checkbox(['post_full_tpl[]', 'post_full_tpl_' . $v], $v, in_array($v, $post_full_tpl)), - __($k) - ); -} - -echo '
'; - -dcPage::helpBlock('zoneclearFeedServer'); diff --git a/src/FeedRow.php b/src/FeedRow.php new file mode 100644 index 0000000..7827971 --- /dev/null +++ b/src/FeedRow.php @@ -0,0 +1,72 @@ +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; + } +} diff --git a/src/FeedsActions.php b/src/FeedsActions.php index f9bccb8..35412f2 100644 --- a/src/FeedsActions.php +++ b/src/FeedsActions.php @@ -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 +namespace Dotclear\Plugin\zoneclearFeedServer; + +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 { - public $zcfs; + public ZoneclearFeedServer $zcfs; - public function __construct($uri, $redirect_args = []) + /** + * @param string $uri + * @param array $redirect_args + */ + public function __construct(string $uri, array $redirect_args = []) { - $this->zcfs = new zoneclearFeedServer(); + $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 '' . __('Feeds server') . '' . @@ -49,331 +67,39 @@ class zcfsFeedsActions extends dcActions __('Back to feeds list') . '

'; } - public function endPage() + public function endPage(): void { echo ''; } - 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) => '', - $this->getCallerTitle() => $this->getRedirection(true), - __('Feeds actions') => '', + Html::escapeHTML((string) dcCore::app()->blog?->name) => '', + $this->getCallerTitle() => $this->getRedirection(true), + __('Feeds actions') => '', ]) ); $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 - '
' . - $ap->getCheckboxes() . - '

' . - form::combo(['upd_cat_id'], $categories_combo, '') . - dcCore::app()->formNonce() . - $ap->getHiddenFields() . - form::hidden(['action'], 'changecat') . - '

' . - '
'; - - $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 - '
' . - $ap->getCheckboxes() . - '

' . - form::combo(['upd_upd_int'], $ap->zcfs->getAllUpdateInterval(), '') . - dcCore::app()->formNonce() . - $ap->getHiddenFields() . - form::hidden(['action'], 'changeint') . - '

' . - '
'; - - $ap->endPage(); + $this->rs = MetaRecord::newFromArray([]); } } } diff --git a/src/FeedsDefaultActions.php b/src/FeedsDefaultActions.php index 88d1cb8..b154062 100644 --- a/src/FeedsDefaultActions.php +++ b/src/FeedsDefaultActions.php @@ -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,116 +184,148 @@ 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; } + $cur = $ap->zcfs->openCursor(); foreach ($ids as $id) { - $cur = $ap->zcfs->openCursor(); - $cur->feed_upd_last = 0; - $ap->zcfs->updFeed($id, $cur); + $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; + $cur = $ap->zcfs->openCursor(); foreach ($ids as $id) { - $cur = $ap->zcfs->openCursor(); - $cur->cat_id = $cat_id == 0 ? null : $cat_id; - $ap->zcfs->updFeed($id, $cur); + $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) => '', - __('Feeds server') => '', - $ap->getCallerTitle() => $ap->getRedirection(true), - __('Change category for this selection') => '', + Html::escapeHTML((string) dcCore::app()->blog?->name) => '', + __('Feeds server') => '', + $ap->getCallerTitle() => $ap->getRedirection(true), + __('Change category for this selection') => '', ]) ); echo - '
' . - $ap->getCheckboxes() . - '

' . - form::combo(['upd_cat_id'], $categories_combo, '') . - dcCore::app()->formNonce() . - $ap->getHiddenFields() . - form::hidden(['action'], 'changecat') . - '

' . - '
'; + (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; + $cur = $ap->zcfs->openCursor(); foreach ($ids as $id) { - $cur = $ap->zcfs->openCursor(); - $cur->feed_upd_int = $upd_int; - $ap->zcfs->updFeed($id, $cur); + $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,24 +334,37 @@ class zcfsDefaultFeedsActions $ap->beginPage( dcPage::breadcrumb( [ - html::escapeHTML(dcCore::app()->blog->name) => '', - __('Feeds server') => '', - $ap->getCallerTitle() => $ap->getRedirection(true), - __('Change update frequency for this selection') => '', + Html::escapeHTML((string) dcCore::app()->blog?->name) => '', + __('Feeds server') => '', + $ap->getCallerTitle() => $ap->getRedirection(true), + __('Change update frequency for this selection') => '', ] ) ); echo - '
' . - $ap->getCheckboxes() . - '

' . - form::combo(['upd_upd_int'], $ap->zcfs->getAllUpdateInterval(), '') . - dcCore::app()->formNonce() . - $ap->getHiddenFields() . - form::hidden(['action'], 'changeint') . - '

' . - '
'; + (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(); } diff --git a/src/FeedsList.php b/src/FeedsList.php index 5453e1c..ff99440 100644 --- a/src/FeedsList.php +++ b/src/FeedsList.php @@ -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 '

' . __('No feeds matches the filter') . '

'; - } else { - echo '

' . __('No feeds') . '

'; - } - } 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 = '
' . - '' . - ''; + echo + (new Text( + 'p', + $filter->show() ? + __('No feeds matches the filter') : + __('No feeds') + )) + ->class('info') + ->render(); - $cols = [ - 'title' => '', - 'desc' => '', - 'period' => '', - 'update' => '', - 'entries' => '', - 'status' => '', - ]; - $cols = new ArrayObject($cols); - - dcCore::app()->callBehavior('adminZcfsFeedsListHeader', $this->rs, $cols); - - $this->userColumns('zcfs_feeds', $cols); - - $html_block .= '' . implode(iterator_to_array($cols)) . '%s
' . ( - $filter ? - sprintf(__('List of %s feeds matching the filter.'), $this->rs_count) : - sprintf(__('List of feeds (%s)'), $this->rs_count) - ) . '
' . __('Name') . '' . __('Feed') . '' . __('Frequency') . '' . __('Last update') . '' . __('Entries') . '' . __('Status') . '
%s
'; - if ($enclose_block) { - $html_block = sprintf($enclose_block, $html_block); - } - - echo $pager->getLinks(); - - $blocks = explode('%s', $html_block); - - echo $blocks[0]; - - while ($this->rs->fetch()) { - echo $this->feedsLine(isset($entries[$this->rs->feed_id])); - } - - echo $blocks[1]; - echo $blocks[2]; - echo $pager->getLinks(); + 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); + + $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"'), + ]); + + $this->userColumns(My::id() . 'feeds', $cols); + + $lines = []; + while ($this->rs->fetch()) { + $lines[] = $this->line(isset($_POST['feeds']) && in_array($this->rs->post_id, $_POST['feeds'])); + } + + 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 ? - 'enable' : - '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' => '' . - form::checkbox(['feeds[]'], $this->rs->feed_id, ['checked' => $checked]) . - '', - 'title' => '' . - '' . html::escapeHTML($this->rs->feed_name) . '' . - '', - 'desc' => '' . - '' . html::escapeHTML($shunk_feed) . '' . - '', - 'period' => '' . - array_search($this->rs->feed_upd_int, $combo_upd_int) . - '', - 'update' => '' . - ( - $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')) - ) . '', - 'entries' => '' . - ( - $entries_count ? - '' . $entries_count . '' : - $entries_count - ) . '', - 'status' => '' . $status . '', - ]; + 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 - '' . - implode(iterator_to_array($cols)) . - ''; + return (new Para('p' . $row->id, 'tr')) + ->class('line' . ($row->status != 1 ? ' offline ' : '')) + ->items(iterator_to_array($cols)); } } diff --git a/src/Frontend.php b/src/Frontend.php index 4e42445..06632e4 100644 --- a/src/Frontend.php +++ b/src/Frontend.php @@ -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 for settings -dcCore::app()->blog->settings->addNamespace(basename(__DIR__)); -$s = dcCore::app()->blog->settings->__get(basename(__DIR__)); +namespace Dotclear\Plugin\zoneclearFeedServer; -# Widgets -require_once __DIR__ . '/_widgets.php'; +use dcCore; +use dcNsProcess; +use dcUtils; +use Dotclear\Database\MetaRecord; +use Dotclear\Helper\Html\Html; +use Exception; -dcCore::app()->addBehavior('coreBlogGetPosts', ['zcfsPublicBehaviors', 'coreBlogGetPosts']); +/** + * Frontend prepend. + */ +class Frontend extends dcNsProcess +{ + public static function init(): bool + { + static::$init = My::phpCompliant(); -if (!$s->active) { - return null; -} -if (1 == $s->bhv_pub_upd) { - dcCore::app()->addBehavior('publicBeforeDocumentV2', ['zcfsPublicBehaviors', 'publicDocumentV2']); -} 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'); + return static::$init; } -}); + + public static function process(): bool + { + if (!static::$init) { + return false; + } + + $s = ZoneclearFeedServer::instance()->settings; + + 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 true; + } + + // feeds update methods + if (1 == $s->bhv_pub_upd) { + 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', 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 \n" . + dcUtils::jsLoad(dcCore::app()->blog->url . dcCore::app()->url->getBase('zoneclearFeedsPage') . '/zcfsupd.js') . + "\n"; + }); + } + + return true; + } +} diff --git a/src/Install.php b/src/Install.php index 902260b..a0713c3 100644 --- a/src/Install.php +++ b/src/Install.php @@ -10,70 +10,91 @@ * @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); -try { - // Check module version - if (!dcCore::app()->newVersion( - basename(__DIR__), - dcCore::app()->plugins->moduleInfo(basename(__DIR__), 'version') - )) { - return null; +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; } - // Upgrade existing install - zcfsUpgrade::preUpgrade(); + public static function process(): bool + { + if (!static::$init) { + return false; + } - // 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) + try { + // Upgrade existing install + Upgrade::preUpgrade(); - ->primary('pk_zcfs', 'feed_id') - ->index('idx_zcfs_type', 'btree', 'feed_type') - ->index('idx_zcfs_blog', 'btree', 'blog_id'); + // Tables + $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) - $ti = new dbStruct(dcCore::app()->con, dcCore::app()->prefix); - $changes = $ti->synchronize($t); + ->primary('pk_zcfs', 'feed_id') + ->index('idx_zcfs_type', 'btree', 'feed_type') + ->index('idx_zcfs_blog', 'btree', 'blog_id'); - // Settings - dcCore::app()->blog->settings->addNamespace(basename(__DIR__)); - $s = dcCore::app()->blog->settings->__get(basename(__DIR__)); - $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); - $s->put('bhv_pub_upd', 2, 'string', 'Auto update on public side (disable/before/after)', false, true); - $s->put('update_limit', 1, 'integer', 'Number of feeds to update at one time', false, true); - $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); + (new Structure(dcCore::app()->con, dcCore::app()->prefix))->synchronize($s); - return true; -} catch (Exception $e) { - dcCore::app()->error->add($e->getMessage()); + // Settings + $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); + $s->put('bhv_pub_upd', 2, 'string', 'Auto update on public side (disable/before/after)', false, true); + $s->put('update_limit', 1, 'integer', 'Number of feeds to update at one time', false, true); + $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', ['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 false; + return true; + } catch (Exception $e) { + dcCore::app()->error->add($e->getMessage()); + + return false; + } + } } diff --git a/src/Manage.php b/src/Manage.php index d10e7b7..44f5110 100644 --- a/src/Manage.php +++ b/src/Manage.php @@ -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); -if (0 !== dcCore::app()->testVersion( - basename(__DIR__), - dcCore::app()->plugins->moduleInfo(basename(__DIR__), 'version') -)) { - return null; -} +namespace Dotclear\Plugin\zoneclearFeedServer; -dcPage::check(dcCore::app()->auth->makePermissions([ - dcAuth::PERMISSION_CONTENT_ADMIN, -])); +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; -$zcfs = new zoneclearFeedServer(); +/** + * 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); -# Not configured -if (!dcCore::app()->blog->settings->__get(basename(__DIR__))->active - || !dcCore::app()->blog->settings->__get(basename(__DIR__))->user -) { - echo - '' . __('Feeds server') . '' . - dcPage::breadcrumb([ - __('Plugins') => '', - __('Feeds server') => '', - ]) . - 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; - - $can_view_page = true; - - $feed_headlink = ''; - $feed_link = '%s'; - - $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('  ', $categories->level - 1) . - '• ' . html::escapeHTML($categories->cat_title) - ] = $categories->cat_id; + // call period manage page + if (($_REQUEST['part'] ?? 'feeds') === 'feed') { + static::$init = ManageFeed::init(); } - } catch (Exception $e) { - dcCore::app()->error->add($e->getMessage()); + + return static::$init; } - # Get entry informations - if (!empty($_REQUEST['feed_id'])) { - $feed = $zcfs->getFeeds(['feed_id' => $_REQUEST['feed_id']]); - - 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') . ' »' - ); - $next_headlink = sprintf( - $feed_headlink, - 'next', - html::escapeHTML($next_rs->feed_name), - $next_rs->feed_id - ); - } - - if (!$prev_rs->isEmpty()) { - $prev_link = sprintf( - $feed_link, - $prev_rs->feed_id, - html::escapeHTML($prev_rs->feed_name), - '« ' . __('previous feed') - ); - $prev_headlink = sprintf( - $feed_headlink, - 'previous', - html::escapeHTML($prev_rs->feed_name), - $prev_rs->feed_id - ); - } + public static function process(): bool + { + if (!static::$init) { + return false; } + + $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; } - 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()); + public static function render(): void + { + if (!static::$init) { + return; } - } - 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()); - } + if (is_null(dcCore::app()->adminurl)) { + return; } - } - # Prepared entries list - if ($feed_id && $can_view_page) { - # action - $posts_actions_page = new dcPostsActions( + $z = ZoneclearFeedServer::instance(); + $s = $z->settings; + + // not configured + if (!$s->active || !$s->user) { + dcPage::openModule(My::id()); + + echo + dcPage::breadcrumb([ + __('Plugins') => '', + My::name() => '', + ]) . + dcPage::notices(); + + dcPage::closeModule(); + + return; + } + + // call feed manage page + if (($_REQUEST['part'] ?? 'feeds') === 'feed') { + ManageFeed::render(); + + return; + } + + // feeds actions + $feeds_actions_page = new FeedsActions( 'plugin.php', - [ - 'p' => basename(__DIR__), - 'part' => 'feed', - 'feed_id' => $feed_id, - '_ANCHOR' => 'entries', - ] + ['p' => My::id(), 'part' => 'feeds'] ); - if ($posts_actions_page->process()) { - return null; + if ($feeds_actions_page->process()) { + return; } - # filters - $post_filter = new zcfsPostFilter(); - $post_filter->add('part', 'feed'); - $post_filter->add('feed_id', $feed_id); - $params = $post_filter->params(); + // 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(); - # 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 + // feeds list try { - $posts = $zcfs->getPostsByFeed($params); - $counter = $zcfs->getPostsByFeed($params, true); - $post_list = new zcfsEntriesList( - dcCore::app(), - $posts, - $counter->f(0) - ); + $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 - echo - '' . __('Feeds server') . '' . - ($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 . + 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() + ); - # --BEHAVIOR-- adminZoneclearFeedServerHeader - dcCore::app()->callBehavior('adminZoneclearFeedServerHeader') . + echo + dcPage::breadcrumb([ + __('Plugins') => '', + My::name() => dcCore::app()->adminurl->get('admin.plugin.' . My::id()), + __('Feeds list') => '', + ]) . + dcPage::notices(); - '' . + echo + (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(); - dcPage::breadcrumb([ - __('Plugins') => '', - __('Feeds server') => dcCore::app()->admin->getPageURL(), - ($feed_id ? __('Edit feed') : __('New feed')) => '', - ]) . - dcPage::notices() . - ($feed_id ? '

' . sprintf(__('Edit feed "%s"'), $feed_name) . '

' : ''); + if (isset($feeds_list)) { + $feeds_filter->display( + 'admin.plugin.' . My::id(), + dcCore::app()->adminurl->getHiddenFormFields('admin.plugin.' . My::id(), ['part' => 'feeds']) + ); - # Feed - if ($can_view_page) { - # nav link - if ($feed_id && ($next_link || $prev_link)) { - echo ''; + $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 ' - -
- -
' . - - '
' . - '

' . __('Feed information') . '

' . - - '

' . - form::field('feed_name', 60, 255, $feed_name, 'maximal') . - '

' . - - '

' . - form::field('feed_owner', 60, 255, $feed_owner, 'maximal') . - '

' . - - // move this away - '

' . - form::field('feed_tweeter', 60, 64, $feed_tweeter, 'maximal') . - '

' . - - '

' . - form::field('feed_url', 60, 255, $feed_url, 'maximal') . - '

' . - - '

' . - form::field('feed_feed', 60, 255, $feed_feed, 'maximal') . - '

' . - - '

' . - form::field('feed_desc', 60, 255, $feed_desc, 'maximal') . - '

' . - - '

' . - form::field('feed_tags', 60, 255, $feed_tags, 'maximal') . - '

' . - - # --BEHAVIOR-- adminZoneclearFeedServerFeedForm - dcCore::app()->callBehavior('adminZoneclearFeedServerFeedForm', $feed_id) . - - '
' . - - '
' . - '

' . __('Local settings') . '

' . - - '

' . - form::combo('feed_cat_id', $combo_categories, $feed_cat_id, 'maximal') . - '

' . - - '

' . - form::combo('feed_status', $combo_status, $feed_status, 'maximal') . - '

' . - - '

' . - form::combo('feed_upd_int', $combo_upd_int, $feed_upd_int, 'maximal') . - '

' . - - '

' . - form::combo('feed_lang', $combo_langs, $feed_lang, 'maximal') . - '

' . - - '

' . - - '
' . - - '
' . - - '

- ' . - dcCore::app()->adminurl->getHiddenFormFields('admin.plugin.' . basename(__DIR__), [ - 'part' => 'feed', - 'feed_id' => $feed_id, - 'action' => 'savefeed', - ]) . - dcCore::app()->formNonce() . - '

-
- '; - } - - # entries - if ($feed_id && $can_view_page && isset($post_filter) && isset($post_list) && isset($posts_actions_page) && !dcCore::app()->error->flag()) { - echo '
'; - - # 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', - '
' . - '%s' . - - '
' . - '

' . - - '

' . __('Selected entries action:') . ' ' . - form::combo('action', $posts_actions_page->getCombo()) . - '

' . - 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() . - '
' . - '
', - $post_filter->show() - ); - - echo '
'; - } - -############################################################ -# -# All feeds -# -############################################################ -} else { - # actions - $feeds_actions_page = new zcfsFeedsActions( - 'plugin.php', - ['p' => basename(__DIR__), 'part' => 'feeds'] - ); - if ($feeds_actions_page->process()) { - return null; - } - - # filters - $feeds_filter = new adminGenericFilter(dcCore::app(), 'zcfs_feeds'); - $feeds_filter->add('part', 'feeds'); - $feeds_filter->add(dcAdminFilters::getPageFilter()); - $feeds_filter->add(dcAdminFilters::getSearchFilter()); - $params = $feeds_filter->params(); - - # feeds - try { - $feeds = $zcfs->getFeeds($params); - $feeds_counter = $zcfs->getFeeds($params, true)->f(0); - $feeds_list = new zcfsFeedsList( - dcCore::app(), - $feeds, - $feeds_counter - ); - } catch (Exception $e) { - dcCore::app()->error->add($e->getMessage()); - } - - # display - echo - '' . __('Feeds server') . '' . - $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') . - - '' . - - dcPage::breadcrumb([ - __('Plugins') => '', - __('Feeds server') => '', - ]) . - dcPage::notices(); - - if (isset($feeds_list)) { - echo - '

' . - '' . - __('New feed') . '

'; - - $feeds_filter->display( - 'admin.plugin.' . basename(__DIR__), - dcCore::app()->adminurl->getHiddenFormFields('admin.plugin.' . basename(__DIR__), ['part' => 'feeds']) - ); - - $feeds_list->feedsDisplay( - $feeds_filter->page, - $feeds_filter->nb, - '
' . - '%s' . - '
' . - '

' . - '

' . __('Selected feeds action:') . ' ' . - form::combo(['action'], $feeds_actions_page->getCombo()) . - '' . - dcCore::app()->adminurl->getHiddenFormFields('admin.plugin.' . basename(__DIR__), $feeds_filter->values(true)) . - dcCore::app()->formNonce() . - '

' . - '
' . - '
', - $feeds_filter->show() - ); + dcPage::closeModule(); } } - -echo ''; diff --git a/src/ManageFeed.php b/src/ManageFeed.php new file mode 100644 index 0000000..087a593 --- /dev/null +++ b/src/ManageFeed.php @@ -0,0 +1,463 @@ +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 '
'; + + # 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 '
'; + } + + dcPage::closeModule(); + } +} diff --git a/src/ManageFeedVars.php b/src/ManageFeedVars.php new file mode 100644 index 0000000..4f7c1e2 --- /dev/null +++ b/src/ManageFeedVars.php @@ -0,0 +1,242 @@ +admin->getPageURL() . '&part=feed&feed_id=%s" />'; + $feed_link = '%s'; + $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') . ' »' + ); + $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), + '« ' . __('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; + } +} diff --git a/src/My.php b/src/My.php new file mode 100644 index 0000000..d24c0e8 --- /dev/null +++ b/src/My.php @@ -0,0 +1,94 @@ + This module template blocks */ + public const TPL_BLOCKS = [ + 'Feeds', + 'FeedsFooter', + 'FeedsHeader', + 'FeedIf', + ]; + + /** @var array 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, '>='); + } +} diff --git a/src/PostsFilter.php b/src/PostsFilter.php index 0aac214..867db04 100644 --- a/src/PostsFilter.php +++ b/src/PostsFilter.php @@ -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()) { - $combo[ - str_repeat(' ', ($categories->level - 1) * 4) . - html::escapeHTML($categories->cat_title) . ' (' . $categories->nb_post . ')' - ] = $categories->cat_id; + if (is_numeric($categories->f('level')) && is_string($categories->f('cat_title'))) { + $combo[ + str_repeat(' ', ((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) { diff --git a/src/PostsList.php b/src/PostsList.php index 615b61f..4b67912 100644 --- a/src/PostsList.php +++ b/src/PostsList.php @@ -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 '

' . ( - $filter ? + echo + (new Text( + 'p', + $filter->show() ? __('No entries matches the filter') : __('No entries') - ) . '

'; - } else { - $pager = new dcPager($page, $this->rs_count, $nb_per_page, 10); - $pager->base_url = $base_url; + )) + ->class('info') + ->render(); - $entries = []; - if (isset($_REQUEST['feeds'])) { - foreach ($_REQUEST['feeds'] as $v) { - $entries[(int) $v] = true; - } - } - - $html_block = '
' . - '' . - ''; - - $cols = [ - 'title' => '', - 'date' => '', - 'author' => '', - 'category' => '', - 'status' => '', - ]; - - $cols = new ArrayObject($cols); - dcCore::app()->callBehavior('adminZcfsPostListHeader', $this->rs, $cols); - - $this->userColumns('zcfs_entries', $cols); - - $html_block .= '' . implode(iterator_to_array($cols)) . '%s
' . ( - $filter ? - sprintf(__('List of %s entries matching the filter.'), $this->rs_count) : - sprintf(__('List of entries (%s)'), $this->rs_count) - ) . '
' . __('Title') . '' . __('Date') . '' . __('Author') . '' . __('Category') . '' . __('Status') . '
'; - if ($enclose_block) { - $html_block = sprintf($enclose_block, $html_block); - } - - echo $pager->getLinks(); - - $blocks = explode('%s', $html_block); - - echo $blocks[0]; - - while ($this->rs->fetch()) { - echo $this->postLine(); - } - - echo $blocks[1]; - - echo $pager->getLinks(); + 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; + + $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"'), + ]); + + $this->userColumns(My::id() . 'posts', $cols); + + $lines = []; + while ($this->rs->fetch()) { + $lines[] = $this->line(isset($_POST['entries']) && in_array($this->rs->post_id, $_POST['entries'])); + } + + 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) ? - '%s' - : '%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 = '%1$s'; - $img_status = ''; - $sts_class = ''; switch ($this->rs->post_status) { case 1: - $img_status = sprintf($img, __('Published'), 'check-on.png'); - $sts_class = 'sts-online'; - - break; - case 0: - $img_status = sprintf($img, __('Unpublished'), 'check-off.png'); - $sts_class = 'sts-offline'; + $img_title = __('Published'); + $img_src = 'check-on.png'; + $sts_class = ' sts-online'; break; case -1: - $img_status = sprintf($img, __('Scheduled'), 'scheduled.png'); - $sts_class = 'sts-scheduled'; + $img_title = __('Scheduled'); + $img_src = 'scheduled.png'; + $sts_class = ' sts-scheduled'; break; case -2: - $img_status = sprintf($img, __('Pending'), 'check-wrn.png'); - $sts_class = 'sts-pending'; + $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 = ''; + $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' => '' . - form::checkbox(['entries[]'], $this->rs->post_id, '', '', '', !$this->rs->isEditable()) . '', - 'title' => '' . - html::escapeHTML(trim(html::clean($this->rs->post_title))) . '', - 'date' => '' . dt::dt2str(__('%Y-%m-%d %H:%M'), $this->rs->post_dt) . '', - 'author' => '' . html::escapeHTML($this->rs->user_id) . '', - 'category' => '' . $cat_title . '', - 'status' => '' . $img_status . '', - ]; + $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 .= ''; - - 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)); } } diff --git a/src/Prepend.php b/src/Prepend.php index 361c1e0..fad397d 100644 --- a/src/Prepend.php +++ b/src/Prepend.php @@ -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; -} - -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 url for page of description of the flux -dcCore::app()->url->register( - 'zoneclearFeedsPage', - 'zcfeeds', - '^zcfeeds(.*?)$', - ['zcfsUrlHandler', 'zcFeedsPage'] -); - -// Add to report on plugin activityReport -if (defined('ACTIVITY_REPORT_V2')) { - zcfsActivityReportBehaviors::init(); +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; + } + + 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(.*?)$', + [UrlHandler::class, 'zoneclearFeedsPage'] + ); + + // report zoneclearFeedServer activities + if (defined('ACTIVITY_REPORT') && ACTIVITY_REPORT == 3) { + ActivityReportActions::init(); + } + + return true; + } } diff --git a/src/RsExtPosts.php b/src/RsExtPosts.php index 3b34626..f6b7945 100644 --- a/src/RsExtPosts.php +++ b/src/RsExtPosts.php @@ -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 $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 $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 (%s)', $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( '

%s

', sprintf(__('Original post on %s'), $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( '

%s... %s

', diff --git a/src/Settings.php b/src/Settings.php new file mode 100644 index 0000000..fa4fa4f --- /dev/null +++ b/src/Settings.php @@ -0,0 +1,125 @@ + */ + public readonly array $post_full_tpl; + + /** @var array */ + 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 The settings keys + */ + public function dump(): array + { + return get_object_vars($this); + } +} diff --git a/src/Template.php b/src/Template.php index cc81e21..bf7ba01 100644 --- a/src/Template.php +++ b/src/Template.php @@ -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 '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" . 'ctx->feeds->fetch()) : ?>' . $c . '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 '' . $c . ''; } - 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 '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 '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']) ? - 'ctx->feeds->feed_lang') . '; ?>' : - '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') : + 'ctx->feeds->feed_lang])) { ?>' . + self::getValue($a, '$langs[dcCore::app()->ctx->feeds->feed_lang]') . + '' . + self::getValue($a, 'dcCore::app()->ctx->feeds->feed_lang') . + ''; } - 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 'ctx->feeds->isStart()) : ?>' . $c . ''; } - public static function FeedsFooter($a, $c) + public static function FeedsFooter(ArrayObject $a, string $c): string { return 'ctx->feeds->isEnd()) : ?>' . $c . ''; } - 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 "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 "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 - "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 'tpl->getFilters($a), $v) . '; ?>'; } - - protected static function getOperator($op) - { - switch (strtolower($op)) { - case 'or': - case '||': - return '||'; - case 'and': - case '&&': - default: - return '&&'; - } - } } diff --git a/src/Uninstall.php b/src/Uninstall.php index bdb75a5..6333081 100644 --- a/src/Uninstall.php +++ b/src/Uninstall.php @@ -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; } - //... } diff --git a/src/UninstallCleaner.php b/src/UninstallCleaner.php new file mode 100644 index 0000000..85f9b41 --- /dev/null +++ b/src/UninstallCleaner.php @@ -0,0 +1,104 @@ +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; + } +} diff --git a/src/Upgrade.php b/src/Upgrade.php index 1a3e18d..76699c9 100644 --- a/src/Upgrade.php +++ b/src/Upgrade.php @@ -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); -class zcfsUpgrade +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() + public static function preUpgrade(): void { - $current = dcCore::app()->getVersion(basename(dirname('../' . __DIR__))); - if ($current && version_compare($current, '2022.12.10', '<')) { + $current = dcCore::app()->getVersion(My::id()); + if (!is_string($current) || empty($current)) { + return; + } + + 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', ]; + $cur = dcCore::app()->con->openCursor(dcCore::app()->prefix . dcNamespace::NS_TABLE_NAME); 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' "); + $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(); + } } diff --git a/src/UrlHandler.php b/src/UrlHandler.php index e6e11ed..e5d216e 100644 --- a/src/UrlHandler.php +++ b/src/UrlHandler.php @@ -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( '%s%s', '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; } } diff --git a/src/Widgets.php b/src/Widgets.php index 5811f54..299fcaf 100644 --- a/src/Widgets.php +++ b/src/Widgets.php @@ -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( '
  • %s
  • ', - $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( '

    %s

    ', - 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('
      %s
    ', 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( '%s ', - 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( '%s', - 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( '%s ', - 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('
      %s
    ', $content) ); } diff --git a/src/ZoneclearFeedServer.php b/src/ZoneclearFeedServer.php new file mode 100644 index 0000000..56c01ec --- /dev/null +++ b/src/ZoneclearFeedServer.php @@ -0,0 +1,976 @@ +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 $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 $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 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 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(); + } +} diff --git a/src/ZoneclearFeedsServer.php b/src/ZoneclearFeedsServer.php deleted file mode 100644 index 30457a8..0000000 --- a/src/ZoneclearFeedsServer.php +++ /dev/null @@ -1,925 +0,0 @@ -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); - } - } -}