From 02edbcb877c5ab5423d59bd7aa21b3cd9700b431 Mon Sep 17 00:00:00 2001 From: Jean-Christian Denis Date: Thu, 20 Apr 2023 14:22:43 +0200 Subject: [PATCH] use namespace (and nearly full rewrite) --- _init.php | 1 - icon.png | Bin 792 -> 0 bytes icon.svg | 2 + js/backend.js | 10 + js/main.js | 91 ---- locales/fr/main.lang.php | 108 +++++ locales/fr/main.po | 536 +++++++++++++++--------- src/Action.php | 45 ++ src/ActivityBehaviors.php | 475 ++++++++++++--------- src/ActivityReport.php | 862 ++++++++++++++++++-------------------- src/Backend.php | 289 +++++++------ src/Combo.php | 72 ++++ src/Config.php | 412 +++++++++--------- src/Context.php | 71 ++++ src/Format.php | 81 ++++ src/Formats.php | 109 ++--- src/Frontend.php | 178 ++------ src/Group.php | 82 ++++ src/Groups.php | 72 ++++ src/Install.php | 132 +++--- src/Manage.php | 167 +++++--- src/ManageList.php | 102 +++++ src/My.php | 77 ++++ src/Prepend.php | 70 +++- src/Settings.php | 113 +++++ src/Template.php | 161 +++++++ src/Uninstall.php | 142 ++++--- src/UrlHandler.php | 56 +++ 28 files changed, 2833 insertions(+), 1683 deletions(-) delete mode 100644 icon.png create mode 100644 icon.svg create mode 100644 js/backend.js delete mode 100644 js/main.js create mode 100644 locales/fr/main.lang.php create mode 100644 src/Action.php create mode 100644 src/Combo.php create mode 100644 src/Context.php create mode 100644 src/Format.php create mode 100644 src/Group.php create mode 100644 src/Groups.php create mode 100644 src/ManageList.php create mode 100644 src/My.php create mode 100644 src/Settings.php create mode 100644 src/Template.php create mode 100644 src/UrlHandler.php diff --git a/_init.php b/_init.php index 89a16e9..74a0ef4 100644 --- a/_init.php +++ b/_init.php @@ -17,6 +17,5 @@ if (!defined('DC_RC_PATH')) { class initActivityReport { public const ACTIVITY_TABLE_NAME = 'activity'; - public const SETTING_TABLE_NAME = 'activity_setting'; public const CACHE_DIR_NAME = 'activityreport'; } diff --git a/icon.png b/icon.png deleted file mode 100644 index 8bdd3304de57761ba769f304df43ef97b4e6cc98..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 792 zcmV+z1LypSP)gn-s{Jsef@w|XbT!@L zKqWCIT8+A(7Sx3z9}!4Pu(d7F()NAMIIl$mh8>gK+?nLgnRCyXOBiF=&l0fDCEXmG zO1fK=fkMUbY^kO|IUx!Li*G1rXYru4dEO2NVi`tlvyl0*xG05#{;n&-JwDA~#O)Gy zp_Bjjq?zlQDb_VBdhw2?3i+}b*G)Mkej}RiH|=KYV#o;^MY)fCsb9d zsU|OsN4*FKTk!a82BnG)znj1#sVFUsLkabxPk!I1k)<6rR_jd$gsWp3CfpLm!B_y3 zsXVd;6|1uoP{IT7MFtSAy@Rth*I@QnS)ry`u-WLkiCn3Iu66|{;z1la6h!*_9BM0* z{Ph5=@-syHFF{yNfEpSa|Bn$Hz+|?+`69m@R^azIIYol*12T?FuMz1z52!OpJxahE zsKZrEL0x&ntu#0QWkR*Ix~y>@k9Eo{8Whm#vABR1lv)~Yvjinr1J#!7Vpv?v?xE{w z`;|a0UsB7zvrDA!!ri2^HGa~ics!CzBobHC!F9M6c_^MfJ51b;DNsGT-LnItXArps z0g2@NX5wr7GudmHX6_GN4fPJ+>*$gBt0~^Ej_Ief7#!&Z5&eLC4(SIs@{H0mdjh0J zPi6ZrCt{+o3}&v|YVDdoQ9g>?w=bA~e*1~E*Mymx?w%PNsdJ5Z>vTu3e;IaWtm6;T WSyNB7T + \ No newline at end of file diff --git a/js/backend.js b/js/backend.js new file mode 100644 index 0000000..fd73b0a --- /dev/null +++ b/js/backend.js @@ -0,0 +1,10 @@ +/*global $, dotclear */ +'use strict'; + +Object.assign(dotclear.msg, dotclear.getData('activityReport')); + +$(() => { + $('#form-logs').on('submit', function () { + return window.confirm(dotclear.msg.confirm_delete); + }); +}); \ No newline at end of file diff --git a/js/main.js b/js/main.js deleted file mode 100644 index 42b9906..0000000 --- a/js/main.js +++ /dev/null @@ -1,91 +0,0 @@ -/* -- BEGIN LICENSE BLOCK ---------------------------------- - * This file is part of activityReport, a plugin for Dotclear 2. - * - * Copyright (c) 2009-2010 JC Denis and contributors - * jcdenis@gdwd.com - * - * Licensed under the GPL version 2.0 license. - * A copy of this license is available in LICENSE file or at - * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html - * -- END LICENSE BLOCK ------------------------------------*/ - -$(function(){ - /* tools */ - dotclear.jcTools = new jcToolsBox(); - - /* setting blog */ - var bForm=$('#setting-blog-form'); - if ($(bForm).attr('id')!=undefined){ - dotclear.jcTools.formFieldsetToMenu(bForm); - $(bForm).submit(function(){dotclear.jcTools.waitMessage();return true;}); - } - /* setting global */ - var gForm=$('#setting-super-form'); - if ($(gForm).attr('id')!=undefined){ - dotclear.jcTools.formFieldsetToMenu(gForm); - $(gForm).submit(function(){dotclear.jcTools.waitMessage();return true;}); - } -}); - -function jcToolsBox(){} - -jcToolsBox.prototype={ - text_wait:'Please wait', - section:'', - - formFieldsetToMenu:function(form){ - var This=this; - var section=$(form).children('fieldset[id='+This.section+']').attr('id'); - var hidden_section=$(form).children('input[name=section]').attr('value'); - var formMenu=$(form).children('p.formMenu').get(0); - if (formMenu==undefined) { - $(form).prepend($('

').addClass('formMenu')); - } - $(form).children('fieldset').each(function(){ - var Fieldset=this; - $(Fieldset).hide(); - var title=$(Fieldset).children('legend').text(); - var menu=$('').text(title).addClass('button').attr('tabindex','2').click( - function(){ - var fieldset_visible=$(form).children('fieldset:visible'); - if (fieldset_visible==undefined){ - $(Fieldset).slideDown('slow');$(form).children('input[type=submit]').show(); - }else{ - $(fieldset_visible).fadeOut('fast',function(){$(Fieldset).fadeIn('fast');$(form).children('input[type=submit]').show();}) - } - if (hidden_section==undefined){ - $(form).children('input[name=section]').remove(); - $(form).append($('').attr('name','section').attr('value',$(Fieldset).attr('id'))); - } - $('.message').fadeOut('slow',function(){$(this).slideUp('slow',function(){$(this).remove();})}); - } - ); - $(form).children('.formMenu').append(menu).append(' '); - }); - if (section!=undefined){ - $(form).children('fieldset[id='+section+']').show(); - }else{ - $(form).children('fieldset:first').show(); - } - }, - - waitMessage:function(){ - var This=this; - var content=$('div[id=content]'); - if (content!=undefined){ - $(content).hide(); - }else{ - $('input').hide();$('select').hide(); - content=$('body'); - } - var text=$('

').addClass('message').text(This.text_wait); - This.blinkItem(text); - var box=$('
').attr('style','margin: 60px auto auto;width:200px;').append(text); - $(content).before($(box)); - }, - - blinkItem:function(item){ - var This=this; - $(item).fadeOut('slow',function(){$(this).fadeIn('slow',function(){This.blinkItem(this);})}); - } -} \ 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..6384cfb --- /dev/null +++ b/locales/fr/main.lang.php @@ -0,0 +1,108 @@ + 1);\n" -msgid "Activity report" -msgstr "Rapport d'activité" - -msgid "View all logs" -msgstr "Voir tous les logs" - -msgid "Number of activities to show on dashboard:" -msgstr "Nombre d'activités à afficher sur le tableau de bord :" - -msgid "Do not show activity report" -msgstr "Ne pas afficher le rapport d'activité" - -msgid "every hour" -msgstr "toutes les heures" - -msgid "every 2 hours" -msgstr "toutes les 2 heures" - -msgid "2 times by day" -msgstr "2 fois par jour" - -msgid "every day" -msgstr "tous les jours" - -msgid "every 2 days" -msgstr "tous les 2 jours" - -msgid "every week" -msgstr "toutes les semaines" - -msgid "every 2 weeks" -msgstr "toutes les 2 semaines" - -msgid "every 4 weeks" -msgstr "toutes les 4 semaines" - -msgid "Plain text" -msgstr "Texte brut" - -msgid "HTML" -msgstr "HTML" - -msgid "Report successfully sent." -msgstr "Rapport envoyé avec succès." - -msgid "Logs successfully deleted." -msgstr "Logs effacés avec succès." - -msgid "never" -msgstr "jamais" - -msgid "on new activity" -msgstr "lors d'une nouvelle activité" - -msgid "Configure activity report for %s" -msgstr "Configurer le rapport d'activité pour %s" - -msgid "current blog" -msgstr "le blog courant" - -msgid "all blogs" -msgstr "tous les blogs" - -msgid "This server has no mail function, activityReport does not send email report." -msgstr "Ce server n'a pas de fonction d'envoie de mail, le rapport ne sera pas envoyer." - -msgid "Enable super administrator report" -msgstr "Autoriser le rapport de super administrateur" - -msgid "Enable report on this blog" -msgstr "Autoriser le rapport sur ce blog" - -msgid "Automatic cleaning of old logs:" -msgstr "Nettoyage automatique des anciennes activités:" - -msgid "Use Dotclear date formaters. ex: %B %d at %H:%M" -msgstr "Utiliser le formatage des dates de Dotclear. ex: %d %B à %H:%M" - -msgid "RSS feed" -msgstr "Flux RSS" - -msgid "Rss2 feed for activity on this blog" -msgstr "Flux Rss2 pour l'activité de ce blog" - -msgid "Atom feed" -msgstr "Flux Atom" - -msgid "Atom feed for activity on this blog" -msgstr "Flux Atom pour l'activité de ce blog" - -msgid "Send report:" -msgstr "Rapport envoyé :" - -msgid "Recipients:" -msgstr "Destinataires :" - -msgid "Separate multiple email addresses with a semicolon \";\"" -msgstr "Séparer les adresses email par un point-virgule \";\"" - -msgid "Report format:" -msgstr "Format du rapport :" - -msgid "Last report by email:" -msgstr "Dernier rapport par email :" - -msgid "Next report by email:" -msgstr "Prochain rapport par email :" - -msgid "Select blogs to add to report" -msgstr "Sélectionner les blogs à ajouter au rapport" - -msgid "Report" -msgstr "Rapport" - -msgid "Select actions by activity type to add to report" -msgstr "Sélectionner les actions par type d'activité à ajouter au rapport" - -msgid "Send report now" -msgstr "Envoyer un rapport maintenant" - -msgid "Delete all logs now" -msgstr "Effacer tous les logs maintenant" - +#: src/ActivityBehaviors.php:32 msgid "ActivityReport messages" msgstr "Messages de l'extension" -msgid "Special messages" -msgstr "Messages spéciaux" - -msgid "%s" -msgstr "%s" - +#: src/ActivityBehaviors.php:33 msgid "Actions on blog" msgstr "Actions sur le blog" -msgid "updating blog" -msgstr "Mise à jour du blog" - -msgid "Blog was updated by \"%s\"" -msgstr "Blog mis à jour par \"%s\"" - -msgid "404 error" -msgstr "Erreur 404" - -msgid "New 404 error page at \"%s\"" -msgstr "Nouvelle erreur 404 à l'adresse \"%s\"" - +#: src/ActivityBehaviors.php:34 msgid "Actions on posts" msgstr "Actions sur les billets" -msgid "post creation" -msgstr "Création de billet" - -msgid "A new post called \"%s\" was created by \"%s\" at %s" -msgstr "Un nouveau billet nommé \"%s\" a été créé par \"%s\" à l'adresse \"%s\"" - -msgid "updating post" -msgstr "Mise à jour de billet" - -msgid "Post called \"%s\" has been updated by \"%s\" at %s" -msgstr "Le billet nommé \"%s\" a été mis à jour par \"%s\" à l'adresse \"%s\"" - -msgid "post deletion" -msgstr "Suppression de billet" - -msgid "Post called \"%s\" has been deleted by \"%s\"" -msgstr "Le billet nommé \"%s\" a été supprimé par \"%s\"" - -msgid "Post protection" -msgstr "Protection des billets" - -msgid "An attempt failed on a passworded post with password \"%s\" at \"%s\"" -msgstr "Une tentative a échoué sur un billet protégé par mot de passe avec l'essai \"%s\" à l'adresse \"%s\"" - +#: src/ActivityBehaviors.php:35 msgid "Actions on comments" msgstr "Actions sur les commentaires" -msgid "comment creation" -msgstr "Création de commentaire" - -msgid "A new comment was created by \"%s\" on post \"%s\" at %s" -msgstr "Un nouveau commentaire a été créé par \"%s\" sur le billet \"%s\"" - -msgid "updating comment" -msgstr "Mise à jour de commentaire" - -msgid "Comment has been updated by \"%s\" at %s" -msgstr "Un commentaire a été mise à jour par \"%s\" à l'adresse \"%s\"" - -msgid "trackback creation" -msgstr "Création de trackback" - -msgid "A new trackback to \"%\" at \"%s\" was created on post \"%s\" at %s" -msgstr "Un nouveau rétrolien vers \"%s\" a été créé sur le billet \"%s\" à l'adresse \"%s\"" - +#: src/ActivityBehaviors.php:36 msgid "Actions on categories" msgstr "Actions sur les catégories" -msgid "category creation" -msgstr "Création de catégorie" - -msgid "A new category called \"%s\" was created by \"%s\" at %s" -msgstr "Un nouvelle catégorie nommée \"%s\" a été créé par \"%s\" à l'adresse \"%s\"" - -msgid "updating category" -msgstr "Mise à jour de catégorie" - -msgid "Category called \"%s\" has been updated by \"%s\" at %s" -msgstr "La catégorie nommée \"%s\" a été mise à jour par \"%s\" à l'adresse \"%s\"" - +#: src/ActivityBehaviors.php:37 msgid "Actions on users" msgstr "Actions sur les utilisateurs" +#: src/ActivityBehaviors.php:43 +msgid "Special messages" +msgstr "Messages spéciaux" + +#: src/ActivityBehaviors.php:44 +msgid "%s" +msgstr "%s" + +#: src/ActivityBehaviors.php:54 +msgid "updating blog" +msgstr "Mise à jour du blog" + +#: src/ActivityBehaviors.php:55 +msgid "Blog was updated by \"%s\"" +msgstr "Blog mis à jour par \"%s\"" + +#: src/ActivityBehaviors.php:63 +msgid "404 error" +msgstr "Erreur 404" + +#: src/ActivityBehaviors.php:64 +msgid "New 404 error page at \"%s\"" +msgstr "Nouvelle erreur 404 à l'adresse \"%s\"" + +#: src/ActivityBehaviors.php:74 +msgid "post creation" +msgstr "Création de billet" + +#: src/ActivityBehaviors.php:75 +msgid "A new post called \"%s\" was created by \"%s\" at %s" +msgstr "Un nouveau billet nommé \"%s\" a été créé par \"%s\" à l'adresse \"%s\"" + +#: src/ActivityBehaviors.php:94 +msgid "updating post" +msgstr "Mise à jour de billet" + +#: src/ActivityBehaviors.php:95 +msgid "Post called \"%s\" has been updated by \"%s\" at %s" +msgstr "Le billet nommé \"%s\" a été mis à jour par \"%s\" à l'adresse \"%s\"" + +#: src/ActivityBehaviors.php:103 +msgid "post deletion" +msgstr "Suppression de billet" + +#: src/ActivityBehaviors.php:104 +msgid "Post called \"%s\" has been deleted by \"%s\"" +msgstr "Le billet nommé \"%s\" a été supprimé par \"%s\"" + +#: src/ActivityBehaviors.php:113 +msgid "Post protection" +msgstr "Protection des billets" + +#: src/ActivityBehaviors.php:114 +msgid "An attempt failed on a passworded post with password \"%s\" at \"%s\"" +msgstr "Une tentative a échoué sur un billet protégé par mot de passe avec l'essai \"%s\" à l'adresse \"%s\"" + +#: src/ActivityBehaviors.php:124 +msgid "comment creation" +msgstr "Création de commentaire" + +#: src/ActivityBehaviors.php:125 +msgid "A new comment was created by \"%s\" on post \"%s\" at %s" +msgstr "Un nouveau commentaire a été créé par \"%s\" sur le billet \"%s\"" + +#: src/ActivityBehaviors.php:134 +msgid "updating comment" +msgstr "Mise à jour de commentaire" + +#: src/ActivityBehaviors.php:135 +msgid "Comment has been updated by \"%s\" at %s" +msgstr "Un commentaire a été mise à jour par \"%s\" à l'adresse \"%s\"" + +#: src/ActivityBehaviors.php:147 +msgid "trackback creation" +msgstr "Création de trackback" + +#: src/ActivityBehaviors.php:148 +msgid "A new trackback to \"%\" at \"%s\" was created on post \"%s\" at %s" +msgstr "Un nouveau rétrolien vers \"%s\" a été créé sur le billet \"%s\" à l'adresse \"%s\"" + +#: src/ActivityBehaviors.php:156 +msgid "category creation" +msgstr "Création de catégorie" + +#: src/ActivityBehaviors.php:157 +msgid "A new category called \"%s\" was created by \"%s\" at %s" +msgstr "Un nouvelle catégorie nommée \"%s\" a été créé par \"%s\" à l'adresse \"%s\"" + +#: src/ActivityBehaviors.php:165 +msgid "updating category" +msgstr "Mise à jour de catégorie" + +#: src/ActivityBehaviors.php:166 +msgid "Category called \"%s\" has been updated by \"%s\" at %s" +msgstr "La catégorie nommée \"%s\" a été mise à jour par \"%s\" à l'adresse \"%s\"" + +#: src/ActivityBehaviors.php:176 msgid "user creation" msgstr "Création d'utilisateur" +#: src/ActivityBehaviors.php:177 msgid "A new user named \"%s\" was created by \"%s\"" msgstr "Un nouvel utilisateur a été ajouté par \"%s\"" +#: src/ActivityBehaviors.php:185 msgid "updating user" msgstr "Mise à jour d'utilisateur" +#: src/ActivityBehaviors.php:186 msgid "User named \"%s\" has been updated by \"%s\"" msgstr "L'utilisateur \"%s\" a été mis à jour par \"%s\"" +#: src/ActivityBehaviors.php:217 msgid "user deletion" msgstr "Suppression d'utilisateur" +#: src/ActivityBehaviors.php:218 msgid "User named \"%s\" has been deleted by \"%\"" msgstr "L'utilisateur nommé \"%s\" a été supprimé par \"%s\"" +#: src/ActivityBehaviors.php:235 +msgid "HTML" +msgstr "HTML" + +#: src/ActivityBehaviors.php:248 +#: src/ActivityReport.php:341 +msgid "Activity report" +msgstr "Rapport d'activité" + +#: src/ActivityReport.php:331 msgid "An error occured when parsing report." msgstr "Une erreur est survenue lors de la compilation du rapport." +#: src/ActivityReport.php:347 msgid "You received a message from your blog's activity report module." msgstr "Vous recevez un message du module de rapport d'activité de votre blog." +#: src/ActivityReport.php:356 msgid "Period from %s to %s" msgstr "Période du %s au %s" +#: src/ActivityReport.php:404 msgid "Activity report deletes some old logs." msgstr "L'extension a automatiquement effacé des anciennes activités." -msgid "Blog activity report" -msgstr "Rapport d'activité du blog" +#: src/ActivityReport.php:661 +msgid "Blog \"%s\" activity report" +msgstr "Rapport d'activité du blog \"%s\"" -msgid "Logs" -msgstr "Enregistrements" +#: src/Backend.php:126 +msgid "View all logs" +msgstr "Voir tous les logs" +#: src/Backend.php:142 +msgid "Number of activities to show on dashboard:" +msgstr "Nombre d'activités à afficher sur le tableau de bord :" + +#: src/Backend.php:144 +msgid "Do not show activity report" +msgstr "Ne pas afficher le rapport d'activité" + +#: src/Backend.php:170 +#: src/ManageList.php:48 +msgid "Group" +msgstr "Groupe" + +#: src/Backend.php:177 +msgid "logs per page" +msgstr "enregistrements par page" + +#: src/Combo.php:45 +msgid "every hour" +msgstr "toutes les heures" + +#: src/Combo.php:46 +msgid "every 2 hours" +msgstr "toutes les 2 heures" + +#: src/Combo.php:47 +msgid "2 times by day" +msgstr "2 fois par jour" + +#: src/Combo.php:48 +msgid "every day" +msgstr "tous les jours" + +#: src/Combo.php:49 +msgid "every 2 days" +msgstr "tous les 2 jours" + +#: src/Combo.php:50 +msgid "every week" +msgstr "toutes les semaines" + +#: src/Combo.php:68 +msgid "every 2 weeks" +msgstr "toutes les 2 semaines" + +#: src/Combo.php:69 +msgid "every 4 weeks" +msgstr "toutes les 4 semaines" + +#: src/Config.php:85 +msgid "Report successfully sent." +msgstr "Rapport envoyé avec succès." + +#: src/Config.php:92 +msgid "Logs successfully deleted." +msgstr "Logs effacés avec succès." + +#: src/Config.php:116 +msgid "never" +msgstr "jamais" + +#: src/Config.php:117 +msgid "on new activity" +msgstr "lors d'une nouvelle activité" + +#: src/Config.php:133 +msgid "This server has no mail function, activityReport does not send email report." +msgstr "Ce server n'a pas de fonction d'envoie de mail, le rapport ne sera pas envoyer." + +#: src/Config.php:140 +msgid "Mail report" +msgstr "Rapport par email" + +#: src/Config.php:142 +msgid "Send report:" +msgstr "Rapport envoyé :" + +#: src/Config.php:146 +msgid "Recipients:" +msgstr "Destinataires :" + +#: src/Config.php:149 +msgid "Separate multiple email addresses with a semicolon \";\"" +msgstr "Séparer les adresses email par un point-virgule \";\"" + +#: src/Config.php:150 +msgid "Leave it empty to disable mail report." +msgstr "Laisser vide pour désactiver le rapport par email." + +#: src/Config.php:155 +msgid "Use Dotclear date formaters. ex: %B %d at %H:%M" +msgstr "Utiliser le formatage des dates de Dotclear. ex: %d %B à %H:%M" + +#: src/Config.php:157 +msgid "Report format:" +msgstr "Format du rapport :" + +#: src/Config.php:162 +msgid "Last report by email:" +msgstr "Dernier rapport par email :" + +#: src/Config.php:163 +msgid "Next report by email:" +msgstr "Prochain rapport par email :" + +#: src/Config.php:167 +msgid "Feeds" +msgstr "Flux" + +#: src/Config.php:170 +msgid "Enable activity feed" +msgstr "Activer le flux d'activités" + +#: src/Config.php:174 +msgid "RSS feed" +msgstr "Flux RSS" + +#: src/Config.php:177 +msgid "Rss2 activities feed" +msgstr "Flux RSS2 d'activités" + +#: src/Config.php:178 +msgid "Atom feed" +msgstr "Flux Atom" + +#: src/Config.php:181 +msgid "Atom activities feed" +msgstr "Flux Atom d'activités" + +#: src/Config.php:188 +msgid "Activities" +msgstr "Activités" + +#: src/Config.php:189 +msgid "Select actions by activity type to add to report" +msgstr "Sélectionner les actions par type d'activité à ajouter au rapport" + +#: src/Config.php:214 +msgid "Automatic cleaning of old logs:" +msgstr "Nettoyage automatique des anciennes activités:" + +#: src/Config.php:219 +msgid "Send report now" +msgstr "Envoyer un rapport maintenant" + +#: src/Config.php:223 +msgid "Delete all logs now" +msgstr "Effacer tous les logs maintenant" + +#: src/Format.php:68 +msgid "Plain text" +msgstr "Texte brut" + +#: src/Manage.php:58 +msgid "Logs successfully deleted" +msgstr "Enregistrements effacés avec succès" + +#: src/Manage.php:91 +msgid "Are you sure you want to delete logs?" +msgstr "Êtes-vous sure de vouloir effacer les enregistrements ?" + +#: src/Manage.php:114 +msgid "Delete all aticivity logs" +msgstr "Effacer tous les enregistrements d'activités" + +#: src/Manage.php:115 +msgid "Delete all allready reported logs" +msgstr "Effacer les enregistrements d'activités déjà envoyé par email" + +#: src/ManageList.php:33 +msgid "No log matches the filter" +msgstr "Aucun enregistrement ne correspond au filtre" + +#: src/ManageList.php:35 msgid "No log" msgstr "Pas d'enregistrement" +#: src/ManageList.php:43 +msgid "List of %s logs matching the filter." +msgstr "Liste des %s logs correspondants au filtre." + +#: src/ManageList.php:44 +msgid "List of %s logs." +msgstr "Listes des %s enregistrements" + +#: src/ManageList.php:50 msgid "Message" msgstr "Message" -msgid "Activity report module" -msgstr "Module de rapport d'activité" +#: src/ManageList.php:79 +msgid "undefined" +msgstr "inconnu" + +#: src/ManageList.php:85 +msgid "reported" +msgstr "envoyé" + +msgid "Activity log" +msgstr "Journal d'activité" + +msgid "Log and receive your blog activity by email, feed, or on dashboard" +msgstr "Enregistrer et recevoir l'activité de votre blog par mail, flux ou sur le tableau de bord" diff --git a/src/Action.php b/src/Action.php new file mode 100644 index 0000000..5225f54 --- /dev/null +++ b/src/Action.php @@ -0,0 +1,45 @@ +addBehavior($behavior, $function); + } + } +} diff --git a/src/ActivityBehaviors.php b/src/ActivityBehaviors.php index f283658..6778273 100644 --- a/src/ActivityBehaviors.php +++ b/src/ActivityBehaviors.php @@ -10,393 +10,470 @@ * @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); -if (!defined('ACTIVITY_REPORT_V2')) { - return null; -} +namespace Dotclear\Plugin\activityReport; -class activityReportBehaviors +use ArrayObject; +use cursor; +use dcBlog; +use dcCore; +use dcRecord; +use dcUtils; +use Dotclear\Helper\Network\Http; + +/** + * Register default activities and export mail formats. + */ +class ActivityBehaviors { - public static function registerBehaviors() + public static function register(): void { - // ActivityReport plugin - dcCore::app()->activityReport->addGroup(basename(dirname(__DIR__)), __('ActivityReport messages')); + $my = new Group(My::id(), __('ActivityReport messages')); + $blog = new Group('blog', __('Actions on blog')); + $post = new Group('post', __('Actions on posts')); + $comment = new Group('comment', __('Actions on comments')); + $category = new Group('category', __('Actions on categories')); + $user = new Group('user', __('Actions on users')); - dcCore::app()->activityReport->addAction( - basename(dirname(__DIR__)), + // ActivityReport plugin + + $my->add(new Action( 'message', __('Special messages'), __('%s'), 'messageActivityReport', - ['activityReportBehaviors', 'messageActivityReport'] - ); - - // Blog - dcCore::app()->activityReport->addGroup('blog', __('Actions on blog')); + [self::class, 'messageActivityReport'] + )); // Not use as it is global : BEHAVIOR adminAfterBlogCreate in admin/blog.php // from BEHAVIOR adminAfterBlogUpdate in admin/blog_pref.php - dcCore::app()->activityReport->addAction( - 'blog', + $blog->add(new Action( 'update', __('updating blog'), __('Blog was updated by "%s"'), 'adminAfterBlogUpdate', - ['activityReportBehaviors', 'blogUpdate'] - ); + [self::class, 'blogUpdate'] + )); // from BEHAVIOR publicHeadContent in template - dcCore::app()->activityReport->addAction( - 'blog', + $blog->add(new Action( 'p404', __('404 error'), __('New 404 error page at "%s"'), 'publicHeadContent', - ['activityReportBehaviors', 'blogP404'] - ); - - // Post - dcCore::app()->activityReport->addGroup('post', __('Actions on posts')); + [self::class, 'blogP404'] + )); // from BEHAVIOR coreAfterPostCreate in inc/core/class.dc.blog.php (DC 2.2) // duplicate adminAfterPostCreate in admin/post.php // duplicate adminAfterPostCreate in admin/services.php - dcCore::app()->activityReport->addAction( - 'post', + $post->add(new Action( 'create', __('post creation'), __('A new post called "%s" was created by "%s" at %s'), 'adminAfterPostCreate', - ['activityReportBehaviors', 'postCreate'] - ); + [self::class, 'postCreate'] + )); // Plugin contribute // from BEHAVIOR publicAfterPostCreate in plugins/contribute/_public.php - dcCore::app()->activityReport->addAction( - 'post', + $post->add(new Action( 'create', __('post creation'), __('A new post called "%s" was created by "%s" at %s'), 'publicAfterPostCreate', - ['activityReportBehaviors', 'postCreate'] - ); + [self::class, 'postCreate'] + )); // from BEHAVIOR coreAfterPostUpdate in inc/core/class.dc.blog.php (DC2.2) // duplicate adminAfterPostUpdate in admin/post.php - dcCore::app()->activityReport->addAction( - 'post', + $post->add(new Action( 'update', __('updating post'), __('Post called "%s" has been updated by "%s" at %s'), 'adminAfterPostUpdate', - ['activityReportBehaviors', 'postUpdate'] - ); + [self::class, 'postUpdate'] + )); // from BEHAVIOR adminBeforePostDelete in admin/post.php - dcCore::app()->activityReport->addAction( - 'post', + $post->add(new Action( 'delete', __('post deletion'), __('Post called "%s" has been deleted by "%s"'), 'adminBeforePostDelete', - ['activityReportBehaviors', 'postDelete'] - ); + [self::class, 'postDelete'] + )); // Wrong attempt on passworded enrty // from BEHAVIOR urlHandlerServeDocument in inc/public/lib.urlhandlers.php - dcCore::app()->activityReport->addAction( - 'post', + $post->add(new Action( 'protection', __('Post protection'), __('An attempt failed on a passworded post with password "%s" at "%s"'), 'urlHandlerServeDocument', - ['activityReportBehaviors', 'postPasswordAttempt'] - ); - - // Comment - dcCore::app()->activityReport->addGroup('comment', __('Actions on comments')); + [self::class, 'postPasswordAttempt'] + )); // from BEHAVIOR coreAfterCommentCreate in inc/core/class.dc.blog.php // duplicate adminAfterCommentCreate in admin/comment.php // duplicate publicAfterCommentCreate in inc/public/lib.urlhandlers.php - dcCore::app()->activityReport->addAction( - 'comment', + $comment->add(new Action( 'create', __('comment creation'), __('A new comment was created by "%s" on post "%s" at %s'), 'coreAfterCommentCreate', - ['activityReportBehaviors', 'commentCreate'] - ); + [self::class, 'commentCreate'] + )); // from BEHAVIOR coreAfterCommentUpdate in inc/core/class.dc.blog.php // duplicate adminAfterCommentUpdate in admin/comment.php - dcCore::app()->activityReport->addAction( - 'comment', + $comment->add(new Action( 'update', __('updating comment'), __('Comment has been updated by "%s" at %s'), 'coreAfterCommentUpdate', - ['activityReportBehaviors', 'commentUpdate'] - ); + [self::class, 'commentUpdate'] + )); // Missing coreBeforeCommentDelete in inc/core/class.dc.blog.php // Missing adminBeforeCommentDelete in admin/comment.php // from BEHAVIOR coreAfterCommentCreate in inc/core/class.dc.blog.php // duplicate publicAfterTrackbackCreate in inc/core/class.dc.trackback.php - dcCore::app()->activityReport->addAction( - 'comment', + $comment->add(new Action( 'trackback', __('trackback creation'), __('A new trackback to "%" at "%s" was created on post "%s" at %s'), 'coreAfterCommentCreate', - ['activityReportBehaviors', 'trackbackCreate'] - ); - - // Category - dcCore::app()->activityReport->addGroup('category', __('Actions on categories')); + [self::class, 'trackbackCreate'] + )); // from BEHAVIOR adminAfterCategoryCreate in admin/category.php - dcCore::app()->activityReport->addAction( - 'category', + $category->add(new Action( 'create', __('category creation'), __('A new category called "%s" was created by "%s" at %s'), 'adminAfterCategoryCreate', - ['activityReportBehaviors', 'categoryCreate'] - ); + [self::class, 'categoryCreate'] + )); // from BEHAVIOR adminAfterCategoryUpdate in admin/category.php - dcCore::app()->activityReport->addAction( - 'category', + $category->add(new Action( 'update', __('updating category'), __('Category called "%s" has been updated by "%s" at %s'), 'adminAfterCategoryUpdate', - ['activityReportBehaviors', 'categoryUpdate'] - ); + [self::class, 'categoryUpdate'] + )); // Missing adminBeforeCategoryDelete in admin/category.php - // User - dcCore::app()->activityReport->addGroup('user', __('Actions on users')); - // from BEHAVIOR adminAfterUserCreate in admin/user.php - dcCore::app()->activityReport->addAction( - 'user', + $user->add(new Action( 'create', __('user creation'), __('A new user named "%s" was created by "%s"'), 'adminAfterUserCreate', - ['activityReportBehaviors', 'userCreate'] - ); + [self::class, 'userCreate'] + )); // from BEHAVIOR adminAfterUserUpdated in admin/user.php - dcCore::app()->activityReport->addAction( - 'user', + $user->add(new Action( 'update', __('updating user'), __('User named "%s" has been updated by "%s"'), 'adminAfterUserUpdate', - ['activityReportBehaviors', 'userUpdate'] - ); + [self::class, 'userUpdate'] + )); + + // from BEHAVIOR adminAfterUserProfileUpdate in admin/preferences.php + $user->add(new Action( + 'preference', + __('updating user preference'), + __('User named "%s" preference has been updated by "%s"'), + 'adminAfterUserProfileUpdate', + [self::class, 'userPreference'] + )); + $user->add(new Action( + 'preference', + __('updating user preference'), + __('User named "%s" preference has been updated by "%s"'), + 'adminAfterUserOptionsUpdate', + [self::class, 'userPreference'] + )); + $user->add(new Action( + 'preference', + __('updating user preference'), + __('User named "%s" preference has been updated by "%s"'), + 'adminAfterDashboardOptionsUpdate', + [self::class, 'userPreference'] + )); // from BEHAVIOR adminBeforeUserDelete in admin/users.php - dcCore::app()->activityReport->addAction( - 'user', + $user->add(new Action( 'delete', __('user deletion'), __('User named "%s" has been deleted by "%"'), 'adminBeforeUserDelete', - ['activityReportBehaviors', 'userDelete'] - ); + [self::class, 'userDelete'] + )); + + ActivityReport::instance()->groups + ->add($my) + ->add($blog) + ->add($post) + ->add($comment) + ->add($category) + ->add($user); + + // Add default email report formats + ActivityReport::instance()->formats + ->add(new Format('plain', [])) // plain text format is build with default values + ->add(new Format('html', [ + 'name' => __('HTML'), + 'blog_title' => '

%TEXT%

', + 'group_title' => '

%TEXT%

', + 'group_open' => '
    ', + 'group_close' => '
', + 'action' => '
  • %TIME%
    %TEXT%
  • ', + 'error' => '

    %TEXT%

    ', + 'period_title' => '

    %TEXT%

    ', + 'period_open' => '
      ', + 'period_close' => '
    ', + 'info' => '
  • %TEXT%
  • ', + 'page' => '' . "\n" . + '' . "\n" . + '' . __('Activity report') . '' . + '' . + '' . + '
    %PERIOD%
    %TEXT%
    ' . + '

    Powered by activityReport

    ' . + '', + ])); } - public static function messageActivityReport($message) + public static function messageActivityReport(string $message): void { $logs = [$message]; - dcCore::app()->activityReport->addLog(basename(dirname(__DIR__)), 'message', $logs); + ActivityReport::instance()->addLog(My::id(), 'message', $logs); } - public static function blogUpdate($cur, $blog_id) + public static function blogUpdate(cursor $cur, string $blog_id): void { - $logs = [dcCore::app()->auth->getInfo('user_cn')]; - dcCore::app()->activityReport->addLog('blog', 'update', $logs); + $logs = [(string) dcCore::app()->auth?->getInfo('user_cn')]; + ActivityReport::instance()->addLog('blog', 'update', $logs); } - public static function blogP404() + public static function blogP404(): void { if (dcCore::app()->url->type != '404') { - return null; + return; } - $logs = [dcCore::app()->blog->url . $_SERVER['QUERY_STRING']]; - dcCore::app()->activityReport->addLog('blog', 'p404', $logs); + $logs = [(string) dcCore::app()->blog?->url . $_SERVER['QUERY_STRING']]; + ActivityReport::instance()->addLog('blog', 'p404', $logs); } - public static function postCreate($cur, $post_id) + public static function postCreate(cursor $cur, int $post_id): void { - $type = $cur->post_type ? $cur->post_type : 'post'; - $post_url = dcCore::app()->blog->getPostURL('', $cur->post_dt, $cur->post_title, $post_id); + $type = $cur->getField('post_type') ?? 'post'; + $post_url = dcCore::app()->blog?->getPostURL('', $cur->getField('post_dt'), $cur->getField('post_title'), $post_id); $logs = [ - $cur->post_title, - dcCore::app()->auth->getInfo('user_cn'), - dcCore::app()->blog->url . dcCore::app()->url->getBase($type) . '/' . $post_url, + (string) $cur->getField('post_title'), + (string) dcCore::app()->auth?->getInfo('user_cn'), + (string) dcCore::app()->blog?->url . dcCore::app()->url->getBase($type) . '/' . $post_url, ]; - dcCore::app()->activityReport->addLog('post', 'create', $logs); + ActivityReport::instance()->addLog('post', 'create', $logs); } - public static function postUpdate($cur, $post_id) + public static function postUpdate(cursor $cur, int $post_id): void { - $type = $cur->post_type ? $cur->post_type : 'post'; - $post_url = dcCore::app()->blog->getPostURL('', $cur->post_dt, $cur->post_title, $post_id); + $type = $cur->getField('post_type') ?? 'post'; + $post_url = dcCore::app()->blog?->getPostURL('', $cur->getField('post_dt'), $cur->getField('post_title'), $post_id); $logs = [ - $cur->post_title, - dcCore::app()->auth->getInfo('user_cn'), - dcCore::app()->blog->url . dcCore::app()->url->getBase($type) . '/' . $post_url, + (string) $cur->getField('post_title'), + (string) dcCore::app()->auth?->getInfo('user_cn'), + (string) dcCore::app()->blog?->url . dcCore::app()->url->getBase($type) . '/' . $post_url, ]; - dcCore::app()->activityReport->addLog('post', 'update', $logs); + ActivityReport::instance()->addLog('post', 'update', $logs); } - public static function postDelete($post_id) + public static function postDelete(int $post_id): void { - $posts = dcCore::app()->blog->getPosts(['post_id' => $post_id, 'limit' => 1]); - $logs = [ - $posts->post_title, - dcCore::app()->auth->getInfo('user_cn'), + $posts = dcCore::app()->blog?->getPosts(['post_id' => $post_id, 'limit' => 1]); + if (!$posts || $posts->isEmpty()) { + return; + } + $logs = [ + (string) $posts->f('post_title'), + (string) dcCore::app()->auth?->getInfo('user_cn'), ]; - dcCore::app()->activityReport->addLog('post', 'delete', $logs); + ActivityReport::instance()->addLog('post', 'delete', $logs); } - public static function postPasswordAttempt($result) + public static function postPasswordAttempt(ArrayObject $result): void { if ($result['tpl'] != 'password-form.html' || empty($_POST['password'])) { - return null; + return; } $logs = [ $_POST['password'], - http::getSelfURI(), + Http::getSelfURI(), ]; - dcCore::app()->activityReport->addLog('post', 'protection', $logs); + ActivityReport::instance()->addLog('post', 'protection', $logs); } - public static function commentCreate($blog, $cur) + public static function commentCreate(dcBlog $blog, cursor $cur): void { - if ($cur->comment_trackback) { - return null; + if ($cur->getField('comment_trackback')) { + return; } - $posts = dcCore::app()->blog->getPosts(['post_id' => $cur->post_id, 'limit' => 1]); - $logs = [ - $cur->comment_author, - $posts->post_title, - dcCore::app()->blog->url . dcCore::app()->url->getBase($posts->post_type) . - '/' . $posts->post_url . '#c' . $cur->comment_id, - ]; - dcCore::app()->activityReport->addLog('comment', 'create', $logs); - } - - public static function commentUpdate($blog, $cur, $old) - { - $posts = dcCore::app()->blog->getPosts(['post_id' => $old->post_id, 'limit' => 1]); - - $logs = [ - dcCore::app()->auth->getInfo('user_cn'), - $posts->post_title, - dcCore::app()->blog->url . dcCore::app()->url->getBase($posts->post_type) . - '/' . $posts->post_url . '#c' . $old->comment_id, - ]; - dcCore::app()->activityReport->addLog('comment', 'update', $logs); - } - - public static function trackbackCreate($cur, $comment_id) - { - // From blog args are $blog, $cur #thks to bruno - $c = $cur instanceof dcBlog ? $comment_id : $cur; - if (!$c->comment_trackback || !$c->comment_site) { - return null; - } - $posts = dcCore::app()->blog->getPosts( - ['post_id' => $c->post_id, 'no_content' => true, 'limit' => 1] + $posts = dcCore::app()->blog?->getPosts( + ['post_id' => $cur->getField('post_id'), 'limit' => 1, 'post_type' => ''] ); - if ($posts->isEmpty()) { - return null; + if (!$posts || $posts->isEmpty()) { + return; } + $logs = [ - $c->comment_author, - $c->comment_site, - $posts->post_title, - dcCore::app()->blog->url . dcCore::app()->url->getBase($posts->post_type) . - '/' . $posts->post_url, + (string) $cur->getField('comment_author'), + (string) $posts->f('post_title'), + (string) dcCore::app()->blog?->url . dcCore::app()->url->getBase((string) $posts->f('post_type')) . + '/' . $posts->f('post_url') . '#c' . $cur->getField('comment_id'), ]; - dcCore::app()->activityReport->addLog('comment', 'trackback', $logs); + ActivityReport::instance()->addLog('comment', 'create', $logs); } - public static function categoryCreate($cur, $cat_id) + public static function commentUpdate(dcBlog $blog, cursor $cur, dcRecord $old): void + { + $posts = dcCore::app()->blog?->getPosts( + ['post_id' => $old->f('post_id'), 'limit' => 1] + ); + if (!$posts || $posts->isEmpty()) { + return; + } + + $logs = [ + (string) dcCore::app()->auth?->getInfo('user_cn'), + (string) $posts->f('post_title'), + (string) dcCore::app()->blog?->url . dcCore::app()->url->getBase((string) $posts->f('post_type')) . + '/' . $posts->f('post_url') . '#c' . $old->f('comment_id'), + ]; + ActivityReport::instance()->addLog('comment', 'update', $logs); + } + + public static function trackbackCreate(dcBlog $blog, cursor $cur): void + { + if (!$cur->getField('comment_trackback')) { + return; + } + + $posts = dcCore::app()->blog?->getPosts( + ['post_id' => $cur->getField('post_id'), 'no_content' => true, 'limit' => 1] + ); + if (!$posts || $posts->isEmpty()) { + return; + } + + $logs = [ + (string) $cur->getField('comment_author'), + (string) $cur->getField('comment_site'), + (string) $posts->f('post_title'), + (string) dcCore::app()->blog?->url . dcCore::app()->url->getBase($posts->f('post_type')) . + '/' . $posts->f('post_url'), + ]; + ActivityReport::instance()->addLog('comment', 'trackback', $logs); + } + + public static function categoryCreate(cursor $cur, int $cat_id): void { $logs = [ - $cur->cat_title, - dcCore::app()->auth->getInfo('user_cn'), - dcCore::app()->blog->url . dcCore::app()->url->getBase('category') . '/' . $cur->cat_url, + (string) $cur->getField('cat_title'), + (string) dcCore::app()->auth?->getInfo('user_cn'), + (string) dcCore::app()->blog?->url . dcCore::app()->url->getBase('category') . '/' . $cur->getField('cat_url'), ]; - dcCore::app()->activityReport->addLog('category', 'create', $logs); + ActivityReport::instance()->addLog('category', 'create', $logs); } - public static function categoryUpdate($cur, $cat_id) + public static function categoryUpdate(cursor $cur, int $cat_id): void { $logs = [ - $cur->cat_title, - dcCore::app()->auth->getInfo('user_cn'), - dcCore::app()->blog->url . dcCore::app()->url->getBase('category') . '/' . $cur->cat_url, + (string) $cur->getField('cat_title'), + (string) dcCore::app()->auth?->getInfo('user_cn'), + (string) dcCore::app()->blog?->url . dcCore::app()->url->getBase('category') . '/' . $cur->getField('cat_url'), ]; - dcCore::app()->activityReport->addLog('category', 'update', $logs); + ActivityReport::instance()->addLog('category', 'update', $logs); } - public static function userCreate($cur, $user_id) + public static function userCreate(cursor $cur, string $user_id): void { $user_cn = dcUtils::getUserCN( - $cur->user_id, - $cur->user_name, - $cur->user_firstname, - $cur->user_displayname + $cur->getField('user_id'), + $cur->getField('user_name'), + $cur->getField('user_firstname'), + $cur->getField('user_displayname') ); $logs = [ - $user_cn, - dcCore::app()->auth->getInfo('user_cn'), + (string) $user_cn, + (string) dcCore::app()->auth?->getInfo('user_cn'), ]; - dcCore::app()->activityReport->addLog('user', 'create', $logs); + ActivityReport::instance()->addLog('user', 'create', $logs); } - public static function usertUpdate($cur, $user_id) + public static function userUpdate(cursor $cur, string $user_id): void { $user_cn = dcUtils::getUserCN( - $cur->user_id, - $cur->user_name, - $cur->user_firstname, - $cur->user_displayname + $cur->getField('user_id'), + $cur->getField('user_name'), + $cur->getField('user_firstname'), + $cur->getField('user_displayname') ); $logs = [ - $user_cn, - dcCore::app()->auth->getInfo('user_cn'), + (string) $user_cn, + (string) dcCore::app()->auth?->getInfo('user_cn'), ]; - dcCore::app()->activityReport->addLog('user', 'update', $logs); + ActivityReport::instance()->addLog('user', 'update', $logs); } - public static function userDelete($user_id) + public static function userPreference(cursor $cur, string $user_id): void + { + $user_cn = dcUtils::getUserCN( + $cur->getField('user_id'), + $cur->getField('user_name'), + $cur->getField('user_firstname'), + $cur->getField('user_displayname') + ); + $logs = [ + (string) $user_cn, + (string) dcCore::app()->auth?->getInfo('user_cn'), + ]; + ActivityReport::instance()->addLog('user', 'preference', $logs); + } + + public static function userDelete(string $user_id): void { $users = dcCore::app()->getUser($user_id); $user_cn = dcUtils::getUserCN( - $users->user_id, - $users->user_name, - $users->user_firstname, - $users->user_displayname + $users->f('user_id'), + $users->f('user_name'), + $users->f('user_firstname'), + $users->f('user_displayname') ); $logs = [ - $user_cn, - dcCore::app()->auth->getInfo('user_cn'), + (string) $user_cn, + (string) dcCore::app()->auth?->getInfo('user_cn'), ]; - dcCore::app()->activityReport->addLog('user', 'delete', $logs); + ActivityReport::instance()->addLog('user', 'delete', $logs); } } diff --git a/src/ActivityReport.php b/src/ActivityReport.php index 005cf55..030d062 100644 --- a/src/ActivityReport.php +++ b/src/ActivityReport.php @@ -10,287 +10,257 @@ * @copyright Jean-Christian Denis * @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html */ -if (!defined('DC_RC_PATH')) { - return; -} +declare(strict_types=1); -class activityReport +namespace Dotclear\Plugin\activityReport; + +use ArrayObject; +use dcAuth; +use dcBlog; +use dcCore; +use dcRecord; +use Dotclear\Database\Statement\{ + DeleteStatement, + JoinStatement, + SelectStatement, + UpdateStatement +}; +use Dotclear\Helper\Crypt; +use Dotclear\Helper\Date; +use Dotclear\Helper\File\{ + Files, + Path +}; +use Dotclear\Helper\Network\Mail\Mail; +use Dotclear\Helper\Text; +use Exception; + +/** + * Activity report main class. + */ +class ActivityReport { - public $con; + /** @var int activity marked as pending mail report */ + public const STATUS_PENDING = 0; - private $ns = null; - private $_global = 0; - private $blog = null; - private $groups = []; - private $settings = []; - private $lock_blog = null; - private $lock_global = null; + /** @var int activity marked as reported by mail */ + public const STATUS_REPORTED = 1; - public function __construct($ns = null) + /** @var string $type Activity report type (by default activityReport) */ + public readonly string $type; + + /** @var Settings $settings Activity report settings for current blog */ + public readonly Settings $settings; + + /** @var Groups $groups Groups of actions */ + public readonly Groups $groups; + + /** @var Formats $formats Export available formats */ + public readonly Formats $formats; + + /** @var ActivityReport $instance ActivityReport instance */ + private static $instance; + + private $lock = null; + + /** + * Constructor sets activity main type. + * + * @param string $type The activity report type + */ + public function __construct(string $type = null) { - $this->con = dcCore::app()->con; - $this->blog = dcCore::app()->con->escape(dcCore::app()->blog->id); - $this->ns = dcCore::app()->con->escape($ns ?? basename(dirname(__DIR__))); - - $this->getSettings(); + $this->type = $type ?? My::id(); + $this->settings = new Settings(); + $this->groups = new Groups(); + $this->formats = new Formats(); # Check if some logs are too olds $this->obsoleteLogs(); } - public function setGlobal() + /** + * Get singleton instance. + * + * @return ActivityReport ActivityReport instance + */ + public static function instance(): ActivityReport { - $this->_global = 1; - } - - public function unsetGlobal() - { - $this->_global = 0; - } - - public function getGroups($group = null, $action = null) - { - if ($action !== null) { - return $this->groups[$group]['actions'][$action] ?? null; - } elseif ($group !== null) { - return $this->groups[$group] ?? null; + if (!is_a(self::$instance, ActivityReport::class)) { + self::$instance = new ActivityReport(); } - return $this->groups; + return self::$instance; } - public function addGroup($group, $title) + /** + * Get logs record. + * + * @param null|ArrayObject $params The query params + * @param bool $count_only Count only + * @param null|SelectStatement $ext_sql The sql select statement + * + * @return null|dcRecord The logs record + */ + public function getLogs(ArrayObject $params = null, bool $count_only = false, ?SelectStatement $ext_sql = null): ?dcRecord { - $this->groups[$group] = [ - 'title' => $title, - 'actions' => [], - ]; - - return true; - } - - public function addAction($group, $action, $title, $msg, $behavior, $function) - { - if (!isset($this->groups[$group])) { - return false; + if (null === $params) { + $params = new ArrayObject(); } - $this->groups[$group]['actions'][$action] = [ - 'title' => $title, - 'msg' => $msg, - ]; - dcCore::app()->addBehavior($behavior, $function); + $sql = $ext_sql ? clone $ext_sql : new SelectStatement(); - return true; - } - - private function getSettings() - { - $settings = [ - 'active' => false, - 'obsolete' => 2419200, - 'interval' => 86400, - 'lastreport' => 0, - 'mailinglist' => [], - 'mailformat' => 'plain', - 'dateformat' => '%Y-%m-%d %H:%M:%S', - 'requests' => [], - 'blogs' => [], - ]; - - $this->settings[0] = $this->settings[1] = $settings; - - $rs = $this->con->select( - 'SELECT setting_id, setting_value, blog_id ' . - 'FROM ' . dcCore::app()->prefix . initActivityReport::SETTING_TABLE_NAME . ' ' . - "WHERE setting_type='" . $this->ns . "' " . - "AND (blog_id='" . $this->blog . "' OR blog_id IS NULL) " . - 'ORDER BY setting_id DESC ' - ); - - while ($rs->fetch()) { - $k = $rs->f('setting_id'); - $v = $rs->f('setting_value'); - $b = $rs->f('blog_id'); - $g = $b === null ? 1 : 0; - - if (isset($settings[$k])) { - $this->settings[$g][$k] = self::decode($v); - } - } - # Force blog - $this->settings[0]['blogs'] = [0 => $this->blog]; - } - - public function getSetting($n) - { - return $this->settings[$this->_global][$n] ?? null; - } - - public function setSetting($n, $v) - { - if (!isset($this->settings[$this->_global][$n])) { - return null; - } - - $c = $this->delSetting($n); - - $cur = $this->con->openCursor(dcCore::app()->prefix . initActivityReport::SETTING_TABLE_NAME); - $this->con->writeLock(dcCore::app()->prefix . initActivityReport::SETTING_TABLE_NAME); - - $cur->blog_id = $this->_global ? null : $this->blog; - $cur->setting_id = $this->con->escape($n); - $cur->setting_type = $this->ns; - $cur->setting_value = (string) self::encode($v); - - $cur->insert(); - $this->con->unlock(); - - $this->settings[$this->_global][$n] = $v; - - return true; - } - - private function delSetting($n) - { - return $this->con->execute( - 'DELETE FROM ' . dcCore::app()->prefix . initActivityReport::SETTING_TABLE_NAME . ' ' . - 'WHERE blog_id' . ($this->_global ? ' IS NULL' : "='" . $this->blog . "'") . ' ' . - "AND setting_id='" . $this->con->escape($n) . "' " . - "AND setting_type='" . $this->ns . "' " - ); - } - - // Action params to put in params['sql'] - public static function requests2params($requests) - { - $r = []; - foreach ($requests as $group => $actions) { - foreach ($actions as $action => $is) { - $r[] = "activity_group='" . $group . "' AND activity_action='" . $action . "' "; - } - } - - return empty($r) ? '' : 'AND (' . implode('OR ', $r) . ') '; - } - - public function getLogs($p, $count_only = false) - { if ($count_only) { - $r = 'SELECT count(E.activity_id) '; + $sql->column($sql->count($sql->unique('E.activity_id'))); } else { - $content_r = empty($p['no_content']) ? 'activity_logs, ' : ''; - - if (!empty($p['columns']) && is_array($p['columns'])) { - $content_r .= implode(', ', $p['columns']) . ', '; + if (empty($params['no_content'])) { + $sql->columns([ + 'activity_logs', + ]); } - - $r = 'SELECT E.activity_id, E.blog_id, B.blog_url, B.blog_name, ' . $content_r . - 'E.activity_group, E.activity_action, E.activity_dt, ' . - 'E.activity_blog_status, E.activity_super_status '; - } - - $r .= 'FROM ' . dcCore::app()->prefix . initActivityReport::ACTIVITY_TABLE_NAME . ' E ' . - 'LEFT JOIN ' . dcCore::app()->prefix . dcBlog::BLOG_TABLE_NAME . ' B on E.blog_id=B.blog_id '; - - if (!empty($p['from'])) { - $r .= $p['from'] . ' '; - } - - if ($this->_global) { - $r .= 'WHERE E.activity_super_status = 0 '; - } else { - $r .= 'WHERE E.activity_blog_status = 0 '; - } - - if (!empty($p['activity_type'])) { - $r .= "AND E.activity_type = '" . $this->con->escape($p['activity_type']) . "' "; - } else { - $r .= "AND E.activity_type = '" . $this->ns . "' "; - } - - if (!empty($p['blog_id'])) { - if (is_array($p['blog_id'])) { - $r .= 'AND E.blog_id' . $this->con->in($p['blog_id']); - } else { - $r .= "AND E.blog_id = '" . $this->con->escape($p['blog_id']) . "' "; + if (!empty($params['columns']) && is_array($params['columns'])) { + $sql->columns($params['columns']); } - } elseif ($this->_global) { - $r .= 'AND E.blog_id IS NOT NULL '; - } else { - $r .= "AND E.blog_id='" . $this->blog . "' "; + $sql->columns([ + 'E.activity_id', + 'E.blog_id', + 'B.blog_url', + 'B.blog_name', + 'E.activity_group', + 'E.activity_action', + 'E.activity_dt', + 'E.activity_status', + ]); + } + $sql + ->from($sql->as(dcCore::app()->prefix . My::ACTIVITY_TABLE_NAME, 'E'), false, true) + ->join( + (new JoinStatement()) + ->left() + ->from($sql->as(dcCore::app()->prefix . dcBlog::BLOG_TABLE_NAME, 'B')) + ->on('E.blog_id = B.blog_id') + ->statement() + ); + + if (!empty($params['join'])) { + $sql->join($params['join']); } - if (isset($p['activity_group'])) { - if (is_array($p['activity_group']) && !empty($p['activity_group'])) { - $r .= 'AND E.activity_group ' . $this->con->in($p['activity_group']); - } elseif ($p['activity_group'] != '') { - $r .= "AND E.activity_group = '" . $this->con->escape($p['activity_group']) . "' "; + if (!empty($params['from'])) { + $sql->from($params['from']); + } + + if (!empty($params['where'])) { + //nope + } + + if (!empty($params['activity_type'])) { + $sql->where('E.activity_type = ' . $sql->quote($params['activity_type'])); + } else { + $sql->where('E.activity_type = ' . $sql->quote($this->type)); + } + + if (!empty($params['blog_id'])) { + if (!is_array($params['blog_id'])) { + $params['blog_id'] = [$params['blog_id']]; + } + $sql->and('E.blog_id' . $sql->in($params['blog_id'])); + } elseif (is_null($params['blog_id'])) { + $sql->and('E.blog_id IS NOT NULL'); + } else { + $sql->and('E.blog_id = ' . $sql->quote((string) dcCore::app()->blog?->id)); + } + + if (isset($params['activity_status'])) { + $sql->and('E.activity_status = ' . ((int) $params['activity_tatus']) . ' '); + } + //$sql->and('E.activity_status = ' . self::STATUS_PENDING); + + if (isset($params['activity_group'])) { + if (!is_array($params['activity_group'])) { + $params['activity_group'] = [$params['activity_group']]; + } + $sql->and('E.activity_group' . $sql->in($params['activity_group'])); + } + + if (isset($params['activity_action'])) { + if (!is_array($params['activity_action'])) { + $params['activity_action'] = [$params['activity_action']]; + } + $sql->and('E.activity_action' . $sql->in($params['activity_action'])); + } + + if (isset($params['from_date_ts'])) { + $sql->and("E.activity_dt >= TIMESTAMP '" . date('Y-m-d H:i:s', $params['from_date_ts']) . "' "); + } + if (isset($params['to_date_ts'])) { + $sql->and("E.activity_dt < TIMESTAMP '" . date('Y-m-d H:i:s', $params['to_date_ts']) . "' "); + } + + if (!empty($params['requests'])) { + $or = []; + foreach ($this->settings->requests as $group => $actions) { + if (empty($actions)) { + continue; + } + foreach ($actions as $action => $is) { + $or[] = $sql->andGroup(['activity_group = ' . $sql->quote($group), 'activity_action = ' . $sql->quote($action)]); + } + } + if (empty($or)) { + $sql->and($sql->orGroup($or)); } } - if (isset($p['activity_action'])) { - if (is_array($p['activity_action']) && !empty($p['activity_action'])) { - $r .= 'AND E.activity_action ' . $this->con->in($p['activity_action']); - } elseif ($p['activity_action'] != '') { - $r .= "AND E.activity_action = '" . $this->con->escape($p['activity_action']) . "' "; - } - } - - if (isset($p['activity_blog_status'])) { - $r .= 'AND E.activity_blog_status = ' . ((int) $p['activity_blog_status']) . ' '; - } - - if (isset($p['activity_super_status'])) { - $r .= 'AND E.activity_super_status = ' . ((int) $p['activity_super_status']) . ' '; - } - - if (isset($p['from_date_ts'])) { - $dt = date('Y-m-d H:i:s', $p['from_date_ts']); - $r .= "AND E.activity_dt >= TIMESTAMP '" . $dt . "' "; - } - if (isset($p['to_date_ts'])) { - $dt = date('Y-m-d H:i:s', $p['to_date_ts']); - $r .= "AND E.activity_dt < TIMESTAMP '" . $dt . "' "; - } - - if (!empty($p['sql'])) { - $r .= $p['sql'] . ' '; + if (!empty($params['sql'])) { + $sql->sql($params['sql']); } if (!$count_only) { - if (!empty($p['order'])) { - $r .= 'ORDER BY ' . $this->con->escape($p['order']) . ' '; + if (!empty($params['order'])) { + $sql->order($sql->escape($params['order'])); } else { - $r .= 'ORDER BY E.activity_dt DESC '; + $sql->order('E.activity_dt DESC'); } } - if (!$count_only && !empty($p['limit'])) { - $r .= $this->con->limit($p['limit']); + if (!$count_only && !empty($params['limit'])) { + $sql->limit($params['limit']); } + $rs = $sql->select(); - return $this->con->select($r); + return $sql->select(); } - public function addLog($group, $action, $logs) + /** + * Add a log. + * + * @param string $group The group + * @param string $action The action + * @param array $logs The logs values + */ + public function addLog(string $group, string $action, array $logs): void { try { - $cur = $this->con->openCursor(dcCore::app()->prefix . initActivityReport::ACTIVITY_TABLE_NAME); - $this->con->writeLock(dcCore::app()->prefix . initActivityReport::ACTIVITY_TABLE_NAME); + $cur = dcCore::app()->con->openCursor(dcCore::app()->prefix . My::ACTIVITY_TABLE_NAME); + dcCore::app()->con->writeLock(dcCore::app()->prefix . My::ACTIVITY_TABLE_NAME); - $cur->activity_id = $this->getNextId(); - $cur->activity_type = $this->ns; - $cur->blog_id = $this->blog; - $cur->activity_group = $this->con->escape((string) $group); - $cur->activity_action = $this->con->escape((string) $action); - $cur->activity_logs = self::encode($logs); - $cur->activity_dt = date('Y-m-d H:i:s'); + $cur->setField('activity_id', $this->getNextId()); + $cur->setField('activity_type', $this->type); + $cur->setField('blog_id', (string) dcCore::app()->blog?->id); + $cur->setField('activity_group', $group); + $cur->setField('activity_action', $action); + $cur->setField('activity_logs', json_encode($logs)); + $cur->setField('activity_dt', date('Y-m-d H:i:s')); + $cur->setField('activity_status', self::STATUS_PENDING); $cur->insert(); - $this->con->unlock(); + dcCore::app()->con->unlock(); + + # --BEHAVIOR-- coreAfterCategoryCreate -- ActivityReport, cursor + dcCore::app()->callBehavior('activityReportAfteAddLog', $this, $cur); } catch (Exception $e) { - $this->con->unlock(); + dcCore::app()->con->unlock(); dcCore::app()->error->add($e->getMessage()); } @@ -298,77 +268,50 @@ class activityReport $this->needReport(); } - private function parseLogs($rs) + /** + * Parse logs using a format. + * + * @param dcRecord $rs The logs record + * + * @return string The parsed logs + */ + private function parseLogs(dcRecord $rs): string { - if ($rs->isEmpty()) { - return ''; - } - - // @todo move this in function - include __DIR__ . '/lib.parselogs.config.php'; - - $from = time(); - $to = 0; - $res = $blog = $group = ''; - $tz = $this->_global ? 'UTC' : dcCore::app()->blog->settings->system->blog_timezone; - - $dt = $this->settings[$this->_global]['dateformat']; - $dt = empty($dt) ? '%Y-%m-%d %H:%M:%S' : $dt; - - $tpl = $this->settings[$this->_global]['mailformat']; - $tpl = $tpl == 'html' ? $format['html'] : $format['plain']; - - $blog_open = $group_open = false; + $from = time(); + $to = 0; + $res = $blog = $group = ''; + $tz = dcCore::app()->blog?->settings->get('system')->get('blog_timezone'); + $dt = empty($this->settings->dateformat) ? '%Y-%m-%d %H:%M:%S' : $this->settings->dateformat; + $format = $this->formats->get($this->formats->has($this->settings->mailformat) ? $this->settings->mailformat : 'plain'); + $group_open = false; while ($rs->fetch()) { - // blog - if ($rs->blog_id != $blog && $this->_global) { - if ($group_open) { - $res .= $tpl['group_close']; - $group_open = false; - } - if ($blog_open) { - $res .= $tpl['blog_close']; - } - - $blog = $rs->blog_id; - $group = ''; - - $res .= str_replace( - ['%TEXT%', '%URL%'], - [$rs->blog_name . ' (' . $rs->blog_id . ')', $rs->blog_url], - $tpl['blog_title'] - ) . $tpl['blog_open']; - - $blog_open = true; - } - - if (isset($this->groups[$rs->activity_group])) { + if ($this->groups->has($rs->f('activity_group')) && $this->groups->get($rs->f('activity_group'))->has($rs->f('activity_action'))) { // Type - if ($rs->activity_group != $group) { + if ($rs->f('activity_group') != $group) { if ($group_open) { - $res .= $tpl['group_close']; + $res .= $format->group_close; } - $group = $rs->activity_group; + $group = $rs->f('activity_group'); $res .= str_replace( '%TEXT%', - __($this->groups[$group]['title']), - $tpl['group_title'] - ) . $tpl['group_open']; + __($this->groups->get($group)->title), + $format->group_title + ) . $format->group_open; $group_open = true; } // Action - $time = strtotime($rs->activity_dt); - $data = self::decode($rs->activity_logs); + $time = strtotime($rs->f('activity_dt')); + $data = json_decode($rs->f('activity_logs'), true); $res .= str_replace( ['%TIME%', '%TEXT%'], - [dt::str($dt, $time, $tz), vsprintf(__($this->groups[$group]['actions'][$rs->activity_action]['msg']), $data)], - $tpl['action'] + [Date::str($dt, (int) $time, $tz), vsprintf(__($this->groups->get($group)->get($rs->f('activity_action'))->message), $data)], + $format->action ); # Period @@ -382,13 +325,10 @@ class activityReport } if ($group_open) { - $res .= $tpl['group_close']; - } - if ($blog_open) { - $res .= $tpl['blog_close']; + $res .= $format->group_close; } if ($to == 0) { - $res .= str_replace('%TEXT%', __('An error occured when parsing report.'), $tpl['error']); + $res .= str_replace('%TEXT%', __('An error occured when parsing report.'), $format->error); } // Top of msg @@ -399,127 +339,143 @@ class activityReport $period = str_replace( '%TEXT%', __('Activity report'), - $tpl['period_title'] - ) . $tpl['period_open']; + $format->period_title + ) . $format->period_open; $period .= str_replace( '%TEXT%', __("You received a message from your blog's activity report module."), - $tpl['info'] + $format->info ); - if (!$this->_global) { - $period .= str_replace('%TEXT%', $rs->blog_name, $tpl['info']); - $period .= str_replace('%TEXT%', $rs->blog_url, $tpl['info']); - } + + $period .= str_replace('%TEXT%', $rs->f('blog_name'), $format->info); + $period .= str_replace('%TEXT%', $rs->f('blog_url'), $format->info); + $period .= str_replace( '%TEXT%', - sprintf(__('Period from %s to %s'), dt::str($dt, $from, $tz), dt::str($dt, $to, $tz)), - $tpl['info'] + sprintf(__('Period from %s to %s'), Date::str($dt, (int) $from, $tz), Date::str($dt, (int) $to, $tz)), + $format->info ); - $period .= $tpl['period_close']; + $period .= $format->period_close; - $res = str_replace(['%PERIOD%', '%TEXT%'], [$period, $res], $tpl['page']); + $res = str_replace(['%PERIOD%', '%TEXT%'], [$period, $res], $format->page); return $res; } - private function obsoleteLogs() + /** + * Delete obsolete logs. + */ + private function obsoleteLogs(): void { // Get blogs and logs count - $rs = $this->con->select( - 'SELECT blog_id ' . - 'FROM ' . dcCore::app()->prefix . initActivityReport::ACTIVITY_TABLE_NAME . ' ' . - "WHERE activity_type='" . $this->ns . "' " . - 'GROUP BY blog_id ' - ); + $sql = new SelectStatement(); + $sql->from(dcCore::app()->prefix . My::ACTIVITY_TABLE_NAME) + ->where('activity_type =' . $sql->quote($this->type)) + ->group('blog_id'); - if ($rs->isEmpty()) { - return null; + $rs = $sql->select(); + + if (!$rs || $rs->isEmpty()) { + return; } while ($rs->fetch()) { - $ts = time(); - $obs_blog = dt::str('%Y-%m-%d %H:%M:%S', $ts - (int) $this->settings[0]['obsolete']); - $obs_global = dt::str('%Y-%m-%d %H:%M:%S', $ts - (int) $this->settings[1]['obsolete']); + $ts = time(); + $obs = Date::str('%Y-%m-%d %H:%M:%S', $ts - (int) $this->settings->obsolete); - $this->con->execute( - 'DELETE FROM ' . dcCore::app()->prefix . initActivityReport::ACTIVITY_TABLE_NAME . ' ' . - "WHERE activity_type='" . $this->ns . "' " . - "AND (activity_dt < TIMESTAMP '" . $obs_blog . "' " . - "OR activity_dt < TIMESTAMP '" . $obs_global . "') " . - "AND blog_id = '" . $this->con->escape($rs->blog_id) . "' " - ); + $sql = new DeleteStatement(); + $sql->from(dcCore::app()->prefix . My::ACTIVITY_TABLE_NAME) + ->where('activity_type =' . $sql->quote($this->type)) + ->and('activity_dt < TIMESTAMP ' . $sql->quote($obs)) + ->and('blog_id = ' . $sql->quote($rs->f('blog_id'))) + ->delete(); - if ($this->con->changes()) { + if (dcCore::app()->con->changes()) { try { - $cur = $this->con->openCursor(dcCore::app()->prefix . initActivityReport::ACTIVITY_TABLE_NAME); - $this->con->writeLock(dcCore::app()->prefix . initActivityReport::ACTIVITY_TABLE_NAME); + $cur = dcCore::app()->con->openCursor(dcCore::app()->prefix . My::ACTIVITY_TABLE_NAME); + dcCore::app()->con->writeLock(dcCore::app()->prefix . My::ACTIVITY_TABLE_NAME); - $cur->activity_id = $this->getNextId(); - $cur->activity_type = $this->ns; - $cur->blog_id = $rs->blog_id; - $cur->activity_group = 'activityReport'; - $cur->activity_action = 'message'; - $cur->activity_logs = self::encode(__('Activity report deletes some old logs.')); - $cur->activity_dt = date('Y-m-d H:i:s'); + $cur->setField('activity_id', $this->getNextId()); + $cur->setField('activity_type', $this->type); + $cur->setField('blog_id', $rs->f('blog_id')); + $cur->setField('activity_group', My::id()); + $cur->setField('activity_action', 'message'); + $cur->setField('activity_logs', json_encode([__('Activity report deletes some old logs.')])); + $cur->setField('activity_dt', date('Y-m-d H:i:s')); + $cur->setField('activity_status', self::STATUS_PENDING); $cur->insert(); - $this->con->unlock(); + dcCore::app()->con->unlock(); } catch (Exception $e) { - $this->con->unlock(); + dcCore::app()->con->unlock(); dcCore::app()->error->add($e->getMessage()); } } } } - private function cleanLogs() + /** + * Delete logs. + * + * @param bool $only_reported Delete only allready reported logs + * + * @return bool Action done + */ + public function deleteLogs(bool $only_reported = false): bool { - $this->con->execute( - 'DELETE FROM ' . dcCore::app()->prefix . initActivityReport::ACTIVITY_TABLE_NAME . ' ' . - "WHERE activity_type='" . $this->ns . "' " . - 'AND activity_blog_status = 1 ' . - 'AND activity_super_status = 1 ' - ); - } + $sql = new DeleteStatement(); - public function deleteLogs() - { - if (!dcCore::app()->auth->isSuperAdmin()) { - return null; + if ($only_reported) { + $sql->and('activity_status = ' . self::STATUS_REPORTED); } - return $this->con->execute( - 'DELETE FROM ' . dcCore::app()->prefix . initActivityReport::ACTIVITY_TABLE_NAME . ' ' . - "WHERE activity_type='" . $this->ns . "' " - ); + return $sql->from(dcCore::app()->prefix . My::ACTIVITY_TABLE_NAME) + ->where('activity_type = ' . $sql->quote($this->type)) + ->delete(); } - private function updateStatus($from_date_ts, $to_date_ts) + /** + * Update logs status according to time interval. + * + * @param int $from_date_ts The start time + * @param int $to_date_ts The end time + */ + private function updateStatus(int $from_date_ts, int $to_date_ts): void { - $r = 'UPDATE ' . dcCore::app()->prefix . initActivityReport::ACTIVITY_TABLE_NAME . ' '; - - if ($this->_global) { - $r .= 'SET activity_super_status = 1 WHERE blog_id IS NOT NULL '; - } else { - $r .= "SET activity_blog_status = 1 WHERE blog_id = '" . $this->blog . "' "; - } - $r .= "AND activity_type = '" . $this->ns . "' " . - "AND activity_dt >= TIMESTAMP '" . date('Y-m-d H:i:s', $from_date_ts) . "' " . - "AND activity_dt < TIMESTAMP '" . date('Y-m-d H:i:s', $to_date_ts) . "' "; - - $this->con->execute($r); + $sql = new UpdateStatement(); + $sql->from(dcCore::app()->prefix . My::ACTIVITY_TABLE_NAME) + ->column('activity_status') + ->set((string) self::STATUS_REPORTED) + ->where('blog_id = ' . $sql->quote((string) dcCore::app()->blog?->id)) + ->and('activity_type =' . $sql->quote($this->type)) + ->and('activity_dt >= TIMESTAMP ' . $sql->quote(date('Y-m-d H:i:s', $from_date_ts))) + ->and('activity_dt < TIMESTAMP ' . $sql->quote(date('Y-m-d H:i:s', $to_date_ts))) + ->update(); } - public function getNextId() + /** + * Get next activity ID. + * + * @return int The next id + */ + public function getNextId(): int { - return $this->con->select( - 'SELECT MAX(activity_id) FROM ' . dcCore::app()->prefix . initActivityReport::ACTIVITY_TABLE_NAME - )->f(0) + 1; + $sql = new SelectStatement(); + $sql->from(dcCore::app()->prefix . My::ACTIVITY_TABLE_NAME) + ->column($sql->max('activity_id')); + + return (int) $sql->select()?->f(0) + 1; } - # Lock a file to see if an update is ongoing - public function lockUpdate() + /** + * Lock update. + * + * Lock a file to see if an update is ongoing. + * + * @return bool The lock success + */ + public function lockUpdate(): bool { try { # Need flock function @@ -531,20 +487,20 @@ class activityReport throw new Exception("Can't write in cache fodler"); } # Set file path - $f_md5 = $this->_global ? md5(DC_MASTER_KEY) : md5($this->blog); + $f_md5 = md5((string) dcCore::app()->blog?->id); $cached_file = sprintf( '%s/%s/%s/%s/%s.txt', DC_TPL_CACHE, - initActivityReport::CACHE_DIR_NAME, + My::CACHE_DIR_NAME, substr($f_md5, 0, 2), substr($f_md5, 2, 2), $f_md5 ); # Real path - $cached_file = path::real($cached_file, false); + $cached_file = (string) Path::real($cached_file, false); // make dir if (!is_dir(dirname($cached_file))) { - files::makeDir(dirname($cached_file), true); + Files::makeDir(dirname($cached_file), true); } //ake file if (!file_exists($cached_file)) { @@ -564,38 +520,45 @@ class activityReport //throw new Exception("Can't lock file"); return false; } - if ($this->_global) { - $this->lock_global = $fp; - } else { - $this->lock_blog = $fp; - } + + $this->lock = $fp; return true; } catch (Exception $e) { // what ? throw $e; } - - return false; } - public function unlockUpdate() + /** + * Unlock update. + */ + public function unlockUpdate(): void { - if ($this->_global) { - @fclose($this->lock_global); - $this->lock_global = null; - } elseif ($this->lock_blog) { - @fclose($this->lock_blog); - $this->lock_blog = null; + if ($this->lock) { + @fclose($this->lock); + $this->lock = null; } } - public static function hasMailer() + /** + * Check if doctclear has maisl fonction. + * + * @return bool has mailer + */ + public static function hasMailer(): bool { return function_exists('mail') || function_exists('_mail'); } - public function needReport($force = false) + /** + * Check if blog need report to be sent and send it. + * + * @param bool $force Force to send report + * + * @return bool The success + */ + public function needReport(bool $force = false): bool { try { // Check if server has mail function @@ -609,33 +572,33 @@ class activityReport $send = false; $now = time(); - $active = (bool) $this->settings[$this->_global]['active']; - $mailinglist = $this->settings[$this->_global]['mailinglist']; - $mailformat = $this->settings[$this->_global]['mailformat']; - $requests = $this->settings[$this->_global]['requests']; - $lastreport = (int) $this->settings[$this->_global]['lastreport']; - $interval = (int) $this->settings[$this->_global]['interval']; - $blogs = $this->settings[$this->_global]['blogs']; + $mailinglist = $this->settings->mailinglist; + $mailformat = $this->settings->mailformat; + $requests = $this->settings->requests; + $lastreport = $this->settings->lastreport; + $interval = $this->settings->interval; if ($force) { $lastreport = 0; } // Check if report is needed - if ($active && !empty($mailinglist) && !empty($requests) && !empty($blogs) - && ($lastreport + $interval) < $now + if (!empty($mailinglist) + && !empty($requests) + && ($lastreport + $interval) < $now ) { // Get datas - $params = [ - 'from_date_ts' => $lastreport, - 'to_date_ts' => $now, - 'blog_id' => $blogs, - 'sql' => self::requests2params($requests), - 'order' => 'blog_id ASC, activity_group ASC, activity_action ASC, activity_dt ASC ', - ]; + $params = new ArrayObject([ + 'from_date_ts' => $lastreport, + 'to_date_ts' => $now, + 'blog_id' => dcCore::app()->blog?->id, + 'activity_status' => self::STATUS_PENDING, + 'requests' => true, + 'order' => 'activity_group ASC, activity_action ASC, activity_dt ASC ', + ]); $logs = $this->getLogs($params); - if (!$logs->isEmpty()) { + if ($logs !== null && !$logs->isEmpty()) { // Datas to readable text $content = $this->parseLogs($logs); if (!empty($content)) { @@ -645,23 +608,14 @@ class activityReport } // Update db - if ($send || $this->_global) { // if global : delete all blog logs even if not selected - $this->updateStatus($lastreport, $now); - $this->cleanLogs(); - $this->setSetting('lastreport', $now); - } - } - - // If this is on a blog, we need to test superAdmin report - if (!$this->_global) { - $this->_global = true; - $this->needReport(); - $this->_global = false; - if ($send) { + $this->updateStatus($lastreport, $now); + $this->settings->set('lastreport', $now); + dcCore::app()->callBehavior('messageActivityReport', 'Activity report has been successfully send by mail.'); } } + $this->unlockUpdate(); } catch (Exception $e) { $this->unlockUpdate(); @@ -670,7 +624,16 @@ class activityReport return true; } - private function sendReport($recipients, $message, $mailformat = ' ') + /** + * Send a report. + * + * @param array $recipients The recipients + * @param string $message The message + * @param string $mailformat The mail content format + * + * @return bool The sent success + */ + private function sendReport(array $recipients, string $message, string $mailformat = 'plain'): bool { if (!is_array($recipients) || empty($message)) { return false; @@ -681,7 +644,7 @@ class activityReport $rc2 = []; foreach ($recipients as $v) { $v = trim($v); - if (!empty($v) && text::isEmail($v)) { + if (!empty($v) && Text::isEmail($v)) { $rc2[] = $v; } } @@ -695,13 +658,13 @@ class activityReport # Sending mails try { $subject = mb_encode_mimeheader( - ($this->_global ? '[' . dcCore::app()->blog->name . '] ' : '') . __('Blog activity report'), + sprintf(__('Blog "%s" activity report'), dcCore::app()->blog?->name), 'UTF-8', 'B' ); $headers = []; - $headers[] = 'From: ' . (defined('DC_ADMIN_MAILFROM') && DC_ADMIN_MAILFROM ? DC_ADMIN_MAILFROM : 'dotclear@local'); + $headers[] = 'From: ' . (defined('DC_ADMIN_MAILFROM') && str_contains(DC_ADMIN_MAILFROM, '@') ? DC_ADMIN_MAILFROM : 'dotclear@local'); $headers[] = 'Content-Type: text/' . $mailformat . '; charset=UTF-8;'; //$headers[] = 'MIME-Version: 1.0'; //$headers[] = 'X-Originating-IP: ' . mb_encode_mimeheader(http::realIP(), 'UTF-8', 'B'); @@ -712,7 +675,7 @@ class activityReport $done = true; foreach ($recipients as $email) { - if (true !== mail::sendMail($email, $subject, $message, $headers)) { + if (true !== Mail::sendMail($email, $subject, $message, $headers)) { $done = false; } } @@ -723,51 +686,54 @@ class activityReport return $done; } - public function getUserCode() + /** + * Generate current user code for public feed. + * + * @return string The code + */ + public function getUserCode(): string { - $code = pack('a32', dcCore::app()->auth->userID()) . - pack('H*', crypt::hmac(DC_MASTER_KEY, dcCore::app()->auth->getInfo('user_pwd'))); + $code = pack('a32', (string) dcCore::app()->auth?->userID()) . + pack('H*', Crypt::hmac(DC_MASTER_KEY, (string) dcCore::app()->auth?->getInfo('user_pwd'))); return bin2hex($code); } - public function checkUserCode($code) + /** + * Check user code from URL. + * + * @param string $code The code + * + * @return string|false The user ID or false + */ + public function checkUserCode(string $code): string|false { $code = pack('H*', $code); $user_id = trim(@pack('a32', substr($code, 0, 32))); $pwd = @unpack('H40hex', substr($code, 32, 40)); - if ($user_id === false || $pwd === false) { + if (empty($user_id) || $pwd === false) { return false; } $pwd = $pwd['hex']; - $strReq = 'SELECT user_id, user_pwd ' . - 'FROM ' . dcCore::app()->prefix . dcAuth::USER_TABLE_NAME . ' ' . - "WHERE user_id = '" . dcCore::app()->con->escape($user_id) . "' "; + $sql = new SelectStatement(); + $sql->from(dcCore::app()->prefix . dcAuth::USER_TABLE_NAME) + ->columns(['user_id', 'user_pwd']) + ->where('user_id =' . $sql->quote($user_id)); - $rs = dcCore::app()->con->select($strReq); + $rs = $sql->select(); - if ($rs->isEmpty()) { + if (!$rs || $rs->isEmpty()) { return false; } - if (crypt::hmac(DC_MASTER_KEY, $rs->user_pwd) != $pwd) { + if (Crypt::hmac(DC_MASTER_KEY, $rs->f('user_pwd')) != $pwd) { return false; } - return $rs->user_id; - } - - public static function encode($a) - { - return @base64_encode(@serialize($a)); - } - - public static function decode($a) - { - return @unserialize(@base64_decode($a)); + return $rs->f('user_id'); } } diff --git a/src/Backend.php b/src/Backend.php index 5432734..579b037 100644 --- a/src/Backend.php +++ b/src/Backend.php @@ -10,140 +10,187 @@ * @copyright Jean-Christian Denis * @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html */ -if (!defined('DC_CONTEXT_ADMIN')) { - return null; -} -if (!defined('ACTIVITY_REPORT_V2')) { - return null; -} +declare(strict_types=1); -dcCore::app()->menu[dcAdmin::MENU_PLUGINS]->addItem( - __('Activity report'), - dcCore::app()->adminurl->get('admin.plugin.' . basename(__DIR__)), - dcPage::getPF(basename(__DIR__) . '/icon.png'), - preg_match( - '/' . preg_quote(dcCore::app()->adminurl->get('admin.plugin.' . basename(__DIR__))) . '(&.*)?$/', - $_SERVER['REQUEST_URI'] - ), - dcCore::app()->auth->check(dcCore::app()->auth->makePermissions([ - dcAuth::PERMISSION_ADMIN, - ]), dcCore::app()->blog->id) -); +namespace Dotclear\Plugin\activityReport; -if (dcCore::app()->activityReport->getSetting('active')) { - dcCore::app()->addBehavior('adminDashboardContentsV2', ['activityReportAdmin', 'adminDashboardContentsV2']); - dcCore::app()->addBehavior('adminDashboardOptionsFormV2', ['activityReportAdmin', 'adminDashboardOptionsFormV2']); - dcCore::app()->addBehavior('adminAfterDashboardOptionsUpdate', ['activityReportAdmin', 'adminAfterDashboardOptionsUpdate']); -} +use ArrayObject; +use dcAdmin; +use dcAuth; +use dcCore; +use dcFavorites; +use dcNsProcess; +use dcPage; +use Dotclear\Helper\Date; +use Dotclear\Helper\Html\Form\{ + Div, + Label, + Para, + Select, + Text +}; -class activityReportAdmin +/** + * Backend process + */ +class Backend extends dcNsProcess { - public static function adminDashboardContentsV2($items) + public static function init(): bool { - dcCore::app()->auth->user_prefs->addWorkspace(basename(__DIR__)); - $limit = abs((int) dcCore::app()->auth->user_prefs->__get(basename(__DIR__))->dashboard_item); - if (!$limit) { - return null; - } - $p = [ - 'limit' => $limit, - 'order' => 'activity_dt DESC', - 'sql' => dcCore::app()->activityReport->requests2params(dcCore::app()->activityReport->getSetting('requests')), - ]; - $lines = []; - $rs = dcCore::app()->activityReport->getLogs($p); - if ($rs->isEmpty()) { - return null; - } - $groups = dcCore::app()->activityReport->getGroups(); - while ($rs->fetch()) { - $group = $rs->activity_group; + static::$init = defined('DC_CONTEXT_ADMIN') + && defined('ACTIVITY_REPORT') + && My::phpCompliant() + && My::isInstalled(); - if (!isset($groups[$group])) { - continue; - } - $lines[] = '
    ' . - '' . __($groups[$group]['actions'][$rs->activity_action]['title']) . '' . - '
    ' . dt::str( - dcCore::app()->blog->settings->system->date_format . ', ' . dcCore::app()->blog->settings->system->time_format, - strtotime($rs->activity_dt), - dcCore::app()->auth->getInfo('user_tz') - ) . '
    ' . - '

    ' . - '' . vsprintf( - __($groups[$group]['actions'][$rs->activity_action]['msg']), - dcCore::app()->activityReport->decode($rs->activity_logs) - ) . '

    '; - } - if (empty($lines)) { - return null; - } - $items[] = new ArrayObject([ - '
    ' . - '

    ' . __('Activity report') . '

    ' . - '
    ' . implode('', $lines) . '
    ' . - '

    ' . - __('View all logs') . ' - ' . - __('Configure plugin') . '

    ' . - '
    ', - ]); + return static::$init; } - public static function adminDashboardOptionsFormV2() + public static function process(): bool { - dcCore::app()->auth->user_prefs->addWorkspace(basename(__DIR__)); - - echo - '
    ' . - '

    ' . __('Activity report') . '

    ' . - '

    ' . - form::combo( - 'activityReport_dashboard_item', - self::comboList(), - self::comboList(dcCore::app()->auth->user_prefs->__get(basename(__DIR__))->dashboard_item) - ) . '

    ' . - '
    '; - } - - public static function adminAfterDashboardOptionsUpdate($user_id = null) - { - if (is_null($user_id)) { - return; + if (!static::$init) { + return false; } - dcCore::app()->auth->user_prefs->addWorkspace(basename(__DIR__)); - dcCore::app()->auth->user_prefs->__get(basename(__DIR__))->put( - 'dashboard_item', - self::comboList(@$_POST['activityReport_dashboard_item']), - 'integer' + 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((string) dcCore::app()->adminurl?->get('admin.plugin.' . My::id())) . '(&.*)?$/', + $_SERVER['REQUEST_URI'] + ), + dcCore::app()->auth?->check(dcCore::app()->auth->makePermissions([ + dcAuth::PERMISSION_ADMIN, + ]), dcCore::app()->blog?->id) ); - } - private static function comboList($q = true) - { - $l = [ - __('Do not show activity report') => 0, - 5 => 5, - 10 => 10, - 15 => 15, - 20 => 20, - 50 => 50, - 100 => 100, - ]; - if (true === $q) { - return $l; - } - if (!$q) { - $q = -1; - } + dcCore::app()->addBehaviors([ + // dashboard favorites icon + 'adminDashboardFavoritesV2' => function (dcFavorites $favs): void { + $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([ + dcAuth::PERMISSION_ADMIN, + ]), + ]); + }, + // dashboard content display + 'adminDashboardContentsV2' => function (ArrayObject $items): void { + $limit = abs((int) dcCore::app()->auth?->user_prefs?->get(My::id())->get('dashboard_item')); + if (!$limit) { + return ; + } - return in_array($q, $l) ? $l[$q] : 0; + $params = new ArrayObject([ + 'limit' => $limit, + 'requests' => true, + ]); + $rs = ActivityReport::instance()->getLogs($params); + if (!$rs || $rs->isEmpty()) { + return; + } + + $lines = []; + $groups = ActivityReport::instance()->groups; + while ($rs->fetch()) { + if (!$groups->has($rs->f('activity_group'))) { + continue; + } + $group = $groups->get($rs->f('activity_group')); + $lines[] = '
    ' . + '' . __($group->get($rs->f('activity_action'))->title) . '' . + '
    ' . Date::str( + dcCore::app()->blog?->settings->get('system')->get('date_format') . ', ' . dcCore::app()->blog?->settings->get('system')->get('time_format'), + (int) strtotime($rs->f('activity_dt')), + dcCore::app()->auth?->getInfo('user_tz') + ) . '
    ' . + '

    ' . + '' . vsprintf( + __($group->get($rs->f('activity_action'))->message), + json_decode($rs->f('activity_logs'), true) + ) . '

    '; + } + if (empty($lines)) { + return ; + } + + $items[] = new ArrayObject([ + '
    ' . + '

    ' . My::name() . '

    ' . + '
    ' . implode('', $lines) . '
    ' . + '

    ' . + __('View all logs') . ' - ' . + __('Configure plugin') . '

    ' . + '
    ', + ]); + }, + // dashboard content user preference form + 'adminDashboardOptionsFormV2' => function (): void { + echo + (new Div())->class('fieldset')->items([ + (new Text('h4', My::name())), + (new Para())->items([ + (new Label(__('Number of activities to show on dashboard:'), Label::OUTSIDE_LABEL_BEFORE))->for(My::id() . '_dashboard_item'), + (new Select(My::id() . '_dashboard_item'))->default((string) dcCore::app()->auth?->user_prefs?->get(My::id())->get('dashboard_item'))->items([ + __('Do not show activity report') => 0, + 5 => 5, + 10 => 10, + 15 => 15, + 20 => 20, + 50 => 50, + 100 => 100, + ]), + ]), + ])->render(); + }, + // save dashboard content user preference + 'adminAfterDashboardOptionsUpdate' => function (?string $user_id = null): void { + if (!is_null($user_id)) { + dcCore::app()->auth?->user_prefs?->get(My::id())->put( + 'dashboard_item', + (int) $_POST[My::id() . '_dashboard_item'], + 'integer' + ); + } + }, + // list filters + 'adminFiltersListsV2' => function (ArrayObject $sorts): void { + $sorts[My::id()] = [ + My::name(), + [ + __('Group') => 'activity_group', + __('Action') => 'activity_action', + __('Date') => 'activity_dt', + __('Status') => 'activity_status', + ], + 'activity_dt', + 'desc', + [__('logs per page'), 30], + ]; + }, + // list columns user preference + 'adminColumnsListsV2' => function (ArrayObject $cols): void { + $cols[My::id()] = [ + My::name(), + [ + 'activity_group' => [true, __('Group')], + 'activity_action' => [true, __('Action')], + 'activity_dt' => [true, __('Date')], + 'activity_status' => [false, __('Status')], + ], + ]; + }, + ]); + + return true; } } diff --git a/src/Combo.php b/src/Combo.php new file mode 100644 index 0000000..2c7da57 --- /dev/null +++ b/src/Combo.php @@ -0,0 +1,72 @@ +formats->dump() as $format) { + $combo[$format->name] = $format->id; + } + + return $combo; + } + + /** + * Get report intervals. + * + * @return array The intervals combo + */ + public static function interval(): array + { + return [ + __('every hour') => 3600, + __('every 2 hours') => 7200, + __('2 times by day') => 43200, + __('every day') => 86400, + __('every 2 days') => 172800, + __('every week') => 604800, + ]; + } + + /** + * Get obsolete period. + * + * @return array The obsolete period combo + */ + public static function obselete(): array + { + return [ + __('every hour') => 3600, + __('every 2 hours') => 7200, + __('2 times by day') => 43200, + __('every day') => 86400, + __('every 2 days') => 172800, + __('every week') => 604800, + __('every 2 weeks') => 1209600, + __('every 4 weeks') => 2419200, + ]; + } +} diff --git a/src/Config.php b/src/Config.php index f5881e2..f4c2f6c 100644 --- a/src/Config.php +++ b/src/Config.php @@ -10,232 +10,218 @@ * @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); -$super = dcCore::app()->auth->isSuperAdmin() && !empty($_REQUEST['super']); -$redir = empty($_REQUEST['redir']) ? dcCore::app()->admin->list->getURL() . '#plugins' : $_REQUEST['redir']; +namespace Dotclear\Plugin\activityReport; -if ($super) { - dcCore::app()->activityReport->setGlobal(); -} +use dcAuth; +use dcCore; +use dcPage; +use dcNsProcess; +use Dotclear\Helper\Date; +use Dotclear\Helper\Html\Form\{ + Checkbox, + Div, + Hidden, + Input, + Label, + Note, + Para, + Select, + Text +}; +use Exception; -$combo_interval = [ - __('every hour') => 3600, - __('every 2 hours') => 7200, - __('2 times by day') => 43200, - __('every day') => 86400, - __('every 2 days') => 172800, - __('every week') => 604800, -]; +/** + * Config process. + */ +class Config extends dcNsProcess +{ + public static function init(): bool + { + static::$init == defined('DC_CONTEXT_ADMIN') + && defined('ACTIVITY_REPORT') + && My::phpCompliant() + && dcCore::app()->auth?->check(dcCore::app()->auth->makePermissions([ + dcAuth::PERMISSION_ADMIN, + ]), dcCore::app()->blog?->id); -$combo_obselete = [ - __('every hour') => 3600, - __('every 2 hours') => 7200, - __('2 times by day') => 43200, - __('every day') => 86400, - __('every 2 days') => 172800, - __('every week') => 604800, - __('every 2 weeks') => 1209600, - __('every 4 weeks') => 2419200, -]; + return static::$init; + } -$combo_format = [ - __('Plain text') => 'plain', - __('HTML') => 'html', -]; - -if (!empty($_POST['save'])) { - try { - dcCore::app()->activityReport->setSetting('active', !empty($_POST['active'])); - if (in_array($_POST['interval'], $combo_interval)) { - dcCore::app()->activityReport->setSetting('interval', (int) $_POST['interval']); + public static function process(): bool + { + if (!static::$init) { + return false; } - if (in_array($_POST['obsolete'], $combo_obselete)) { - dcCore::app()->activityReport->setSetting('obsolete', (int) $_POST['obsolete']); + + if (empty($_POST['save'])) { + return true; } - dcCore::app()->activityReport->setSetting('mailinglist', explode(';', $_POST['mailinglist'])); - dcCore::app()->activityReport->setSetting('mailformat', isset($_POST['mailformat']) && $_POST['mailformat'] == 'html' ? 'html' : 'plain'); - dcCore::app()->activityReport->setSetting('dateformat', html::escapeHTML($_POST['dateformat'])); - dcCore::app()->activityReport->setSetting('requests', $_POST['requests'] ?? []); - dcCore::app()->activityReport->setSetting('blogs', $_POST['blogs'] ?? []); - if (!empty($_POST['send_report_now'])) { - dcCore::app()->activityReport->needReport(true); + try { + $s = ActivityReport::instance()->settings; - dcAdminNotices::addSuccessNotice( - __('Report successfully sent.') + $s->set('feed_active', !empty($_POST['feed_active'])); + if (in_array($_POST['interval'], Combo::interval())) { + $s->set('interval', (int) $_POST['interval']); + } + if (in_array($_POST['obsolete'], Combo::obselete())) { + $s->set('obsolete', (int) $_POST['obsolete']); + } + $s->set('mailinglist', explode(';', $_POST['mailinglist'])); + $s->set('mailformat', isset($_POST['mailformat']) && $_POST['mailformat'] == 'html' ? 'html' : 'plain'); + $s->set('dateformat', $_POST['dateformat']); + $s->set('requests', $_POST['requests'] ?? []); + + dcPage::addSuccessNotice( + __('Configuration successfully updated.') ); - } - if (!empty($_POST['delete_report_now'])) { - dcCore::app()->activityReport->deleteLogs(); - dcAdminNotices::addSuccessNotice( - __('Logs successfully deleted.') + if (!empty($_POST['send_report_now'])) { + ActivityReport::instance()->needReport(true); + + dcPage::addSuccessNotice( + __('Report successfully sent.') + ); + } + if (!empty($_POST['delete_report_now'])) { + ActivityReport::instance()->deleteLogs(); + + dcPage::addSuccessNotice( + __('Logs successfully deleted.') + ); + } + + dcCore::app()->adminurl?->redirect('admin.plugins', [ + 'module' => My::id(), + 'conf' => 1, + ]); + } catch (Exception $e) { + dcCore::app()->error->add($e->getMessage()); + } + + return true; + } + + public static function render(): void + { + if (!static::$init) { + return; + } + + $s = ActivityReport::instance()->settings; + + if (!$s->lastreport) { + $last_report = __('never'); + $next_report = __('on new activity'); + } else { + $last_report = Date::str( + dcCore::app()->blog?->settings->get('system')->get('date_format') . ', ' . dcCore::app()->blog?->settings->get('system')->get('time_format'), + $s->lastreport, + dcCore::app()->auth?->getInfo('user_tz') + ); + $next_report = Date::str( + dcCore::app()->blog?->settings->get('system')->get('date_format') . ', ' . dcCore::app()->blog?->settings->get('system')->get('time_format'), + $s->interval + $s->lastreport, + dcCore::app()->auth?->getInfo('user_tz') ); } - dcAdminNotices::addSuccessNotice( - __('Configuration successfully updated.') - ); - dcCore::app()->adminurl->redirect('admin.plugins', ['module' => basename(__DIR__), 'conf' => 1, 'super' => $super]); - } catch (Exception $e) { - dcCore::app()->error->add($e->getMessage()); + if (!ActivityReport::hasMailer()) { + echo '

    ' . + __('This server has no mail function, activityReport does not send email report.') . + '

    '; + } + + echo + (new Div())->class('two-boxes')->separator('')->items([ + (new Div())->class('fieldset box odd')->items([ + (new Text('h4', __('Mail report'))), + (new Para())->items([ + (new Label(__('Send report:'), Label::OUTSIDE_LABEL_BEFORE))->for('interval'), + (new Select('interval'))->default((string) $s->interval)->items(Combo::interval()), + ]), + (new Para())->items([ + (new Label(__('Recipients:'), Label::OUTSIDE_LABEL_BEFORE))->for('mailinglist'), + (new Input('mailinglist'))->size(60)->maxlenght(255)->value(implode(';', $s->mailinglist)), + ]), + (new Note())->class('form-note')->text(__('Separate multiple email addresses with a semicolon ";"')), + (new Note())->class('form-note')->text(__('Leave it empty to disable mail report.')), + (new Para())->items([ + (new Label(__('Date format:'), Label::OUTSIDE_LABEL_BEFORE))->for('dateformat'), + (new Input('dateformat'))->size(60)->maxlenght(255)->value($s->dateformat), + ]), + (new Note())->class('form-note')->text(__('Use Dotclear date formaters. ex: %B %d at %H:%M')), + (new Para())->items([ + (new Label(__('Report format:'), Label::OUTSIDE_LABEL_BEFORE))->for('mailformat'), + (new Select('mailformat'))->default($s->mailformat)->items(Combo::mailformat()), + ]), + (new Text( + 'ul', + '
  • ' . __('Last report by email:') . ' ' . $last_report . '
  • ' . + '
  • ' . __('Next report by email:') . ' ' . $next_report . '
  • ' + )), + ]), + (new Div())->class('fieldset box even')->items([ + (new Text('h4', __('Feeds'))), + (new Para())->items([ + (new Checkbox('feed_active', $s->feed_active))->value(1), + (new Label(__('Enable activity feed'), Label::OUTSIDE_LABEL_AFTER))->for('feed_active')->class('classic'), + ]), + (new Text( + 'ul', + '
  • ' . __('RSS feed') . ' ' . + '' . + __('Rss2 activities feed') . '
  • ' . + '
  • ' . __('Atom feed') . ' ' . + '' . + __('Atom activities feed') . '
  • ' + )), + ]), + ])->render(); + + $i = 0; + $g = [ + (new Text('h4', __('Activities'))), + (new Text('p', __('Select actions by activity type to add to report'))), + ]; + foreach (ActivityReport::instance()->groups->dump() as $group_id => $group) { + $a = []; + $a[] = (new Text('h5', __($group->title))); + foreach ($group->dump() as $action_id => $action) { + $a[] = (new Para())->items([ + (new Checkbox( + ['requests[' . $group_id . '][' . $action_id . ']', 'requests_' . $group_id . '_' . $action_id . '_'], + isset($s->requests[$group_id][$action_id]) + ))->value(1), + (new Label(__($action->title), Label::OUTSIDE_LABEL_AFTER))->for( + 'requests_' . $group_id . '_' . $action_id . '_' + )->class('classic'), + ]); + } + $g[] = (new Div())->class('fieldset box')->items($a); + } + echo + (new Div('setting_report'))->class('fieldset one-box')->items($g)->render(); + + echo + (new Div('settings'))->class('fieldset')->items([ + (new Text('h4', __('Maintenance'))), + (new Para())->items([ + (new Label(__('Automatic cleaning of old logs:'), Label::OUTSIDE_LABEL_BEFORE))->for('obselete'), + (new Select('obselete'))->default((string) $s->obsolete)->items(Combo::obselete()), + ]), + (new Para())->items([ + (new Checkbox('send_report_now'))->value(1), + (new Label(__('Send report now'), Label::OUTSIDE_LABEL_AFTER))->for('send_report_now')->class('classic'), + ]), + (new Para())->items([ + (new Checkbox('delete_report_now'))->value(1), + (new Label(__('Delete all logs now'), Label::OUTSIDE_LABEL_AFTER))->for('delete_report_now')->class('classic'), + ]), + ])->render(); } } - -$last_report_ts = dcCore::app()->activityReport->getSetting('lastreport'); -if (!$last_report_ts) { - $last_report = __('never'); - $next_report = __('on new activity'); -} else { - $last_report = dt::str( - dcCore::app()->blog->settings->system->date_format . ', ' . dcCore::app()->blog->settings->system->time_format, - $last_report_ts, - dcCore::app()->auth->getInfo('user_tz') - ); - $next_report = dt::str( - dcCore::app()->blog->settings->system->date_format . ', ' . dcCore::app()->blog->settings->system->time_format, - (int) dcCore::app()->activityReport->getSetting('interval') + $last_report_ts, - dcCore::app()->auth->getInfo('user_tz') - ); -} -$emails = implode(';', dcCore::app()->activityReport->getSetting('mailinglist')); - -echo '

    ' . ($super ? __('All blogs') : __('Current blog')) . '

    '; - -if (dcCore::app()->auth->isSuperAdmin()) { - echo sprintf( - '

    %s

    ', - dcCore::app()->adminurl->get('admin.plugins', ['module' => basename(__DIR__), 'conf' => 1, 'super' => !$super]), - sprintf(__('Configure activity report for %s'), $super ? __('current blog') : __('all blogs')) - ); -} -if (!activityReport::hasMailer()) { - echo '

    ' . - __('This server has no mail function, activityReport does not send email report.') . - '

    '; -} - -echo ' -

    ' . __('Settings') . '

    -
    - -

    - -

    ' . -form::combo('obsolete', $combo_obselete, dcCore::app()->activityReport->getSetting('obsolete')) . '

    - -

    -

    ' . __('Use Dotclear date formaters. ex: %B %d at %H:%M') . '

    ' . - -form::hidden(['super'], $super); - -if (!$super) { - echo - '

    ' . __('RSS feed') . '' . - '' . - __('Rss2 feed for activity on this blog') . '
    ' . - '' . __('Atom feed') . '' . - '' . - __('Atom feed for activity on this blog') . '

    '; -} -echo ' -
    - -

    ' . -form::combo('interval', $combo_interval, dcCore::app()->activityReport->getSetting('interval')) . '

    - -

    -

    ' . __('Separate multiple email addresses with a semicolon ";"') . '

    - -

    ' . -form::combo('mailformat', $combo_format, dcCore::app()->activityReport->getSetting('mailformat')) . '

    - -
      -
    • ' . __('Last report by email:') . ' ' . $last_report . '
    • -
    • ' . __('Next report by email:') . ' ' . $next_report . '
    • -
    -

    -

    '; - -if ($super) { - echo ' -

    ' . __('Blogs') . '

    -

    ' . __('Select blogs to add to report') . '

    '; - - $i = $j = 0; - $selected_blogs = dcCore::app()->activityReport->getSetting('blogs'); - $blogs = dcCore::app()->getBlogs(); - $num_blogs = $blogs->count(); - while ($blogs->fetch()) { - $blog_id = dcCore::app()->con->escape($blogs->blog_id); - - echo ' -
    -

    -
    '; - - $i++; - } - echo '
    '; -} else { - echo form::hidden('blogs[0]', dcCore::app()->blog->id); -} -echo ' -

    ' . __('Report') . '

    -

    ' . __('Select actions by activity type to add to report') . '

    '; - -$groups = dcCore::app()->activityReport->getGroups(); -$blog_request = dcCore::app()->activityReport->getSetting('requests'); - -$i = 0; -foreach ($groups as $group_id => $group) { - echo '
    ' . __($group['title']) . '
    '; - - foreach ($group['actions'] as $action_id => $action) { - echo ' -

    '; - } - echo '
    '; -} -echo '
    '; - -if (1) { - echo ' -

    ' . __('Special') . '

    - -

    - -

    - -
    '; -} - -dcCore::app()->activityReport->unsetGlobal(); diff --git a/src/Context.php b/src/Context.php new file mode 100644 index 0000000..6878204 --- /dev/null +++ b/src/Context.php @@ -0,0 +1,71 @@ +ctx || !dcCore::app()->ctx->exists('activityreports')) { + return ''; + } + + $group = dcCore::app()->ctx->__get('activityreports')->activity_group; + $action = dcCore::app()->ctx->__get('activityreports')->activity_action; + + if (!ActivityReport::instance()->groups->get($group)->has($action)) { + return ''; + } + + return __(ActivityReport::instance()->groups->get($group)->get($action)->title); + } + + /** + * Parse content. + * + * @return string The parsed content + */ + public static function parseContent(): string + { + if (!dcCore::app()->ctx || !dcCore::app()->ctx->exists('activityreports')) { + return ''; + } + + $group = dcCore::app()->ctx->__get('activityreports')->activity_group; + $action = dcCore::app()->ctx->__get('activityreports')->activity_action; + $logs = json_decode((string) dcCore::app()->ctx->__get('activityreports')->activity_logs, true); + + if (!is_array($logs) || !ActivityReport::instance()->groups->get($group)->has($action)) { + return ''; + } + + dcCore::app()->initWikiComment(); + + return dcCore::app()->wikiTransform(vsprintf( + __(ActivityReport::instance()->groups->get($group)->get($action)->message), + $logs + )); + } +} diff --git a/src/Format.php b/src/Format.php new file mode 100644 index 0000000..02ee987 --- /dev/null +++ b/src/Format.php @@ -0,0 +1,81 @@ +name = $format['name'] ?? __('Plain text'); + $this->blog_title = $format['blog_title'] ?? "\n--- %TEXT% ---\n"; + $this->group_title = $format['group_title'] ?? "\n-- %TEXT% --\n\n"; + $this->group_open = $format['group_open'] ?? ''; + $this->group_close = $format['group_close'] ?? ''; + $this->action = $format['action'] ?? "- %TIME% : %TEXT%\n"; + $this->error = $format['error'] ?? '%TEXT%'; + $this->period_title = $format['period_title'] ?? "%TEXT%\n"; + $this->period_open = $format['period_open'] ?? ''; + $this->period_close = $format['period_close'] ?? ''; + $this->info = $format['info'] ?? "%TEXT%\n"; + $this->page = $format['page'] ?? "%PERIOD%\n-----------------------------------------------------------\n%TEXT%"; + } +} diff --git a/src/Formats.php b/src/Formats.php index 94ed062..9ab2007 100644 --- a/src/Formats.php +++ b/src/Formats.php @@ -10,52 +10,63 @@ * @copyright Jean-Christian Denis * @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html */ -$format = [ - 'html' => [ - 'blog_title' => '

    %TEXT%

    ', - 'blog_open' => '', - 'blog_close' => '', - 'group_title' => '

    %TEXT%

    ', - 'group_open' => '
      ', - 'group_close' => '
    ', - 'action' => '
  • %TIME%
    %TEXT%
  • ', - 'error' => '

    %TEXT%

    ', - 'period_title' => '

    %TEXT%

    ', - 'period_open' => '
      ', - 'period_close' => '
    ', - 'info' => '
  • %TEXT%
  • ', - 'page' => '' . "\n" . - '' . "\n" . - '' . __('Activity report') . '' . - '' . - '' . - '
    %PERIOD%
    %TEXT%
    ' . - '

    Powered by activityReport

    ' . - '', - ], - 'plain' => [ - 'blog_title' => "\n--- %TEXT% ---\n", - 'blog_open' => '', - 'blog_close' => '', - 'group_title' => "\n-- %TEXT% --\n\n", - 'group_open' => '', - 'group_close' => '', - 'action' => "- %TIME% : %TEXT%\n", - 'error' => '%TEXT%', - 'period_title' => "%TEXT%\n", - 'period_open' => '', - 'period_close' => '', - 'info' => "%TEXT%\n", - 'page' => "%PERIOD%\n-----------------------------------------------------------\n%TEXT%", - ], -]; +declare(strict_types=1); + +namespace Dotclear\Plugin\activityReport; + +/** + * Email report formats stack. + */ +class Formats +{ + /** @var array $stack The formats stack */ + private array $stack = []; + + /** + * Chek if a format exists. + * + * @param string $id The format ID + * + * @return bool True if it exists + */ + public function has(string $id): bool + { + return isset($this->stack[$id]); + } + + /** + * Add a format. + * + * @param Format $format The format object + * + * @return Formats The formats instance + */ + public function add(Format $format): Formats + { + $this->stack[$format->id] = $format; + + return $this; + } + + /** + * Get a format. + * + * @param string $id The format ID + * + * @return Format The format descriptor + */ + public function get(string $id): Format + { + return $this->stack[$id] ?? new Format('plain', []); + } + + /** + * Get all formats. + * + * @return array The formats stack + */ + public function dump(): array + { + return $this->stack; + } +} diff --git a/src/Frontend.php b/src/Frontend.php index 4f1b726..9597759 100644 --- a/src/Frontend.php +++ b/src/Frontend.php @@ -10,165 +10,41 @@ * @copyright Jean-Christian Denis * @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html */ -if (!defined('DC_RC_PATH')) { - return null; -} -if (!defined('ACTIVITY_REPORT_V2')) { - return null; -} +declare(strict_types=1); -dcCore::app()->tpl->setPath(dcCore::app()->tpl->getPath(), __DIR__ . '/default-templates/tpl'); -dcCore::app()->tpl->addBlock('activityReports', ['activityReportPublicTpl','activityReports']); -dcCore::app()->tpl->addValue('activityReportFeedID', ['activityReportPublicTpl','activityReportFeedID']); -dcCore::app()->tpl->addValue('activityReportTitle', ['activityReportPublicTpl','activityReportTitle']); -dcCore::app()->tpl->addValue('activityReportDate', ['activityReportPublicTpl','activityReportDate']); -dcCore::app()->tpl->addValue('activityReportContent', ['activityReportPublicTpl','activityReportContent']); +namespace Dotclear\Plugin\activityReport; -class activityReportPublicUrl extends dcUrlHandlers +use dcCore; +use dcNsProcess; + +/** + * Front end process. + */ +class Frontend extends dcNsProcess { - public static function feed(?string $args): void + public static function init(): bool { - if (!preg_match('/^(atom|rss2)\/(.+)$/', $args, $m)) { - self::p404(); + static::$init = defined('ACTIVITY_REPORT') + && My::phpCompliant() + && My::isInstalled(); - return; - } - if (!defined('ACTIVITY_REPORT_V2')) { - self::p404(); + return static::$init; + } - return; - } - if (!dcCore::app()->activityReport->getSetting('active')) { - self::p404(); - - return; - } - $mime = $m[1] == 'atom' ? 'application/atom+xml' : 'application/xml'; - - if (false === dcCore::app()->activityReport->checkUserCode($m[2])) { - self::p404(); - - return; + public static function process(): bool + { + if (!static::$init) { + return false; } - dcCore::app()->ctx->nb_entry_per_page = (int) dcCore::app()->blog->settings->system->nb_post_per_feed; - dcCore::app()->ctx->short_feed_items = (int) dcCore::app()->blog->settings->system->short_feed_items; + dcCore::app()->tpl->setPath(dcCore::app()->tpl->getPath(), implode(DIRECTORY_SEPARATOR, [My::root(), 'default-templates', 'tpl'])); - header('X-Robots-Tag: ' . context::robotsPolicy(dcCore::app()->blog->settings->system->robots_policy, '')); - self::serveDocument('activityreport-' . $m[1] . '.xml', $mime); - } -} - -class activityReportPublicTpl -{ - public static function activityReports($attr, $content) - { - $lastn = 0; - if (isset($attr['lastn'])) { - $lastn = abs((int) $attr['lastn']) + 0; - } - - $p = '$_page_number = dcCore::app()->public->getPageNumber(); if ($_page_number < 1) { $_page_number = 1; }' . "\n\$params = array();\n"; - - if ($lastn > 0) { - $p .= "\$params['limit'] = " . $lastn . ";\n"; - } else { - $p .= "\$params['limit'] = dcCore::app()->ctx->nb_entry_per_page;\n"; - } - - if (!isset($attr['ignore_pagination']) || $attr['ignore_pagination'] == '0') { - $p .= "\$params['limit'] = array(((\$_page_number-1)*\$params['limit']),\$params['limit']);\n"; - } else { - $p .= "\$params['limit'] = array(0, \$params['limit']);\n"; - } - - return - "ctx->activityreport_params = $params; ' . "\n" . - 'dcCore::app()->ctx->activityreports = dcCore::app()->activityReport->getLogs($params); unset($params); ' . "\n" . - 'while (dcCore::app()->ctx->activityreports->fetch()) : ?>' . $content . 'ctx->pop("activityreports"); dcCore::app()->ctx->pop("activityreport_params"); ' . "\n" . - '?>'; - } - - public static function activityReportFeedID($attr) - { - return - 'urn:md5:ctx->activityreports->blog_id.' . - 'dcCore::app()->ctx->activityreports->activity_id.dcCore::app()->ctx->activityreports->activity_dt); ' . - '?>'; - } - - public static function activityReportTitle($attr) - { - $f = dcCore::app()->tpl->getFilters($attr); - - return ''; - } - - public static function activityReportContent($attr) - { - $f = dcCore::app()->tpl->getFilters($attr); - - return ''; - } - - public static function activityReportDate($attr) - { - $format = ''; - if (!empty($attr['format'])) { - $format = addslashes($attr['format']); - } - - $iso8601 = !empty($attr['iso8601']); - $rfc822 = !empty($attr['rfc822']); - - $f = dcCore::app()->tpl->getFilters($attr); - - if ($rfc822) { - return 'ctx->activityreports->activity_dt),dcCore::app()->blog->settings->system->blog_timezone)') . '; ?>'; - } elseif ($iso8601) { - return 'ctx->activityreports->activity_dt),dcCore::app()->blog->settings->system->blog_timezone)') . '; ?>'; - } elseif (!empty($format)) { - return 'ctx->activityreports->activity_dt)") . '; ?>'; - } - - return 'blog->settings->system->date_format,dcCore::app()->ctx->activityreports->activity_dt)') . '; ?>'; - } -} - -class activityReportContext -{ - public static function parseTitle() - { - $groups = dcCore::app()->activityReport->getGroups(); - - $group = dcCore::app()->ctx->activityreports->activity_group; - $action = dcCore::app()->ctx->activityreports->activity_action; - - if (!empty($groups[$group]['actions'][$action]['title'])) { - return __($groups[$group]['actions'][$action]['title']); - } - - return ''; - } - - public static function parseContent() - { - $groups = dcCore::app()->activityReport->getGroups(); - - $group = dcCore::app()->ctx->activityreports->activity_group; - $action = dcCore::app()->ctx->activityreports->activity_action; - $logs = dcCore::app()->ctx->activityreports->activity_logs; - $logs = dcCore::app()->activityReport->decode($logs); - - if (!empty($groups[$group]['actions'][$action]['msg'])) { - dcCore::app()->initWikiComment(); - - return dcCore::app()->wikiTransform(vsprintf(__($groups[$group]['actions'][$action]['msg']), $logs)); - } - - return ''; + dcCore::app()->tpl->addBlock('activityReports', [Template::class, 'activityReports']); + dcCore::app()->tpl->addValue('activityReportFeedID', [Template::class, 'activityReportFeedID']); + dcCore::app()->tpl->addValue('activityReportTitle', [Template::class, 'activityReportTitle']); + dcCore::app()->tpl->addValue('activityReportDate', [Template::class, 'activityReportDate']); + dcCore::app()->tpl->addValue('activityReportContent', [Template::class, 'activityReportContent']); + + return true; } } diff --git a/src/Group.php b/src/Group.php new file mode 100644 index 0000000..61f3617 --- /dev/null +++ b/src/Group.php @@ -0,0 +1,82 @@ + $stack The actions stack */ + private array $stack = []; + + /** + * Constructor sets group description. + * + * @param string $id The group ID + * @param string $title The group title + */ + public function __construct(public readonly string $id, public readonly string $title) + { + } + + /** + * Chek if a action exists. + * + * @param string $id The action ID + * + * @return bool True if it exists + */ + public function has(string $id): bool + { + return isset($this->stack[$id]); + } + + /** + * Add an action. + * + * @param Action $action The action object + * + * @return Group The group instance + */ + public function add(Action $action): Group + { + $this->stack[$action->id] = $action; + + return $this; + } + + /** + * Get an action. + * + * @param string $id The action ID + * + * @return Action The action descriptor + */ + public function get(string $id): Action + { + return $this->stack[$id] ?? new Action($id, 'undefined', 'undefined', 'undefined', null); + } + + /** + * Get all actions. + * + * @return array The actions stack + */ + public function dump(): array + { + return $this->stack; + } +} diff --git a/src/Groups.php b/src/Groups.php new file mode 100644 index 0000000..742838e --- /dev/null +++ b/src/Groups.php @@ -0,0 +1,72 @@ + $stack The actions groups stack */ + private array $stack = []; + + /** + * Chek if a group exists. + * + * @param string $id The group ID + * + * @return bool True if it exists + */ + public function has(string $id): bool + { + return isset($this->stack[$id]); + } + + /** + * Add a group. + * + * @param Group $group The group object + * + * @return Groups The groups instance + */ + public function add(Group $group): Groups + { + $this->stack[$group->id] = $group; + + return $this; + } + + /** + * Get a group. + * + * @param string $id The group ID + * + * @return Group The group descriptor + */ + public function get(string $id): Group + { + return $this->stack[$id] ?? new Group($id, 'undefined'); + } + + /** + * Get all groups. + * + * @return array The groups stack + */ + public function dump(): array + { + return $this->stack; + } +} diff --git a/src/Install.php b/src/Install.php index 4c94e7c..e4f0efd 100644 --- a/src/Install.php +++ b/src/Install.php @@ -10,53 +10,87 @@ * @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\activityReport; + +use dbStruct; +use dcCore; +use dcNsProcess; +use Dotclear\Database\Statement\{ + DropStatement, + TruncateStatement +}; +use Exception; + +/** + * Install process. + */ +class Install extends dcNsProcess +{ + public static function init(): bool + { + static::$init = defined('DC_CONTEXT_ADMIN') + && My::phpCompliant() + && dcCore::app()->newVersion(My::id(), dcCore::app()->plugins->moduleInfo(My::id(), 'version')); + + return static::$init; + } + + public static function process(): bool + { + if (!static::$init) { + return false; + } + + try { + self::beforeGrowUp(); + + $s = new dbStruct(dcCore::app()->con, dcCore::app()->prefix); + $s->{My::ACTIVITY_TABLE_NAME} + ->activity_id('bigint', 0, false) + ->activity_type('varchar', 32, false, "'" . My::id() . "'") + ->blog_id('varchar', 32, true) + ->activity_group('varchar', 32, false) + ->activity_action('varchar', 32, false) + ->activity_logs('text', 0, false) + ->activity_dt('timestamp', 0, false, 'now()') + ->activity_status('smallint', 0, false, 0) + + ->primary('pk_activity', 'activity_id') + ->index('idx_activity_type', 'btree', 'activity_type') + ->index('idx_activity_blog_id', 'btree', 'blog_id') + ->index('idx_activity_action', 'btree', 'activity_group', 'activity_action') + ->index('idx_activity_status', 'btree', 'activity_status'); + + (new dbStruct(dcCore::app()->con, dcCore::app()->prefix))->synchronize($s); + + return true; + } catch (Exception $e) { + dcCore::app()->error->add($e->getMessage()); + + return false; + } + } + + /** + * Do some action on previous version before install. + */ + private static function beforeGrowUp(): void + { + $current = dcCore::app()->getVersion('activityReport'); + + // sorry not sorry we restart from scratch + if ($current && version_compare($current, '3.0', '<')) { + $struct = new dbStruct(dcCore::app()->con, dcCore::app()->prefix); + + if ($struct->tableExists('activity')) { + (new TruncateStatement())->from(dcCore::app()->prefix . 'activity')->truncate(); + } + if ($struct->tableExists('activity_settings')) { + (new TruncateStatement())->from(dcCore::app()->prefix . 'activity_settings')->truncate(); + (new DropStatement())->from(dcCore::app()->prefix . 'activity_settings')->drop(); + } + } + } } - -if (!dcCore::app()->newVersion( - basename(__DIR__), - dcCore::app()->plugins->moduleInfo(basename(__DIR__), 'version') -)) { - return null; -} - -try { - $s = new dbStruct(dcCore::app()->con, dcCore::app()->prefix); - $s->{initActivityReport::ACTIVITY_TABLE_NAME} - ->activity_id('bigint', 0, false) - ->activity_type('varchar', 32, false, "'" . basename(__DIR__) . "'") - ->blog_id('varchar', 32, true) - ->activity_group('varchar', 32, false) - ->activity_action('varchar', 32, false) - ->activity_logs('text', 0, false) - ->activity_dt('timestamp', 0, false, 'now()') - ->activity_blog_status('smallint', 0, false, 0) - ->activity_super_status('smallint', 0, false, 0) - - ->primary('pk_activity', 'activity_id') - ->index('idx_activity_type', 'btree', 'activity_type') - ->index('idx_activity_blog_id', 'btree', 'blog_id') - ->index('idx_activity_action', 'btree', 'activity_group', 'activity_action') - ->index('idx_activity_blog_status', 'btree', 'activity_blog_status') - ->index('idx_activity_super_status', 'btree', 'activity_super_status'); - - $s->{initActivityReport::SETTING_TABLE_NAME} - ->setting_id('varchar', 64, false) - ->blog_id('varchar', 32, true) - ->setting_type('varchar', 32, false) - ->setting_value('text', 0, false) - - ->unique('uk_activity_setting', 'setting_id', 'blog_id', 'setting_type') - ->index('idx_activity_setting_blog_id', 'btree', 'blog_id') - ->index('idx_activity_setting_type', 'btree', 'setting_type'); - - $si = new dbStruct(dcCore::app()->con, dcCore::app()->prefix); - $changes = $si->synchronize($s); - - return true; -} catch (Exception $e) { - dcCore::app()->error->add($e->getMessage()); -} - -return false; diff --git a/src/Manage.php b/src/Manage.php index 1db35ec..c6e3693 100644 --- a/src/Manage.php +++ b/src/Manage.php @@ -10,83 +10,114 @@ * @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 (!defined('ACTIVITY_REPORT_V2')) { - return null; -} +namespace Dotclear\Plugin\activityReport; -dcPage::check(dcCore::app()->auth->makePermissions([ - dcAuth::PERMISSION_ADMIN, -])); +use ArrayObject; +use adminGenericFilter; +use dcAuth; +use dcCore; +use dcNsProcess; +use dcPage; +use Dotclear\Helper\Html\Form\{ + Form, + Hidden, + Para, + Submit, + Text +}; +use Exception; -$super = dcCore::app()->auth->isSuperAdmin() && !empty($_REQUEST['super']); +/** + * Manage process (admin logs list). + */ +class Manage extends dcNsProcess +{ + public static function init(): bool + { + static::$init = defined('DC_CONTEXT_ADMIN') + && defined('ACTIVITY_REPORT') + && My::phpCompliant() + && dcCore::app()->auth?->check(dcCore::app()->auth->makePermissions([ + dcAuth::PERMISSION_ADMIN, + ]), dcCore::app()->blog?->id); -if ($super) { - dcCore::app()->activityReport->setGlobal(); -} - -$logs = dcCore::app()->activityReport->getLogs([]); - -if ($super) { - $breadcrumb = [ - __('Current blog') => dcCore::app()->adminurl->get('admin.plugin.' . basename(__DIR__), ['super' => 0]), - '' . __('All blogs') . '' => '', - ]; -} else { - $breadcrumb = ['' . __('Current blog') . '' => '']; - if (dcCore::app()->auth->isSuperAdmin()) { - $breadcrumb[__('All blogs')] = dcCore::app()->adminurl->get('admin.plugin.' . basename(__DIR__), ['super' => 1]); + return static::$init; } -} -echo '' . __('Activity report') . '' . -dcPage::breadcrumb(array_merge([__('Activity report') => '', __('Logs') => ''], $breadcrumb), ['hl' => false]) . -dcPage::notices(); - -if ($logs->isEmpty()) { - echo '

    ' . __('No log') . '

    '; -} else { - echo ' -
    - - - - '; - if ($super) { - echo ''; - } - echo ''; - - while ($logs->fetch()) { - $action = dcCore::app()->activityReport->getGroups($logs->activity_group, $logs->activity_action); - - if (empty($action)) { - continue; + public static function process(): bool + { + if (!static::$init) { + return false; } - $off = $super && $logs->activity_blog_status == 1 ? ' offline' : ''; - $date = dt::str( - dcCore::app()->blog->settings->system->date_format . ', ' . dcCore::app()->blog->settings->system->time_format, - strtotime($logs->activity_dt), - dcCore::app()->auth->getInfo('user_tz') + if (!empty($_POST['delete_all_logs']) || !empty($_POST['delete_reported_logs'])) { + try { + ActivityReport::instance()->deleteLogs(!empty($_POST['delete_reported_logs'])); + dcPage::addSuccessNotice(__('Logs successfully deleted')); + dcCore::app()->adminurl?->redirect('admin.plugin.' . My::id()); + } catch (Exception $e) { + dcCore::app()->error->add($e->getMessage()); + } + } + + return true; + } + + public static function render(): void + { + if (!static::$init) { + return; + } + + $logs = $counter = $list = null; + $filter = new adminGenericFilter(dcCore::app(), My::id()); + $params = new ArrayObject($filter->params()); + + try { + $logs = ActivityReport::instance()->getLogs($params); + $counter = ActivityReport::instance()->getLogs($params, true); + if (!is_null($logs) && !is_null($counter)) { + $list = new ManageList(dcCore::app(), $logs, $counter->f(0)); + } + } catch (Exception $e) { + dcCore::app()->error->add($e->getMessage()); + } + + dcPage::openModule( + My::name(), + $filter->js((string) dcCore::app()->adminurl?->get('admin.plugin.' . My::id())) . + dcPage::jsJson(My::id(), ['confirm_delete' => __('Are you sure you want to delete logs?')]) . + dcPage::jsModuleLoad(My::id() . '/js/backend.js') . + + # --BEHAVIOR-- activityReportListHeader -- + dcCore::app()->callBehavior('activityReportListHeader') ); - $msg = vsprintf(__($action['msg']), dcCore::app()->activityReport->decode($logs->activity_logs)); - echo ' - - - - '; - if ($super) { - echo ''; + echo + dcPage::breadcrumb([ + __('Plugins') => '', + My::name() => '', + ]) . + dcPage::notices(); + + if (!is_null($list)) { + $filter->display('admin.plugin.' . My::id(), (new Hidden('p', My::id()))->render()); + $list->logsDisplay($filter, '%s'); } - echo ''; - } - echo '
    ' . __('Action') . '' . __('Message') . '' . __('Date') . '' . __('Blog') . '
    ' . __($action['title']) . '' . $msg . '' . $date . '' . $logs->blog_id . '
    '; -} -dcCore::app()->activityReport->unsetGlobal(); -echo ''; + if (!is_null($logs) && !$logs->isEmpty()) { + echo + (new Form('form-logs'))->method('post')->action(dcCore::app()->admin->getPageURL())->fields([ + (new Para())->class('right')->separator(' ')->items([ + (new Submit('delete_all_logs'))->class('delete')->value(__('Delete all aticivity logs')), + (new Submit('delete_reported_logs'))->class('delete')->value(__('Delete all allready reported logs')), + dcCore::app()->formNonce(false), + ]), + ])->render(); + } + + dcPage::closeModule(); + } +} diff --git a/src/ManageList.php b/src/ManageList.php new file mode 100644 index 0000000..424912c --- /dev/null +++ b/src/ManageList.php @@ -0,0 +1,102 @@ +rs || $this->rs->isEmpty()) { + if ($filter->show()) { + echo '

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

    '; + } else { + echo '

    ' . __('No log') . '

    '; + } + } else { + $pager = new dcPager((int) $filter->value('page'), (int) $this->rs_count, (int) $filter->value('nb'), 10); + $pager->var_page = 'page'; + + $html_block = '
    '; + + $cols = new ArrayObject([ + 'activity_group' => '', + 'activity_action' => '', + 'activity_logs' => '', + 'activity_date' => '', + 'activity_status' => '', + ]); + + $this->userColumns(My::id(), $cols); + + $html_block .= '' . implode(iterator_to_array($cols)) . '%s
    ' . ( + $filter->show() ? + sprintf(__('List of %s logs matching the filter.'), $this->rs_count) : + sprintf(__('List of %s logs.'), $this->rs_count) + ) . '
    ' . __('Group') . '' . __('Action') . '' . __('Message') . '' . __('Date') . '' . __('Status') . '
    %s
    '; + if ($enclose_block) { + $html_block = sprintf($enclose_block, $html_block); + } + $blocks = explode('%s', $html_block); + + echo $pager->getLinks() . $blocks[0]; + + while ($this->rs->fetch()) { + echo $this->logsLine(); + } + + echo $blocks[1] . $blocks[2] . $pager->getLinks(); + } + } + + private function logsLine(): string + { + $offline = (int) $this->rs->f('activity_status') == ActivityReport::STATUS_REPORTED ? ' offline' : ''; + $group = ActivityReport::instance()->groups->get($this->rs->f('activity_group')); + $action = $group->get($this->rs->f('activity_action')); + $message = json_decode((string) $this->rs->f('activity_logs'), true); + $message = $message[0] == 'undefined' ? __('undefined') : vsprintf(__($action->message), $message); + $date = Date::str( + dcCore::app()->blog?->settings->get('system')->get('date_format') . ', ' . dcCore::app()->blog?->settings->get('system')->get('time_format'), + (int) strtotime((string) $this->rs->f('activity_dt')), + dcCore::app()->auth?->getInfo('user_tz') + ); + $status = (int) $this->rs->f('activity_status') == ActivityReport::STATUS_PENDING ? __('pending') : __('reported'); + + $cols = new ArrayObject([ + 'activity_group' => '' . __($group->title) . '', + 'activity_action' => '' . __($action->title) . '', + 'activity_logs' => '' . $message . '', + 'activity_date' => '' . $date . '', + 'activity_status' => '' . $status . '', + ]); + + $this->userColumns(My::id(), $cols); + + return + '' . + implode(iterator_to_array($cols)) . + ''; + } +} diff --git a/src/My.php b/src/My.php new file mode 100644 index 0000000..fec69aa --- /dev/null +++ b/src/My.php @@ -0,0 +1,77 @@ +plugins->moduleInfo(self::id(), 'name')); + } + + /** + * This module root. + */ + public static function root(): string + { + return dirname(__DIR__); + } + + /** + * Check is module is trully installed. + * + * Required as table structrue has changed + */ + public static function isInstalled(): bool + { + return dcCore::app()->getVersion(self::id()) == dcCore::app()->plugins->moduleInfo(self::id(), 'version'); + } + + /** + * Check php version. + */ + public static function phpCompliant(): bool + { + return version_compare(phpversion(), self::PHP_MIN, '>='); + } +} diff --git a/src/Prepend.php b/src/Prepend.php index f38fd80..16fd939 100644 --- a/src/Prepend.php +++ b/src/Prepend.php @@ -10,30 +10,58 @@ * @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); -Clearbricks::lib()->autoload([ - 'activityReport' => __DIR__ . '/inc/class.activity.report.php', - 'activityReportBehaviors' => __DIR__ . '/inc/class.activity.report.behaviors.php', -]); +namespace Dotclear\Plugin\activityReport; -try { - if (!defined('ACTIVITY_REPORT_V2')) { - dcCore::app()->__set('activityReport', new activityReport()); +use dcCore; +use dcNsProcess; +use Exception; - dcCore::app()->url->register( - basename(__DIR__), - 'reports', - '^reports/((atom|rss2)/(.+))$', - ['activityReportPublicUrl', 'feed'] - ); +/** + * Prepend process. + */ +class Prepend extends dcNsProcess +{ + public static function init(): bool + { + static::$init = defined('DC_RC_PATH') + && My::phpCompliant() + && My::isInstalled(); - define('ACTIVITY_REPORT_V2', true); - - activityReportBehaviors::registerBehaviors(); + return static::$init; + } + + public static function process(): bool + { + if (!static::$init) { + return false; + } + + if (defined('ACTIVITY_REPORT')) { + return true; + } + + try { + // launch once activity report stuff + ActivityReport::instance(); + + // regirster activity feed URL + dcCore::app()->url->register( + My::id(), + 'reports', + '^reports/((atom|rss2)/(.+))$', + [UrlHandler::class, 'feed'] + ); + + // declare report open + define('ACTIVITY_REPORT', My::COMPATIBILITY_VERSION); + + // register predefined activities scan + ActivityBehaviors::register(); + } catch (Exception $e) { + } + + return true; } -} catch (Exception $e) { - //throw new Exception('Failed to launch activityReport'); } diff --git a/src/Settings.php b/src/Settings.php new file mode 100644 index 0000000..21136e5 --- /dev/null +++ b/src/Settings.php @@ -0,0 +1,113 @@ +blog === null) { + throw new Exception('Blog is not set'); + } + + $this->feed_active = (bool) ($this->get('feed_active') ?? false); + $this->obsolete = (int) ($this->get('obsolete') ?? 2419200); + $this->interval = (int) ($this->get('interval') ?? 86400); + $this->lastreport = (int) ($this->get('lastreport') ?? 0); + $this->mailinglist = (array) ($this->get('mailinglist') ?? []); + $this->mailformat = (string) ($this->get('mailformat') ?? 'plain'); + $this->dateformat = (string) ($this->get('dateformat') ?? '%Y-%m-%d %H:%M:%S'); + $this->requests = (array) ($this->get('requests') ?? []); + } + + /** + * Dump properties. + * + * @return array The settings properties + */ + public function dump(): array + { + $vars = get_class_vars(__CLASS__); + + return $vars ? $vars : []; + } + + /** + * Set a setting in database. + * + * Setting is modified in database but not in script ! + * + * @param string $key The setting key + * @param mixed $value The setting value + */ + public function set(string $key, mixed $value): void + { + if (property_exists($this, $key) && gettype($value) == gettype($this->{$key})) { + dcCore::app()->blog?->settings->get(My::id())->put( + $key, + $value, + gettype($value), + null, + true, + false + ); + } + } + + /** + * Get a setting from database. + * + * @param string $key The setting key + * + * @return mixed The setting value + */ + private function get(string $key): mixed + { + return dcCore::app()->blog?->settings->get(My::id())->get($key); + } +} diff --git a/src/Template.php b/src/Template.php new file mode 100644 index 0000000..419f771 --- /dev/null +++ b/src/Template.php @@ -0,0 +1,161 @@ +public->getPageNumber(); if ($_page_number < 1) { $_page_number = 1; }' . "\n\$params = new ArrayObject();\n"; + + if ($lastn > 0) { + $p .= "\$params['limit'] = " . $lastn . ";\n"; + } else { + $p .= "\$params['limit'] = dcCore::app()->ctx->nb_entry_per_page;\n"; + } + + if (!isset($attr['ignore_pagination']) || $attr['ignore_pagination'] == '0') { + $p .= "\$params['limit'] = array(((\$_page_number-1)*\$params['limit']),\$params['limit']);\n"; + } else { + $p .= "\$params['limit'] = array(0, \$params['limit']);\n"; + } + + return + "ctx->activityreport_params = $params; ' . "\n" . + 'dcCore::app()->ctx->activityreports = ' . ActivityReport::class . '::instance()->getLogs($params); unset($params); ' . "\n" . + 'while (dcCore::app()->ctx->activityreports->fetch()) : ?>' . $content . 'ctx->pop("activityreports"); dcCore::app()->ctx->pop("activityreport_params"); ' . "\n" . + '?>'; + } + + /** + * tpl:activityReportFeedID [attributes] : Activity report feed ID (tpl value) + * + * attributes: + * + * - any filters See self::getFilters() + * + * @param ArrayObject $attr The attributes + * + * @return string The code + */ + public static function activityReportFeedID(ArrayObject $attr): string + { + return + 'urn:md5:ctx->activityreports->blog_id.' . + 'dcCore::app()->ctx->activityreports->activity_id.dcCore::app()->ctx->activityreports->activity_dt); ' . + '?>'; + } + + /** + * tpl:activityReportTitle [attributes] : Activity report log title (tpl value) + * + * attributes: + * + * - any filters See self::getFilters() + * + * @param ArrayObject $attr The attributes + * + * @return string The code + */ + public static function activityReportTitle(ArrayObject $attr): string + { + $f = dcCore::app()->tpl->getFilters($attr); + + return ''; + } + + /** + * tpl:activityReportContent [attributes] : Activity report log message (tpl value) + * + * attributes: + * + * - any filters See self::getFilters() + * + * @param ArrayObject $attr The attributes + * + * @return string The code + */ + public static function activityReportContent(ArrayObject $attr): string + { + $f = dcCore::app()->tpl->getFilters($attr); + + return ''; + } + + /** + * tpl:activityReportDate [attributes] : Activity report log date (tpl value) + * + * attributes: + * + * - format Use Date::str() (if iso8601 nor rfc822 were specified default to %Y-%m-%d %H:%M:%S) + * - iso8601 (1|0) Use Date::iso8601() + * - rfc822 (1|0) Use Date::rfc822() + * - any filters See self::getFilters() + * + * @param ArrayObject $attr The attributes + * + * @return string The code + */ + public static function activityReportDate(ArrayObject $attr): string + { + $format = ''; + if (!empty($attr['format'])) { + $format = addslashes($attr['format']); + } + + $iso8601 = !empty($attr['iso8601']); + $rfc822 = !empty($attr['rfc822']); + + $f = dcCore::app()->tpl->getFilters($attr); + + if ($rfc822) { + return 'ctx->activityreports->activity_dt),dcCore::app()->blog->settings->system->blog_timezone)') . '; ?>'; + } elseif ($iso8601) { + return 'ctx->activityreports->activity_dt),dcCore::app()->blog->settings->system->blog_timezone)') . '; ?>'; + } elseif (!empty($format)) { + return 'ctx->activityreports->activity_dt)") . '; ?>'; + } + + return 'blog->settings->system->date_format,dcCore::app()->ctx->activityreports->activity_dt)') . '; ?>'; + } +} diff --git a/src/Uninstall.php b/src/Uninstall.php index def1c60..980420d 100644 --- a/src/Uninstall.php +++ b/src/Uninstall.php @@ -10,72 +10,78 @@ * @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\activityReport; + +use dcCore; +use dcNsProcess; +use Dotclear\Plugin\Uninstaller\Uninstaller; + +/** + * Uninstall process. + * + * Using plugin Uninstaller + */ +class Uninstall extends dcNsProcess +{ + 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; + } + + Uninstaller::instance() + ->addUserAction( + 'tables', + 'delete', + My::ACTIVITY_TABLE_NAME + ) + ->addUserAction( + 'settings', + 'delete_all', + My::id() + ) + ->addUserAction( + 'versions', + 'delete', + My::id() + ) + ->addUserAction( + 'plugins', + 'delete', + My::id() + ) + ->addDirectAction( + 'tables', + 'delete', + My::ACTIVITY_TABLE_NAME + ) + ->addDirectAction( + 'settings', + 'delete_all', + My::id() + ) + ->addDirectAction( + 'versions', + 'delete', + My::id() + ) + ->addDirectAction( + 'plugins', + 'delete', + My::id() + ) + ; + + // no custom action + return false; + } } - -$this->addUserAction( - /* type */ - 'tables', - /* action */ - 'delete', - /* ns */ - initActivityReport::ACTIVITY_TABLE_NAME, - /* description */ - sprintf(__('delete %s table'), '"activity"') -); - -$this->addUserAction( - /* type */ - 'tables', - /* action */ - 'delete', - /* ns */ - initActivityReport::SETTING_TABLE_NAME, - /* description */ - sprintf(__('delete %s table'), '"activity_setting"') -); - -$this->addUserAction( - /* type */ - 'plugins', - /* action */ - 'delete', - /* ns */ - basename(__DIR__), - /* description */ - __('delete plugin files') -); - -$this->addUserAction( - /* type */ - 'versions', - /* action */ - 'delete', - /* ns */ - basename(__DIR__), - /* description */ - __('delete the version number') -); - -$this->addDirectAction( - /* type */ - 'versions', - /* action */ - 'delete', - /* ns */ - basename(__DIR__), - /* description */ - sprintf(__('delete %s version number'), basename(__DIR__)) -); - -$this->addDirectAction( - /* type */ - 'plugins', - /* action */ - 'delete', - /* ns */ - basename(__DIR__), - /* description */ - sprintf(__('delete %s plugin files'), basename(__DIR__)) -); diff --git a/src/UrlHandler.php b/src/UrlHandler.php new file mode 100644 index 0000000..4b8a6cc --- /dev/null +++ b/src/UrlHandler.php @@ -0,0 +1,56 @@ +ctx + || !preg_match('/^(atom|rss2)\/(.+)$/', (string) $args, $m) + || !defined('ACTIVITY_REPORT') + || !ActivityReport::instance()->settings->feed_active + ) { + self::p404(); + } + + // get type of feed + $mime = $m[1] == 'atom' ? 'application/atom+xml' : 'application/xml'; + if (false === ActivityReport::instance()->checkUserCode($m[2])) { + self::p404(); + } + + // feed limits + dcCore::app()->ctx->__set('nb_entry_per_page', (int) dcCore::app()->blog?->settings->get('system')->get('nb_post_per_feed')); + dcCore::app()->ctx->__set('short_feed_items', (int) dcCore::app()->blog?->settings->get('system')->get('short_feed_items')); + + // serve activity feed template + header('X-Robots-Tag: ' . context::robotsPolicy(dcCore::app()->blog?->settings->get('system')->get('robots_policy'), '')); + self::serveDocument('activityreport-' . $m[1] . '.xml', $mime); + } +}