commit c54f9986c77d26e7b8361170fc269fe9c83f0cc6 Author: Jean-Christian Denis Date: Fri Sep 3 00:04:20 2021 +0200 Initial commit - version 1.0.1 from 2010.06.08 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d511905 --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/_admin.php b/_admin.php new file mode 100644 index 0000000..8143bf6 --- /dev/null +++ b/_admin.php @@ -0,0 +1,93 @@ +addItem( + __('Activity report'), + 'plugin.php?p=activityReport','index.php?pf=activityReport/icon.png', + preg_match('/plugin.php\?p=activityReport(&.*)?$/', + $_SERVER['REQUEST_URI']), + $core->auth->check('admin',$core->blog->id) +); + +# Dashboarditems +if ($core->activityReport->getSetting('dashboardItem')) +{ + $core->addBehavior( + 'adminDashboardHeaders', + array('activityReportAdmin','dashboardHeaders') + ); + $core->addBehavior( + 'adminDashboardItems', + array('activityReportAdmin','dashboardItems') + ); +} + +class activityReportAdmin +{ + # Add CSS to dashboardHeaders for items + public static function dashboardHeaders() + { + return + "\n \n". + " \n"; + } + + # Add report to dashboardItems + public static function dashboardItems($core, $__dashboard_items) + { + $r = $core->activityReport->getSetting('requests'); + $g = $core->activityReport->getGroups(); + + $p = array(); + $p['limit'] = 20; + $p['order'] = 'activity_dt DESC'; + $p['sql'] = $core->activityReport->requests2params($r); + + $res = ''; + $rs = $core->activityReport->getLogs($p); + if (!$rs->isEmpty()) + { + while($rs->fetch()) + { + $group = $rs->activity_group; + + if (!isset($g[$group])) continue; + + $res .= + '

'. + __($g[$group]['actions'][$rs->activity_action]['title']). + '

'. + vsprintf( + __($g[$group]['actions'][$rs->activity_action]['msg']), + $core->activityReport->decode($rs->activity_logs) + ). + '
'; + } + } + if (!empty($res)) + { + $__dashboard_items[1][] = + '

'.__('Activity report').'

'. + '
'.$res.'
'; + } + } +} +?> \ No newline at end of file diff --git a/_define.php b/_define.php new file mode 100644 index 0000000..4859123 --- /dev/null +++ b/_define.php @@ -0,0 +1,24 @@ +registerModule( + /* Name */ "Activity report", + /* Description*/ "Receive your blog activity by email, feed, or on dashboard", + /* Author */ "JC Denis", + /* Version */ '1.0.1', + /* Permissions */ 'admin', + /* Priority */ -1000000 +); + /* date */ #20100608 +?> \ No newline at end of file diff --git a/_install.php b/_install.php new file mode 100644 index 0000000..ae34c72 --- /dev/null +++ b/_install.php @@ -0,0 +1,71 @@ +plugins->moduleInfo('activityReport','version'); +$old_version = $core->getVersion('activityReport'); + +if (version_compare($old_version,$new_version,'>=')) {return;} + +try +{ + # Check DC version + if (version_compare(DC_VERSION,'2.2-beta','<')) + { + throw new Exception('translater requires Dotclear 2.2'); + } + + # Table + $s = new dbStruct($core->con,$core->prefix); + $s->activity + ->activity_id ('bigint',0,false) + ->activity_type ('varchar',32,false,"'activityReport'") + ->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->activity_setting + ->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($core->con,$core->prefix); + $changes = $si->synchronize($s); + + # Version + $core->setVersion('activityReport',$new_version); + + return true; +} +catch (Exception $e) +{ + $core->error->add($e->getMessage()); +} +return false; +?> \ No newline at end of file diff --git a/_prepend.php b/_prepend.php new file mode 100644 index 0000000..3abd00b --- /dev/null +++ b/_prepend.php @@ -0,0 +1,38 @@ +activityReport = new activityReport($core); + + $core->url->register( + 'activityReport', + 'reports', + '^reports/((atom|rss2)/(.+))$', + array('activityReportPublicUrl','feed') + ); + + define('ACTIVITY_REPORT',true); + + require_once dirname(__FILE__).'/inc/class.activity.report.behaviors.php'; +} +catch (Exception $e) { + //throw new Exception('Failed to launch activityReport'); +} +?> \ No newline at end of file diff --git a/_public.php b/_public.php new file mode 100644 index 0000000..40aef1e --- /dev/null +++ b/_public.php @@ -0,0 +1,174 @@ +tpl->setPath($core->tpl->getPath(),dirname(__FILE__).'/default-templates/tpl'); +$core->tpl->addBlock('activityReports',array('activityReportPublicTpl','activityReports')); +$core->tpl->addValue('activityReportFeedID',array('activityReportPublicTpl','activityReportFeedID')); +$core->tpl->addValue('activityReportTitle',array('activityReportPublicTpl','activityReportTitle')); +$core->tpl->addValue('activityReportDate',array('activityReportPublicTpl','activityReportDate')); +$core->tpl->addValue('activityReportContent',array('activityReportPublicTpl','activityReportContent')); + +class activityReportPublicUrl extends dcUrlHandlers +{ + public static function feed($args) + { + global $core, $_ctx; + + if (!preg_match('/^(atom|rss2)\/(.+)$/',$args,$m)) + { + self::p404(); + return; + } + if (!defined('ACTIVITY_REPORT')){ + self::p404(); + return; + } + if (!$core->activityReport->getSetting('active')) + { + self::p404(); + return; + } + $mime = $m[1] == 'atom' ? 'application/atom+xml' : 'application/xml'; + + if (false === $core->activityReport->checkUserCode($m[2])) { + self::p404(); + return; + } + + $_ctx->nb_entry_per_page = $core->blog->settings->system->nb_post_per_feed; + $_ctx->short_feed_items = $core->blog->settings->system->short_feed_items; + + header('X-Robots-Tag: '.context::robotsPolicy($core->blog->settings->system->robots_policy,'')); + self::serveDocument('activityreport-'.$m[1].'.xml',$mime); + return; + } +} + +class activityReportPublicTpl +{ + public static function activityReports($attr,$content) + { + $lastn = 0; + if (isset($attr['lastn'])) { + $lastn = abs((integer) $attr['lastn'])+0; + } + + $p = 'if (!isset($_page_number)) { $_page_number = 1; }'."\n\$params = array();\n"; + + if ($lastn > 0) { + $p .= "\$params['limit'] = ".$lastn.";\n"; + } else { + $p .= "\$params['limit'] = \$_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"; + } + + $res = + "activityreport_params = $params; '."\n". + '$_ctx->activityreports = $core->activityReport->getLogs($params); unset($params); '."\n". + 'while ($_ctx->activityreports->fetch()) : ?>'.$content.'activityreports = null; $_ctx->activityreport_params = null; '."\n". + "?>"; + + return $res; + } + + public static function activityReportFeedID($attr) + { + return + 'urn:md5:activityreports->blog_id.'. + '$_ctx->activityreports->activity_id.$_ctx->activityreports->activity_dt); '. + '?>'; + } + + public static function activityReportTitle($attr) + { + $f = $GLOBALS['core']->tpl->getFilters($attr); + return ''; + } + + public static function activityReportContent($attr) + { + $f = $GLOBALS['core']->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 = $GLOBALS['core']->tpl->getFilters($attr); + + if ($rfc822) { + return 'activityreports->activity_dt),\$core->blog->settings->system->blog_timezone)").'; ?>'; + } elseif ($iso8601) { + return 'activityreports->activity_dt),\$core->blog->settings->system->blog_timezone)").'; ?>'; + } elseif (!empty($format)) { + return 'activityreports->activity_dt)").'; ?>'; + } else { + return 'blog->settings->system->date_format,\$_ctx->activityreports->activity_dt)").'; ?>'; + } + } +} + +class activityReportContext +{ + public static function parseTitle() + { + global $core,$_ctx; + + $groups = $core->activityReport->getGroups(); + + $group = $_ctx->activityreports->activity_group; + $action = $_ctx->activityreports->activity_action; + + if (!empty($groups[$group]['actions'][$action]['title'])) { + return __($groups[$group]['actions'][$action]['title']); + } + return ''; + } + + public static function parseContent() + { + global $core,$_ctx; + + $groups = $core->activityReport->getGroups(); + + $group = $_ctx->activityreports->activity_group; + $action = $_ctx->activityreports->activity_action; + $logs = $_ctx->activityreports->activity_logs; + $logs = $core->activityReport->decode($logs); + + if (!empty($groups[$group]['actions'][$action]['msg'])) { + $core->initWikiComment(); + return $core->wikiTransform(vsprintf(__($groups[$group]['actions'][$action]['msg']),$logs)); + } + return ''; + } +} +?> \ No newline at end of file diff --git a/_uninstall.php b/_uninstall.php new file mode 100644 index 0000000..bb74757 --- /dev/null +++ b/_uninstall.php @@ -0,0 +1,56 @@ +addUserAction( + /* type */ 'tables', + /* action */ 'delete', + /* ns */ 'activity', + /* description */ sprintf(__('delete %s table'),'"activity"') +); + +$this->addUserAction( + /* type */ 'tables', + /* action */ 'delete', + /* ns */ 'activity_setting', + /* description */ sprintf(__('delete %s table'),'"activity_setting"') +); + +$this->addUserAction( + /* type */ 'plugins', + /* action */ 'delete', + /* ns */ 'activityReport', + /* description */ __('delete plugin files') +); + +$this->addUserAction( + /* type */ 'versions', + /* action */ 'delete', + /* ns */ 'activityReport', + /* description */ __('delete the version number') +); + +$this->addDirectAction( + /* type */ 'versions', + /* action */ 'delete', + /* ns */ 'activityReport', + /* description */ sprintf(__('delete %s version number'),'activityReport') +); + +$this->addDirectAction( + /* type */ 'plugins', + /* action */ 'delete', + /* ns */ 'activityReport', + /* description */ sprintf(__('delete %s plugin files'),'activityReport') +); +?> \ No newline at end of file diff --git a/default-templates/tpl/activityreport-atom.xml b/default-templates/tpl/activityreport-atom.xml new file mode 100644 index 0000000..edde1e3 --- /dev/null +++ b/default-templates/tpl/activityreport-atom.xml @@ -0,0 +1,33 @@ + + + + {{tpl:BlogName encode_xml="1"}}{{tpl:SysFeedSubtitle encode_xml="1"}} + {{tpl:BlogDescription encode_xml="1"}} + + + {{tpl:BlogUpdateDate iso8601="1"}} + + {{tpl:BlogEditor encode_xml="1"}} + + {{tpl:BlogFeedID}} + Dotclear + + + + + {{tpl:activityReportTitle encode_xml="1"}} + {{tpl:activityReportFeedID}} + {{tpl:activityReportDate iso8601="1"}} + activityReport module for Dotclear 2 + + {{tpl:activityReportContent absolute_urls="1" encode_xml="1"}} + + + + + + \ No newline at end of file diff --git a/default-templates/tpl/activityreport-rss2.xml b/default-templates/tpl/activityreport-rss2.xml new file mode 100644 index 0000000..32d46fa --- /dev/null +++ b/default-templates/tpl/activityreport-rss2.xml @@ -0,0 +1,33 @@ + + + + + {{tpl:BlogName encode_xml="1"}}{{tpl:SysFeedSubtitle encode_xml="1"}} + {{tpl:BlogURL}} + + {{tpl:BlogDescription encode_xml="1"}} + {{tpl:BlogLanguage}} + {{tpl:BlogUpdateDate rfc822="1"}} + {{tpl:BlogCopyrightNotice encode_xml="1"}} + http://blogs.law.harvard.edu/tech/rss + Dotclear + + + + + {{tpl:activityReportTitle encode_xml="1"}} + {{tpl:activityReportFeedID}} + {{tpl:activityReportDate rfc822="1"}} + activityReport module for Dotclear 2 + + {{tpl:activityReportContent absolute_urls="1" encode_xml="1"}} + + + + + + \ No newline at end of file diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..8bdd330 Binary files /dev/null and b/icon.png differ diff --git a/inc/class.activity.report.behaviors.php b/inc/class.activity.report.behaviors.php new file mode 100644 index 0000000..f095a7b --- /dev/null +++ b/inc/class.activity.report.behaviors.php @@ -0,0 +1,435 @@ +activityReport->addGroup('activityReport',__('ActivityReport messages')); + +$core->activityReport->addAction( + 'activityReport', + 'message', + __('Special messages'), + __('%s'), + 'messageActivityReport', + array('activityReportBehaviors','messageActivityReport') +); + +/* Blog +-------------------------*/ +$core->activityReport->addGroup('blog',__('Actions on blog')); + +# Not use as it is global : BEHAVIOR adminAfterBlogCreate in admin/blog.php + +# from BEHAVIOR adminAfterBlogUpdate in admin/blog_pref.php +$core->activityReport->addAction( + 'blog', + 'update', + __('updating blog'), + __('Blog was updated by "%s"'), + 'adminAfterBlogUpdate', + array('activityReportBehaviors','blogUpdate') +); + +# from BEHAVIOR publicHeadContent in template +$core->activityReport->addAction( + 'blog', + 'p404', + __('404 error'), + __('New 404 error page at "%s"'), + 'publicHeadContent', + array('activityReportBehaviors','blogP404') +); + + +/* Post +-------------------------*/ +$core->activityReport->addGroup('post',__('Actions on posts')); + +# 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 +$core->activityReport->addAction( + 'post', + 'create', + __('post creation'), + __('A new post called "%s" was created by "%s" at %s'), + 'adminAfterPostCreate', + array('activityReportBehaviors','postCreate') +); + +# Plugin contribute +# from BEHAVIOR publicAfterPostCreate in plugins/contribute/_public.php +$core->activityReport->addAction( + 'post', + 'create', + __('post creation'), + __('A new post called "%s" was created by "%s" at %s'), + 'publicAfterPostCreate', + array('activityReportBehaviors','postCreate') +); + +# from BEHAVIOR coreAfterPostUpdate in inc/core/class.dc.blog.php (DC2.2) +# duplicate adminAfterPostUpdate in admin/post.php +$core->activityReport->addAction( + 'post', + 'update', + __('updating post'), + __('Post called "%s" has been updated by "%s" at %s'), + 'adminAfterPostUpdate', + array('activityReportBehaviors','postUpdate') +); + +# from BEHAVIOR adminBeforePostDelete in admin/posts_actions.php +# from BEHAVIOR adminBeforePostDelete in admin/post.php +$core->activityReport->addAction( + 'post', + 'delete', + __('post deletion'), + __('Post called "%s" has been deleted by "%s"'), + 'adminBeforePostDelete', + array('activityReportBehaviors','postDelete') +); + +# Wrong attempt on passworded enrty +# from BEHAVIOR urlHandlerServeDocument in inc/public/lib.urlhandlers.php +$core->activityReport->addAction( + 'post', + 'protection', + __('Post protection'), + __('An attempt failed on a passworded post with password "%s" at "%s"'), + 'urlHandlerServeDocument', + array('activityReportBehaviors','postPasswordAttempt') +); + + + +/* Comment +-------------------------*/ +$core->activityReport->addGroup('comment',__('Actions on comments')); + +# from BEHAVIOR coreAfterCommentCreate in inc/core/class.dc.blog.php +# duplicate adminAfterCommentCreate in admin/comment.php +# duplicate publicAfterCommentCreate in inc/public/lib.urlhandlers.php +$core->activityReport->addAction( + 'comment', + 'create', + __('comment creation'), + __('A new comment was created by "%s" on post "%s" at %s'), + 'coreAfterCommentCreate', + array('activityReportBehaviors','commentCreate') +); + +# from BEHAVIOR coreAfterCommentUpdate in inc/core/class.dc.blog.php +# duplicate adminAfterCommentUpdate in admin/comment.php +$core->activityReport->addAction( + 'comment', + 'update', + __('updating comment'), + __('Comment has been updated by "%s" at %s'), + 'coreAfterCommentUpdate', + array('activityReportBehaviors','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 +$core->activityReport->addAction( + 'comment', + 'trackback', + __('trackback creation'), + __('A new trackback to "%" at "%s" was created on post "%s" at %s'), + 'coreAfterCommentCreate', + array('activityReportBehaviors','trackbackCreate') +); + + +/* Category +-------------------------*/ +$core->activityReport->addGroup('category',__('Actions on categories')); + +# from BEHAVIOR adminAfterCategoryCreate in admin/category.php +$core->activityReport->addAction( + 'category', + 'create', + __('category creation'), + __('A new category called "%s" was created by "%s" at %s'), + 'adminAfterCategoryCreate', + array('activityReportBehaviors','categoryCreate') +); + +# from BEHAVIOR adminAfterCategoryUpdate in admin/category.php +$core->activityReport->addAction( + 'category', + 'update', + __('updating category'), + __('Category called "%s" has been updated by "%s" at %s'), + 'adminAfterCategoryUpdate', + array('activityReportBehaviors','categoryUpdate') +); + +# Missing adminBeforeCategoryDelete in admin/category.php + + +/* User +-------------------------*/ +$core->activityReport->addGroup('user',__('Actions on users')); + +# from BEHAVIOR adminAfterUserCreate in admin/user.php +$core->activityReport->addAction( + 'user', + 'create', + __('user creation'), + __('A new user named "%s" was created by "%s"'), + 'adminAfterUserCreate', + array('activityReportBehaviors','userCreate') +); + +# from BEHAVIOR adminAfterUserUpdated in admin/user.php +$core->activityReport->addAction( + 'user', + 'update', + __('updating user'), + __('User named "%s" has been updated by "%s"'), + 'adminAfterUserUpdate', + array('activityReportBehaviors','userUpdate') +); + +# from BEHAVIOR adminBeforeUserDelete in admin/users.php +$core->activityReport->addAction( + 'user', + 'delete', + __('user deletion'), + __('User named "%s" has been deleted by "%"'), + 'adminBeforeUserDelete', + array('activityReportBehaviors','userDelete') +); + + +class activityReportBehaviors +{ + public static function messageActivityReport($message) + { + global $core; + + $logs = array($message); + + $core->activityReport->addLog('activityReport','message',$logs); + } + + public static function blogUpdate($cur,$blog_id) + { + global $core; + + $logs = array($core->auth->getInfo('user_cn')); + + $core->activityReport->addLog('blog','update',$logs); + } + + public static function blogP404() + { + global $core; + if ($core->url->type != '404') return; + + $logs = array($core->blog->url.$_SERVER['QUERY_STRING']); + + $core->activityReport->addLog('blog','p404',$logs); + } + + public static function postCreate($cur,$post_id) + { + global $core; + + $type = $cur->post_type ? $cur->post_type : 'post'; + $post_url = $core->blog->getPostURL('',$cur->post_dt,$cur->post_title,$post_id); + + $logs = array( + $cur->post_title, + $core->auth->getInfo('user_cn'), + $core->blog->url.$core->url->getBase($type).'/'.$post_url + ); + + $core->activityReport->addLog('post','create',$logs); + } + + public static function postUpdate($cur,$post_id) + { + global $core; + + $type = $cur->post_type ? $cur->post_type : 'post'; + $post_url = $core->blog->getPostURL('',$cur->post_dt,$cur->post_title,$post_id); + + $logs = array( + $cur->post_title, + $core->auth->getInfo('user_cn'), + $core->blog->url.$core->url->getBase($type).'/'.$post_url + ); + + $core->activityReport->addLog('post','update',$logs); + } + + public static function postDelete($post_id) + { + global $core; + $posts = $core->blog->getPosts(array('post_id'=>$post_id,'limit'=>1)); + + $logs = array( + $posts->post_title, + $core->auth->getInfo('user_cn') + ); + + $core->activityReport->addLog('post','delete',$logs); + } + + public static function postPasswordAttempt($result) + { + global $core; + if ($result['tpl'] != 'password-form.html' || empty($_POST['password'])) return; + + $logs = array( + $_POST['password'], + http::getSelfURI() + ); + + $core->activityReport->addLog('post','protection',$logs); + } + + public static function commentCreate($blog,$cur) + { + global $core; + if ($cur->comment_trackback) return; + + $posts = $core->blog->getPosts(array('post_id'=>$cur->post_id,'limit'=>1)); + + $logs = array( + $cur->comment_author, + $posts->post_title, + $core->blog->url.$core->url->getBase($posts->post_type). + '/'.$posts->post_url.'#c'.$cur->comment_id + ); + + $core->activityReport->addLog('comment','create',$logs); + } + + public static function commentUpdate($blog,$cur,$old) + { + global $core; + $posts = $core->blog->getPosts(array('post_id'=>$old->post_id,'limit'=>1)); + + $logs = array( + $core->auth->getInfo('user_cn'), + $posts->post_title, + $core->blog->url.$core->url->getBase($posts->post_type). + '/'.$posts->post_url.'#c'.$old->comment_id + ); + + $core->activityReport->addLog('comment','update',$logs); + } + + public static function trackbackCreate($cur,$comment_id) + { + global $core; + + // From blog args are $blog,$cur #thks to bruno + $c = $cur instanceOf dcBlog ? $comment_id : $cur; + if (!$c->comment_trackback || !$c->comment_site) return; + + $posts = $core->blog->getPosts( + array('post_id'=>$c->post_id,'no_content'=>true,'limit'=>1)); + if ($posts->isEmpty()) return; + + $logs = array( + $c->comment_author, + $c->comment_site, + $posts->post_title, + $core->blog->url.$core->url->getBase($posts->post_type). + '/'.$posts->post_url + ); + + $core->activityReport->addLog('comment','trackback',$logs); + } + + public static function categoryCreate($cur,$cat_id) + { + global $core; + + $logs = array( + $cur->cat_title, + $core->auth->getInfo('user_cn'), + $core->blog->url.$core->url->getBase('category').'/'.$cur->cat_url + ); + + $core->activityReport->addLog('category','create',$logs); + } + + public static function categoryUpdate($cur,$cat_id) + { + global $core; + + $logs = array( + $cur->cat_title, + $core->auth->getInfo('user_cn'), + $core->blog->url.$core->url->getBase('category').'/'.$cur->cat_url + ); + + $core->activityReport->addLog('category','update',$logs); + } + + public static function userCreate($cur,$user_id) + { + global $core; + $user_cn = dcUtils::getUserCN($cur->user_id, $cur->user_name, + $cur->user_firstname, $cur->user_displayname); + + $logs = array( + $user_cn, + $core->auth->getInfo('user_cn') + ); + + $core->activityReport->addLog('user','create',$logs); + } + + public static function usertUpdate($cur,$user_id) + { + global $core; + $user_cn = dcUtils::getUserCN($cur->user_id, $cur->user_name, + $cur->user_firstname, $cur->user_displayname); + + $logs = array( + $user_cn, + $core->auth->getInfo('user_cn') + ); + + $core->activityReport->addLog('user','update',$logs); + } + + public static function userDelete($user_id) + { + global $core; + $users = $core->getUser($id); + $user_cn = dcUtils::getUserCN($users->user_id, $users->user_name, + $users->user_firstname, $users->user_displayname); + + $logs = array( + $user_cn, + $core->auth->getInfo('user_cn') + ); + + $core->activityReport->addLog('user','delete',$logs); + } +} +?> \ No newline at end of file diff --git a/inc/class.activity.report.php b/inc/class.activity.report.php new file mode 100644 index 0000000..d38849e --- /dev/null +++ b/inc/class.activity.report.php @@ -0,0 +1,824 @@ +core =& $core; + $this->con = $core->con; + $this->table = $core->prefix.'activity'; + $this->blog = $core->con->escape($core->blog->id); + $this->ns = $core->con->escape($ns); + + $this->getSettings(); + + # Check if some logs are too olds + $this->obsoleteLogs(); + } + + public function setGlobal() + { + $this->_global = 1; + } + + public function unsetGlobal() + { + $this->_global = 0; + } + + public function getGroups($group=null,$action=null) + { + if ($action !== null) + { + return isset($this->groups[$group]['actions'][$action]) ? + $this->groups[$group]['actions'][$action] : null; + } + elseif ($group !== null) + { + return isset($this->groups[$group]) ? + $this->groups[$group] : null; + } + else + { + return $this->groups; + } + } + + public function addGroup($group,$title) + { + $this->groups[$group] = array( + 'title' => $title, + 'actions'=>array() + ); + return true; + } + + public function addAction($group,$action,$title,$msg,$behavior,$function) + { + if (!isset($this->groups[$group])) return false; + + $this->groups[$group]['actions'][$action] = array( + 'title' => $title, + 'msg' => $msg + ); + $this->core->addBehavior($behavior,$function); + return true; + } + + private function getSettings() + { + $settings = array(); + + $settings['active'] = false; + $settings['obsolete'] = 2419200; + $settings['dashboardItem'] = false; + $settings['interval'] = 86400; + $settings['lastreport'] = 0; + $settings['mailinglist'] = array(); + $settings['mailformat'] = 'plain'; + $settings['dateformat'] = '%Y-%m-%d %H:%M:%S'; + $settings['requests'] = array(); + $settings['blogs'] = array(); + + $this->settings[0] = $this->settings[1] = $settings; + + $rs = $this->con->select( + 'SELECT setting_id, setting_value, blog_id '. + 'FROM '.$this->table.'_setting '. + "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'] = array(1=>$this->blog); + } + + public function getSetting($n) + { + return isset($this->settings[$this->_global][$n]) ? + $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($this->table.'_setting'); + $this->con->writeLock($this->table.'_setting'); + + $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 '.$this->table.'_setting '. + "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 = array(); + 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) '; + } + else + { + $content_r = empty($p['no_content']) ? 'activity_logs, ' : ''; + + if (!empty($params['columns']) && is_array($params['columns'])) + { + $content_r .= implode(', ',$params['columns']).', '; + } + + $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 '.$this->table.' E '. + 'LEFT JOIN '.$this->core->prefix.'blog 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'])."' "; + } + } + elseif($this->_global) + { + $r .= 'AND E.blog_id IS NOT NULL '; + } + else + { + $r .= "AND E.blog_id='".$this->blog."' "; + } + + 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 (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 = ".((integer) $p['activity_blog_status'])." "; + } + + if (isset($p['activity_super_status'])) + { + $r .= "AND E.activity_super_status = ".((integer) $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 (!$count_only) + { + if (!empty($p['order'])) + { + $r .= 'ORDER BY '.$this->con->escape($p['order']).' '; + } else { + $r .= 'ORDER BY E.activity_dt DESC '; + } + } + + if (!$count_only && !empty($p['limit'])) + { + $r .= $this->con->limit($p['limit']); + } + + return $this->con->select($r); + } + + public function addLog($group,$action,$logs) + { + try + { + $cur = $this->con->openCursor($this->table); + $this->con->writeLock($this->table); + + $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->insert(); + $this->con->unlock(); + } + catch (Exception $e) { + $this->con->unlock(); + $this->core->error->add($e->getMessage()); + } + + # Test if email report is needed + $this->needReport(); + } + + private function parseLogs($rs) + { + if ($rs->isEmpty()) return ''; + + include dirname(__FILE__).'/lib.parselogs.config.php'; + + $from = time(); + $to = 0; + $res = $blog = $group = ''; + $tz = $this->_global ? 'UTC' : $this->core->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; + + 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(array('%TEXT%','%URL%'),array($rs->blog_name.' ('.$rs->blog_id.')',$rs->blog_url),$tpl['blog_title']); + + $res .= $tpl['blog_open']; + $blog_open = true; + } + + if (isset($this->groups[$rs->activity_group])) + { + # Type + if ($rs->activity_group != $group) + { + if ($group_open) { + $res .= $tpl['group_close']; + } + + $group = $rs->activity_group; + + $res .= str_replace('%TEXT%',__($this->groups[$group]['title']),$tpl['group_title']); + + $res .= $tpl['group_open']; + $group_open = true; + } + + # Action + $time = strtotime($rs->activity_dt); + $data = self::decode($rs->activity_logs); + + $res .= str_replace(array('%TIME%','%TEXT%'),array(dt::str($dt,$time,$tz),vsprintf(__($this->groups[$group]['actions'][$rs->activity_action]['msg']),$data)),$tpl['action']); + + # Period + if ($time < $from) $from = $time; + if ($time > $to) $to = $time; + } + } + + if ($group_open) { + $res .= $tpl['group_close']; + } + if ($blog_open) { + $res .= $tpl['blog_close']; + } + + if ($to == 0) { + $res .= str_replace('%TEXT%',__('An error occured when parsing report.'),$tpl['error']); + } + + # Top of msg + if (empty($res)) return ''; + + $period = str_replace('%TEXT%',__('Activity report'),$tpl['period_title']); + $period .= $tpl['period_open']; + + $period .= str_replace('%TEXT%',__("You received a message from your blog's activity report module."),$tpl['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%',sprintf(__('Period from %s to %s'),dt::str($dt,$from,$tz),dt::str($dt,$to,$tz)),$tpl['info']); + $period .= $tpl['period_close']; + + $res = str_replace(array('%PERIOD%','%TEXT%'),array($period,$res),$tpl['page']); + + return $res; + } + + private function obsoleteLogs() + { + # Get blogs and logs count + $rs = $this->con->select( + "SELECT blog_id ". + 'FROM '.$this->table.' '. + "WHERE activity_type='".$this->ns."' ". + 'GROUP BY blog_id ' + ); + + if ($rs->isEmpty()) return; + + while ($rs->fetch()) + { + $ts = time(); + $obs_blog = dt::str('%Y-%m-%d %H:%M:%S',$ts - (integer) $this->settings[0]['obsolete']); + $obs_global = dt::str('%Y-%m-%d %H:%M:%S',$ts - (integer) $this->settings[1]['obsolete']); + + $this->con->execute( + 'DELETE FROM '.$this->table.' '. + "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)."' " + ); + + if ($this->con->changes()) + { + try + { + $cur = $this->con->openCursor($this->table); + $this->con->writeLock($this->table); + + $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->insert(); + $this->con->unlock(); + } + catch (Exception $e) { + $this->con->unlock(); + $this->core->error->add($e->getMessage()); + } + } + } + } + + private function cleanLogs() + { + $this->con->execute( + 'DELETE FROM '.$this->table.' '. + "WHERE activity_type='".$this->ns."' ". + "AND activity_blog_status = 1 ". + "AND activity_super_status = 1 " + ); + } + + public function deleteLogs() + { + if (!$this->core->auth->isSuperAdmin()) return; + + return $this->con->execute( + 'DELETE FROM '.$this->table.' '. + "WHERE activity_type='".$this->ns."' " + ); + } + + private function updateStatus($from_date_ts,$to_date_ts) + { + $r = + 'UPDATE '.$this->table.' '; + + 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); + } + + public function getNextId() + { + return $this->con->select( + 'SELECT MAX(activity_id) FROM '.$this->table + )->f(0) + 1; + } + + # Lock a file to see if an update is ongoing + public function lockUpdate() + { + try + { + # Need flock function + if (!function_exists('flock')) { + throw New Exception("Can't call php function named flock"); + } + # Cache writable ? + if (!is_writable(DC_TPL_CACHE)) { + throw new Exception("Can't write in cache fodler"); + } + # Set file path + $f_md5 = $this->_global ? md5(DC_MASTER_KEY) : md5($this->blog); + $cached_file = sprintf('%s/%s/%s/%s/%s.txt', + DC_TPL_CACHE, + 'activityreport', + substr($f_md5,0,2), + substr($f_md5,2,2), + $f_md5 + ); + # Real path + $cached_file = path::real($cached_file,false); + # Make dir + if (!is_dir(dirname($cached_file))) { + + files::makeDir(dirname($cached_file),true); + } + # Make file + if (!file_exists($cached_file)) { + !$fp = @fopen($cached_file, 'w'); + if ($fp === false) { + throw New Exception("Can't create file"); + } + fwrite($fp,'1',strlen('1')); + fclose($fp); + } + # Open file + if (!($fp = @fopen($cached_file, 'r+'))) { + throw New Exception("Can't open file"); + } + # Lock file + if (!flock($fp,LOCK_EX)) { + throw New Exception("Can't lock file"); + } + if ($this->_global) + { + $this->lock_global = $fp; + } + else + { + $this->lock_blog = $fp; + } + return true; + } + catch (Exception $e) + { + throw $e; + } + return false; + } + + public function unlockUpdate() + { + if ($this->_global) + { + @fclose($this->lock_global); + $this->lock_global = null; + } + else + { + @fclose($this->lock_blog); + $this->lock_blog = null; + } + } + + public static function hasMailer() + { + return function_exists('mail') || function_exists('_mail'); + } + + public function needReport($force=false) + { + try + { + # Check if server has mail function + if (!self::hasMailer()) + { + throw new Exception('No mail fonction'); + } + + # Limit to one update at a time + $this->lockUpdate(); + + $send = false; + $now = time(); + + $active = (boolean) $this->settings[$this->_global]['active']; + $mailinglist = $this->settings[$this->_global]['mailinglist']; + $mailformat = $this->settings[$this->_global]['mailformat']; + $requests = $this->settings[$this->_global]['requests']; + $lastreport = (integer) $this->settings[$this->_global]['lastreport']; + $interval = (integer) $this->settings[$this->_global]['interval']; + $blogs = $this->settings[$this->_global]['blogs']; + + if ($force) $lastreport = 0; + + # Check if report is needed + if ($active && !empty($mailinglist) && !empty($requests) && !empty($blogs) + && ($lastreport + $interval) < $now ) + { + # Get datas + $params = array(); + $params['from_date_ts'] = $lastreport; + $params['to_date_ts'] = $now; + $params['blog_id'] = $blogs; + $params['sql'] = self::requests2params($requests); + $params['order'] = 'blog_id ASC, activity_group ASC, activity_action ASC, activity_dt ASC '; + + $logs = $this->getLogs($params); + if (!$logs->isEmpty()) + { + # Datas to readable text + $content = $this->parseLogs($logs); + if (!empty($content)) + { + # Send mails + $send = $this->sendReport($mailinglist,$content,$mailformat); + } + } + + # Update db + if ($send || $this->_global) // if global : delete all blog logs even if not selected + { + # Update log status + $this->updateStatus($lastreport,$now); + # Delete old logs + $this->cleanLogs(); + # Then set update time + $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->core->callBehavior('messageActivityReport','Activity report has been successfully send by mail.'); + } + } + $this->unlockUpdate(); + } + catch (Exception $e) + { + $this->unlockUpdate(); + //throw $e; + } + return true; + } + + private function sendReport($recipients,$msg,$mailformat='') + { + if (!is_array($recipients) || empty($msg) || !text::isEmail($this->mailer)) return false; + $mailformat = $mailformat == 'html' ? 'html' : 'plain'; + + # Checks recipients addresses + $rc2 = array(); + foreach ($recipients as $v) + { + $v = trim($v); + if (!empty($v) && text::isEmail($v)) + { + $rc2[] = $v; + } + } + $recipients = $rc2; + unset($rc2); + + if (empty($recipients)) return false; + + # Sending mails + try + { + $headers = array( + 'From: '.mail::B64Header(__('Activity report module')).' <'.$this->mailer.'>', + 'Reply-To: <'.$this->mailer.'>', + 'Content-Type: text/'.$mailformat.'; charset=UTF-8;', + 'MIME-Version: 1.0', + 'X-Originating-IP: '.http::realIP(), + 'X-Mailer: Dotclear', + 'X-Blog-Id: '.mail::B64Header($this->core->blog->id), + 'X-Blog-Name: '.mail::B64Header($this->core->blog->name), + 'X-Blog-Url: '.mail::B64Header($this->core->blog->url) + ); + + $subject = $this->_global ? + mail::B64Header(__('Blog activity report')) : + mail::B64Header('['.$this->core->blog->name.'] '.__('Blog activity report')); + + $done = true; + foreach ($recipients as $email) + { + if (true !== mail::sendMail($email,$subject,$msg,$headers)) { + $done = false; + } + } + } + catch (Exception $e) + { + $done = false; + } + + return $done; + } + + public function getUserCode() + { + $code = + pack('a32',$this->core->auth->userID()). + pack('H*',crypt::hmac(DC_MASTER_KEY,$this->core->auth->getInfo('user_pwd'))); + return bin2hex($code); + } + + public function checkUserCode($code) + { + $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) { + return false; + } + + $pwd = $pwd['hex']; + + $strReq = 'SELECT user_id, user_pwd '. + 'FROM '.$this->core->prefix.'user '. + "WHERE user_id = '".$this->core->con->escape($user_id)."' "; + + $rs = $this->core->con->select($strReq); + + if ($rs->isEmpty()) { + return false; + } + + if (crypt::hmac(DC_MASTER_KEY,$rs->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)); + } +} +?> \ No newline at end of file diff --git a/inc/img/feed.png b/inc/img/feed.png new file mode 100644 index 0000000..315c4f4 Binary files /dev/null and b/inc/img/feed.png differ diff --git a/inc/lib.activity.report.index.php b/inc/lib.activity.report.index.php new file mode 100644 index 0000000..4586dae --- /dev/null +++ b/inc/lib.activity.report.index.php @@ -0,0 +1,374 @@ +activityReport; + $section = isset($_REQUEST['section']) ? $_REQUEST['section'] : ''; + + if ($global) + { + $O->setGlobal(); + $t = 'super'; + } + else + { + $t = 'blog'; + } + + $combo_int = array( + __('every hour') => 3600, + __('every 2 hours') => 7200, + __('2 times by day') => 43200, + __('every day') => 86400, + __('every 2 days') => 172800, + __('every week') => 604800 + ); + + $combo_obs = array( + __('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 + ); + + $combo_format = array( + __('Plain text') => 'plain', + __('HTML') => 'html' + ); + + $redirect = false; + if (!empty($_POST[$t.'_settings'])) + { + # Active notification on this blog + $O->setSetting('active',isset($_POST['active'])); + # Add dashboard items + $O->setSetting('dashboardItem',isset($_POST['dashboardItem'])); + # Report interval + if (in_array($_POST['interval'],$combo_int)) + { + $O->setSetting('interval',(integer) $_POST['interval']); + } + # check obsolete logs interval + if (in_array($_POST['obsolete'],$combo_obs)) + { + $O->setSetting('obsolete',(integer) $_POST['obsolete']); + } + # mail list + $O->setSetting('mailinglist',explode(';',$_POST['mailinglist'])); + # mail format + $mailformat = isset($_POST['mailformat']) && $_POST['mailformat'] == 'html' ? 'html' : 'plain'; + $O->setSetting('mailformat',$mailformat); + # date format + $O->setSetting('dateformat',html::escapeHTML($_POST['dateformat'])); + # request infos + $requests = isset($_POST['requests']) ? $_POST['requests'] : array(); + $O->setSetting('requests',$requests); + #blogs + $blogs = isset($_POST['blogs']) ? $_POST['blogs'] : array(); + $O->setSetting('blogs',$blogs); + + $redirect = true; + } + + # force to send report now + if (!empty($_POST[$t.'_force_report'])) + { + $core->activityReport->needReport(true); + $redirect = true; + } + + # force to delete all logs now + if (!empty($_POST[$t.'_force_delete'])) + { + $core->activityReport->deleteLogs(); + $redirect = true; + } + + if ($redirect) + { + http::redirect('plugin.php?p=activityReport&tab='.$t.'_settings&section'.$section); + } + + $bl = $O->getSetting('lastreport'); + $blog_last = !$bl ? __('never') : dt::str($core->blog->settings->system->date_format.', '.$core->blog->settings->system->time_format,$bl,$core->auth->getInfo('user_tz')); + + $bi = $O->getSetting('interval'); + $blog_next = !$bl ? __('on new activity') : dt::str($core->blog->settings->system->date_format.', '.$core->blog->settings->system->time_format,$bl+$bi,$core->auth->getInfo('user_tz')); + + $emails = implode(';',$O->getSetting('mailinglist')); + + ?> +
+ + + +

<?php echo __('RSS feed'); ?> + + +
+ <?php echo __('Atom feed'); ?> + +

+ + + +
+ +
+ +

+

+ +

+ +

+ +

+

+ +

+ +

+

+ +
    +
  • +
  • +
+ +
+ +
+
+ getSetting('blogs'); + $blogs = $core->getBlogs(); + while($blogs->fetch()) + { + $blog_id = $core->con->escape($blogs->blog_id); + ?> +
+

+
+ +
+
+ +
+
+ getGroups(); + $blog_request = $O->getSetting('requests'); + + $i = 0; + foreach($groups as $k_group => $v_group) + { + + ?> +
+

+ $v_action) + { + ?> +

+ +
+
+
+
+ +

+ + + + + + formNonce(); + ?> +

+
+
+ unsetGlobal(); + } + + public static function logTab($core,$title,$global=false) + { + $O =& $core->activityReport; + if ($global) + { + $O->setGlobal(); + $t = 'super'; + } + else + { + $t = 'blog'; + } + + $params = array(); + $logs = $O->getLogs($params); + + ?> +
+ isEmpty()) + { + echo '

'.__('No log').'

'; + } + else + { + + ?> + + + + + + + + + + + + + fetch()) + { + $off = $global && $logs->activity_blog_status == 1 ? + ' offline' : ''; + $date = dt::str( + $core->blog->settings->system->date_format.', '.$core->blog->settings->system->time_format, + strtotime($logs->activity_dt), + $core->auth->getInfo('user_tz') + ); + $action = $O->getGroups($logs->activity_group,$logs->activity_action); + + if (empty($action)) continue; + + $msg = vsprintf(__($action['msg']),$O->decode($logs->activity_logs)); + ?> + + + + + + + + + + +
blog_id; ?>
+ +
+ unsetGlobal(); + } +} +?> \ No newline at end of file diff --git a/inc/lib.parselogs.config.php b/inc/lib.parselogs.config.php new file mode 100644 index 0000000..404db46 --- /dev/null +++ b/inc/lib.parselogs.config.php @@ -0,0 +1,70 @@ + array( + +'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' => array( + +'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%" + +)); +?> \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..12ebd9a --- /dev/null +++ b/index.php @@ -0,0 +1,70 @@ + + + + <?php echo __('Activity report'); ?> +'."\n//\n\n"; +?> + + +

    blog->name). + ' › '.__('Activity report'); +?>

    + + +

    + auth->isSuperAdmin()) +{ + activityReportLib::settingTab($core,__('Super settings'),true); + activityReportLib::logTab($core,__('Super logs'),true); +} + +?> + +
    +

    +activityReport - +plugins->moduleInfo('activityReport','version'); ?>  +activityReport +

    + + \ No newline at end of file diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..291050f --- /dev/null +++ b/js/main.js @@ -0,0 +1,91 @@ +/* -- 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..54e0d2c --- /dev/null +++ b/locales/fr/main.lang.php @@ -0,0 +1,264 @@ + \ No newline at end of file diff --git a/locales/fr/main.po b/locales/fr/main.po new file mode 100644 index 0000000..d1c985b --- /dev/null +++ b/locales/fr/main.po @@ -0,0 +1,353 @@ +# Language: Français +# Module: activityReport - 1.0 +# Date: 2010-06-08 07:54:36 +# Translated with translater 1.4 + +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Project-Id-Version: activityReport 1.0\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2010-06-08T07:54:36+00:00\n" +"Last-Translator: JC Denis\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Transfer-Encoding: 8bit\n" + +#: _admin.php:19 +#: _admin.php:88 +#: inc/class.activity.report.php:435 +#: inc/lib.parselogs.config.php:33 +#: index.php:26 +#: index.php:42 +msgid "Activity report" +msgstr "Rapport d'activité" + +#: inc/class.activity.report.behaviors.php:19 +msgid "ActivityReport messages" +msgstr "Messages de l'extension" + +#: inc/class.activity.report.behaviors.php:24 +msgid "Special messages" +msgstr "Messages spéciaux" + +#: inc/class.activity.report.behaviors.php:25 +msgid "%s" +msgstr "%s" + +#: inc/class.activity.report.behaviors.php:32 +msgid "Actions on blog" +msgstr "Actions sur le blog" + +#: inc/class.activity.report.behaviors.php:40 +msgid "updating blog" +msgstr "Mise à jour du blog" + +#: inc/class.activity.report.behaviors.php:41 +msgid "Blog was updated by \"%s\"" +msgstr "Blog mis à jour par \"%s\"" + +#: inc/class.activity.report.behaviors.php:50 +msgid "404 error" +msgstr "Erreur 404" + +#: inc/class.activity.report.behaviors.php:51 +msgid "New 404 error page at \"%s\"" +msgstr "Nouvelle erreur 404 à l'adresse \"%s\"" + +#: inc/class.activity.report.behaviors.php:59 +msgid "Actions on posts" +msgstr "Actions sur les billets" + +#: inc/class.activity.report.behaviors.php:67 +#: inc/class.activity.report.behaviors.php:78 +msgid "post creation" +msgstr "Création de billet" + +#: inc/class.activity.report.behaviors.php:68 +#: inc/class.activity.report.behaviors.php:79 +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\"" + +#: inc/class.activity.report.behaviors.php:89 +msgid "updating post" +msgstr "Mise à jour de billet" + +#: inc/class.activity.report.behaviors.php:90 +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\"" + +#: inc/class.activity.report.behaviors.php:100 +msgid "post deletion" +msgstr "Suppression de billet" + +#: inc/class.activity.report.behaviors.php:101 +msgid "Post called \"%s\" has been deleted by \"%s\"" +msgstr "Le billet nommé \"%s\" a été supprimé par \"%s\"" + +#: inc/class.activity.report.behaviors.php:111 +msgid "Post protection" +msgstr "Protection des billets" + +#: inc/class.activity.report.behaviors.php:112 +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\"" + +#: inc/class.activity.report.behaviors.php:121 +msgid "Actions on comments" +msgstr "Actions sur les commentaires" + +#: inc/class.activity.report.behaviors.php:129 +msgid "comment creation" +msgstr "Création de commentaire" + +#: inc/class.activity.report.behaviors.php:130 +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\"" + +#: inc/class.activity.report.behaviors.php:140 +msgid "updating comment" +msgstr "Mise à jour de commentaire" + +#: inc/class.activity.report.behaviors.php:141 +msgid "Comment has been updated by \"%s\" at %s" +msgstr "Un commentaire a été mise à jour par \"%s\" à l'adresse \"%s\"" + +#: inc/class.activity.report.behaviors.php:154 +msgid "trackback creation" +msgstr "Création de trackback" + +#: inc/class.activity.report.behaviors.php:155 +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\"" + +#: inc/class.activity.report.behaviors.php:163 +msgid "Actions on categories" +msgstr "Actions sur les catégories" + +#: inc/class.activity.report.behaviors.php:169 +msgid "category creation" +msgstr "Création de catégorie" + +#: inc/class.activity.report.behaviors.php:170 +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\"" + +#: inc/class.activity.report.behaviors.php:179 +msgid "updating category" +msgstr "Mise à jour de catégorie" + +#: inc/class.activity.report.behaviors.php:180 +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\"" + +#: inc/class.activity.report.behaviors.php:190 +msgid "Actions on users" +msgstr "Actions sur les utilisateurs" + +#: inc/class.activity.report.behaviors.php:196 +msgid "user creation" +msgstr "Création d'utilisateur" + +#: inc/class.activity.report.behaviors.php:197 +msgid "A new user named \"%s\" was created by \"%s\"" +msgstr "Un nouvel utilisateur a été ajouté par \"%s\"" + +#: inc/class.activity.report.behaviors.php:206 +msgid "updating user" +msgstr "Mise à jour d'utilisateur" + +#: inc/class.activity.report.behaviors.php:207 +msgid "User named \"%s\" has been updated by \"%s\"" +msgstr "L'utilisateur \"%s\" a été mis à jour par \"%s\"" + +#: inc/class.activity.report.behaviors.php:216 +msgid "user deletion" +msgstr "Suppression d'utilisateur" + +#: inc/class.activity.report.behaviors.php:217 +msgid "User named \"%s\" has been deleted by \"%\"" +msgstr "L'utilisateur nommé \"%s\" a été supprimé par \"%s\"" + +#: inc/class.activity.report.php:429 +msgid "An error occured when parsing report." +msgstr "Une erreur est survenue lors de la compilation du rapport." + +#: inc/class.activity.report.php:438 +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." + +#: inc/class.activity.report.php:444 +msgid "Period from %s to %s" +msgstr "Période du %s au %s" + +#: inc/class.activity.report.php:490 +msgid "Activity report deletes some old logs." +msgstr "L'extension a automatiquement effacé des anciennes activités." + +#: inc/class.activity.report.php:745 +msgid "Activity report module" +msgstr "Module de rapport d'activité" + +#: inc/class.activity.report.php:757 +#: inc/class.activity.report.php:758 +msgid "Blog activity report" +msgstr "Rapport d'activité du blog" + +#: inc/lib.activity.report.index.php:33 +#: inc/lib.activity.report.index.php:42 +msgid "every hour" +msgstr "toutes les heures" + +#: inc/lib.activity.report.index.php:34 +#: inc/lib.activity.report.index.php:43 +msgid "every 2 hours" +msgstr "toutes les 2 heures" + +#: inc/lib.activity.report.index.php:35 +#: inc/lib.activity.report.index.php:44 +msgid "2 times by day" +msgstr "2 fois par jour" + +#: inc/lib.activity.report.index.php:36 +#: inc/lib.activity.report.index.php:45 +msgid "every day" +msgstr "tous les jours" + +#: inc/lib.activity.report.index.php:37 +#: inc/lib.activity.report.index.php:46 +msgid "every 2 days" +msgstr "tous les 2 jours" + +#: inc/lib.activity.report.index.php:38 +#: inc/lib.activity.report.index.php:47 +msgid "every week" +msgstr "toutes les semaines" + +#: inc/lib.activity.report.index.php:48 +msgid "every 2 weeks" +msgstr "toutes les 2 semaines" + +#: inc/lib.activity.report.index.php:49 +msgid "every 4 weeks" +msgstr "toutes les 4 semaines" + +#: inc/lib.activity.report.index.php:53 +msgid "Plain text" +msgstr "Texte brut" + +#: inc/lib.activity.report.index.php:54 +msgid "HTML" +msgstr "HTML" + +#: inc/lib.activity.report.index.php:111 +msgid "never" +msgstr "jamais" + +#: inc/lib.activity.report.index.php:114 +msgid "on new activity" +msgstr "lors d'une nouvelle activité" + +#: inc/lib.activity.report.index.php:123 +#: inc/lib.activity.report.index.php:124 +msgid "RSS feed" +msgstr "Flux RSS" + +#: inc/lib.activity.report.index.php:125 +msgid "Rss2 feed for activity on this blog" +msgstr "Flux Rss2 pour l'activité de ce blog" + +#: inc/lib.activity.report.index.php:127 +#: inc/lib.activity.report.index.php:128 +msgid "Atom feed" +msgstr "Flux Atom" + +#: inc/lib.activity.report.index.php:129 +msgid "Atom feed for activity on this blog" +msgstr "Flux Atom pour l'activité de ce blog" + +#: inc/lib.activity.report.index.php:135 +#: index.php:52 +msgid "Settings" +msgstr "Paramètres" + +#: inc/lib.activity.report.index.php:141 +msgid "Enable super administrator report" +msgstr "Autoriser le rapport de super administrateur" + +#: inc/lib.activity.report.index.php:142 +msgid "Enable report on this blog" +msgstr "Autoriser le rapport sur ce blog" + +#: inc/lib.activity.report.index.php:145 +msgid "Automatic cleaning of old logs:" +msgstr "Nettoyage automatique des anciennes activités:" + +#: inc/lib.activity.report.index.php:157 +msgid "Add activity report on dashboard items" +msgstr "Ajouter le rapport d'activité au tableau de bord" + +#: inc/lib.activity.report.index.php:164 +msgid "Send report:" +msgstr "Rapport envoyé :" + +#: inc/lib.activity.report.index.php:171 +msgid "Use Dotclear date formaters. ex: %B %d at %H:%M" +msgstr "Utiliser le formatage des dates de Dotclear. ex: %d %B à %H:%M" + +#: inc/lib.activity.report.index.php:173 +msgid "Report format:" +msgstr "Format du rapport :" + +#: inc/lib.activity.report.index.php:177 +msgid "Recipients:" +msgstr "Destinataires :" + +#: inc/lib.activity.report.index.php:180 +msgid "Separate multiple email addresses with a semicolon \";\"" +msgstr "Séparer les adresses email par un point-virgule \";\"" + +#: inc/lib.activity.report.index.php:183 +msgid "Last report by email:" +msgstr "Dernier rapport par email :" + +#: inc/lib.activity.report.index.php:184 +msgid "Next report by email:" +msgstr "Prochain rapport par email :" + +#: inc/lib.activity.report.index.php:223 +msgid "Report" +msgstr "Rapport" + +#: inc/lib.activity.report.index.php:270 +msgid "Send report by email now" +msgstr "Envoyer un rapport par email maintenant" + +#: inc/lib.activity.report.index.php:314 +msgid "No log" +msgstr "Pas d'enregistrement" + +#: inc/lib.activity.report.index.php:324 +msgid "Message" +msgstr "Message" + +#: index.php:34 +msgid "Please wait" +msgstr "Veuillez patienter" + +#: index.php:49 +msgid "This server has no mail function, activityReport not send email report." +msgstr "Ce server n'a pas de fonction d'envoie de mail, le rapport ne sera pas envoyer." + +#: index.php:53 +msgid "Logs" +msgstr "Enregistrements" + +#: index.php:57 +msgid "Super settings" +msgstr "Super paramètres" + +#: index.php:58 +msgid "Super logs" +msgstr "Super enregistrements" + diff --git a/release.txt b/release.txt new file mode 100644 index 0000000..f9e4cf6 --- /dev/null +++ b/release.txt @@ -0,0 +1,51 @@ +1.0.1 20100608 + * Fixed (again) simultaneous report + +1.0 20100606 + * Switched to DC 2.2 + * Fixed simultaneous report (used php flock) + * Added some stuff to admin interface + + +0.9 20100129 + * Fixed timezone on mail report + * Added option to fix bug on big logs + +0.8 20100112 + * Added support of _uninstall.php file + * Added support of html or plain text email + * Added support of plugin contribute + +0.7.1 20091220 + * Fixed spam loop + +0.7 20091219 + * Added message for attempt on protected post + +0.6.2 20091208 + * Added extra messages + * Fixed bugs with uninstalled plugins + * Fixed typo + +0.6.1 20091111 + * Fixed bug with dashboard items. + +0.6 20091108 + * Fixed date display (closes #340) + * Fixed RSS 'protection' (closes #339) + * Added blog name to mail subject + +0.5 20091026 + * Changed priority to -1000000 in order to work ?! + * Used a constant to see if activityReport plugin is on + * Fixed typo + +0.4 20091019 + * Fixed fatal error on plugin update + +0.3 20090927 + * Added atom/rss2 feeds + * Moved some class + +0.2 20090915 + * First public release \ No newline at end of file