diff --git a/.distignore b/.distignore new file mode 100644 index 0000000..f76ba00 --- /dev/null +++ b/.distignore @@ -0,0 +1,25 @@ +# Directories +/.git/ +/.github/ +/.wordpress-org/ +/bin/ +/node_modules/ +/tests/ +/vendor/ + +# Files +/.distignore +/.editorconfig +/.gitattributes +/.gitignore +/.phpcs.xml.dist +/.wp-env.json +/.wp-env.override.json +/CHANGELOG.md +/composer.json +/composer.lock +/phpunit.xml.dist + +# Caches and logs +/.phpunit.cache/ +*.log diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5c985df --- /dev/null +++ b/.gitattributes @@ -0,0 +1,40 @@ +# Exclude these files from release archives. +# This will also make them unavailable when using Composer with `--prefer-dist`. +# If you develop for this plugin using Composer, use `--prefer-source`. +# https://blog.madewithlove.be/post/gitattributes/ + +# Directories +/.github/ export-ignore +/.wordpress-org/ export-ignore +/bin/ export-ignore +/node_modules/ export-ignore +/tests/ export-ignore +/vendor/ export-ignore + +# Files +/.distignore export-ignore +/.editorconfig export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.phpcs.xml.dist export-ignore +/.wp-env.json export-ignore +/.wp-env.override.json export-ignore +/CHANGELOG.md export-ignore +/composer.json export-ignore +/composer.lock export-ignore +/phpunit.xml.dist export-ignore + +# Auto detect text files and perform LF normalization +# https://pablorsk.medium.com/be-a-git-ninja-the-gitattributes-file-e58c07c9e915 + +* text=auto + +# The above will handle all files NOT found below + +*.md text +*.php text +*.inc text +*.js text +*.txt text + +*.png binary diff --git a/.github/workflows/cs-lint.yml b/.github/workflows/cs-lint.yml index d8b5bfe..4556e7b 100644 --- a/.github/workflows/cs-lint.yml +++ b/.github/workflows/cs-lint.yml @@ -39,11 +39,13 @@ jobs: steps: - name: Setup PHP - uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # 2.37.1 + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 with: - php-version: "7.4" + # Linting and CS run on a recent PHP; the minimum supported version is + # checked statically by PHPCompatibilityWP via the testVersion config. + php-version: "8.2" coverage: none - tools: cs2pr + tools: composer, cs2pr # Show PHP lint violations inline in the file diff. # @link https://github.com/marketplace/actions/xmllint-problem-matcher @@ -53,10 +55,10 @@ jobs: # Show XML violations inline in the file diff. # @link https://github.com/marketplace/actions/xmllint-problem-matcher - name: Register XML violations to appear as file diff comments - uses: korelstar/xmllint-problem-matcher@1bd292d642ddf3d369d02aaa8b262834d61198c0 # v1.2.0 + uses: korelstar/xmllint-problem-matcher@dd2ad21bd8a2de0187cb621419537f345e7e509c # v1.3.0 - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -68,7 +70,9 @@ jobs: # Install dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-composer-dependencies - name: Install Composer dependencies - uses: ramsey/composer-install@994bb194a4fefcf39449ccf0f7766a4318f1ac76 # v1 + uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0 + with: + composer-options: "--prefer-dist --no-progress" # Lint PHP. - name: Lint PHP against parse errors @@ -83,10 +87,11 @@ jobs: xml-schema-file: ./vendor/phpunit/phpunit/phpunit.xsd # Check the code-style consistency of the PHP files. - # Temporarily disabled: the plugin carries a backlog of pre-existing WPCS 3.x - # violations (see PR #13). Re-enable this gate once that cleanup has landed. - # - name: Check PHP code style - # run: composer cs -- --report-full --report-checkstyle=./phpcs-report.xml - # - # - name: Show PHPCS results in PR - # run: cs2pr ./phpcs-report.xml + # continue-on-error lets the next step turn the report into inline + # annotations; cs2pr then fails the job if any violations were found. + - name: Check PHP code style + continue-on-error: true + run: composer cs -- --report-full --report-checkstyle=./phpcs-report.xml + + - name: Show PHPCS results in PR + run: cs2pr ./phpcs-report.xml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..786a144 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,32 @@ +name: Deploy to WordPress.org + +on: + release: + types: [released] # Trigger when a (non-prerelease) release is published. + +# Disable all permissions by default; grant minimal permissions per job. +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + deploy: + name: Deploy to WordPress.org SVN + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: WordPress.org plugin deploy + uses: 10up/action-wordpress-plugin-deploy@54bd289b8525fd23a5c365ec369185f2966529c2 # v2.3.0 + env: + SLUG: safe-report-comments + SVN_USERNAME: ${{ secrets.SVN_USERNAME }} + SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} diff --git a/.github/workflows/integrations.yml b/.github/workflows/integrations.yml index 711e3fb..7dfc1e9 100644 --- a/.github/workflows/integrations.yml +++ b/.github/workflows/integrations.yml @@ -50,12 +50,12 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Setup PHP ${{ matrix.php }} - uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # 2.37.1 + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 with: php-version: ${{ matrix.php }} coverage: none @@ -68,7 +68,9 @@ jobs: run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - name: Install Composer dependencies - uses: ramsey/composer-install@994bb194a4fefcf39449ccf0f7766a4318f1ac76 # v1 + uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0 + with: + composer-options: "--prefer-dist --no-progress" - name: Start MySQL service run: sudo systemctl start mysql.service diff --git a/.gitignore b/.gitignore index d8a7996..069d45a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,23 @@ -composer.lock -vendor/ +# Dependencies +/node_modules/ +/vendor/ +/composer.lock + +# Built assets (generated by build process) +/build/ + +# WordPress environment +/.wp-env.override.json + +# Tests +/.phpunit.cache/ +/phpunit.xml + +# PHPCS +/phpcs.xml +/.phpcs.xml +/phpcs-report.xml + +# Logs +*.log +npm-debug.log* diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index 6d21b8f..92aa2d8 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -9,7 +9,12 @@ . - /vendor/ + */build/* + */node_modules/* + */vendor/* + + */tests/bootstrap.php diff --git a/screenshot-1.png b/.wordpress-org/screenshot-1.png similarity index 100% rename from screenshot-1.png rename to .wordpress-org/screenshot-1.png diff --git a/screenshot-2.png b/.wordpress-org/screenshot-2.png similarity index 100% rename from screenshot-2.png rename to .wordpress-org/screenshot-2.png diff --git a/screenshot-3.png b/.wordpress-org/screenshot-3.png similarity index 100% rename from screenshot-3.png rename to .wordpress-org/screenshot-3.png diff --git a/screenshot-4.png b/.wordpress-org/screenshot-4.png similarity index 100% rename from screenshot-4.png rename to .wordpress-org/screenshot-4.png diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6af413d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,67 @@ +# Changelog for Safe Report Comments + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.5.0] - 2026-08-20 + +This release formalises the plugin's minimum requirements as WordPress 6.4 and PHP 7.4. + +### Changed + +- In automatic mode the report link is attached via the `comment_text` filter and positioned client-side, instead of via the `comment_reply_link` filter. +- Front-end flagging messages and the "Report comment" link text are now translatable; they were previously hard-coded in English (#33). + +### Removed + +- Internal `add_flagging_link()` method and the `safe_report_comments_comment_reply_link` filter, which the new placement mechanism no longer uses. + +### Fixed + +- Report link no longer disappears on themes that modify the comment reply-link markup, such as Twenty Twenty. It is now positioned in the browser next to the reply link rather than by parsing that markup server-side (#14). +- Report link now appears on comments at the maximum threading depth, where WordPress renders no reply link. +- Report link now appears in block themes that render comments with the Comment Content block, not only classic `wp_list_comments()` output. +- Reporting a comment already marked as spam or trashed no longer pushes it back into the moderation queue; only currently approved comments are moved to hold (#16). +- A visitor's own report is remembered again via the flag cookie, so the same comment can no longer be reported repeatedly by one person (#15). + +### Security + +- Flagging is now restricted to genuine, public comment types. Previously any numeric ID that shared the comments table — such as WooCommerce order notes — could be reported and forced into moderation once the threshold was reached, even though no report link was ever shown for it. A new `is_reportable_comment()` helper gates flagging, with filters to adjust the reportable types or veto a target. + +## [0.4.1] - 2014-07-23 + +### Fixed + +- Typo fix (props @spencermorin). + +## [0.4] - 2014-07-23 + +### Security + +- Security fix (h/t vortfu). + +## [0.3.2] - 2013-03-06 + +### Added + +- New `safe_report_comments_allow_moderated_to_be_reflagged` filter allows comments to be reflagged after being moderated. + +## [0.3.1] - 2012-11-21 + +### Fixed + +- Use `home_url()` for generating the `ajaxurl` on mapped domains, but `admin_url()` where the domain isn't mapped. + +## [0.3] - 2012-11-07 + +### Changed + +- Coding standards and cleanup. + +[0.5.0]: https://github.com/Automattic/safe-report-comments/compare/0.4.1...0.5.0 +[0.4.1]: https://github.com/Automattic/safe-report-comments/compare/0.4...0.4.1 +[0.4]: https://github.com/Automattic/safe-report-comments/compare/0.3.2...0.4 +[0.3.2]: https://github.com/Automattic/safe-report-comments/compare/0.3.1...0.3.2 +[0.3.1]: https://github.com/Automattic/safe-report-comments/compare/0.3...0.3.1 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9efa6fb --- /dev/null +++ b/LICENSE @@ -0,0 +1,338 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + + 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, see . + +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 + Moe Ghoul, 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/README.md b/README.md new file mode 100644 index 0000000..9c90004 --- /dev/null +++ b/README.md @@ -0,0 +1,112 @@ +# Safe Report Comments + +**Contributors:** tott, danielbachhuber, automattic, garyj +**Tags:** flagging, comments, report comments, inappropriate, spam +**Requires at least:** 6.4 +**Tested up to:** 7.0 +**Requires PHP:** 7.4 +**Stable tag:** 0.5.0 +**License:** GPL-2.0-or-later +**License URI:** https://www.gnu.org/licenses/gpl-2.0.html + +This plugin gives your visitors the possibility to report a comment as inappropriate. After a set threshold is reached the comment is put into moderation where the moderator can decide whether to approve it. If a comment is approved by a moderator it will not be auto-moderated again while still counting the amount of reports. + +## Description + +This plugin gives your visitors the possibility to report a comment as inappropriate. After a set threshold is reached the comment is put into moderation where the moderator can decide whether to approve it. If a comment is approved by a moderator it will not be auto-moderated again while still counting the amount of reports. + +## Installation + +1. Download and unzip the plugin. +2. Copy the `safe-report-comments` directory into your plugins folder. +3. Visit your Plugins page and activate the plugin. +4. A new checkbox called "Allow comment flagging" will appear on the **Settings → Discussion** page. +5. Activate the flag and set the threshold value, which appears on the same page once flagging is enabled. + +## Screenshots + +![Simple activation via discussion settings](.wordpress-org/screenshot-1.png) + +![Amount of reports per comment is shown in comments administration screen](.wordpress-org/screenshot-2.png) + +![Fits well within most themes without any further action.](.wordpress-org/screenshot-3.png) + +![Ajax feedback right in place where available.](.wordpress-org/screenshot-4.png) + +## Customizations + +By default this plugin should work with most existing themes without any changes. It appends the flagging link to each comment and then, in the browser, moves it next to that comment's reply link. The reply link is located via its core `data-commentid` attribute, so placement does not depend on your theme's specific markup. + +The report link ships unstyled and inherits your theme's link styles from wherever it appears, so it can look different next to a reply link than at the end of a comment (the deepest threading level, which has no reply link). To adjust or unify its appearance, target the `.safe-comments-report-link` class in your theme's CSS. + +If you would rather control the placement yourself, you can place the flagging link manually. Define `no_autostart_safe_report_comments` in your theme's `functions.php` file and initialize the class with auto-attachment disabled: `$safe_report_comments = new Safe_Report_Comments( false );`. + +Here is an example of a custom setup in `functions.php` that places the flagging link via a comment callback function. + +In `functions.php`: + +~~~php +// Flag comments plugin included in theme's functions.php - disable plugin. +define( 'no_autostart_safe_report_comments', true ); +include_once( 'replace-with-path-to/safe-report-comments/safe-report-comments.php' ); +// Make sure not to auto-attach to the comment reply link. +$safe_report_comments = new Safe_Report_Comments( false ); + +// Change link layout to have a pipe prepended. +add_filter( 'safe_report_comments_flagging_link', 'adjust_flagging_link' ); +function adjust_flagging_link( $link ) { + return ' | ' . $link; +} + +// Adjust the text to "Report abuse" rather than "Report comment". +add_filter( 'safe_report_comments_flagging_link_text', 'adjust_flagging_text' ); +function adjust_flagging_text( $text ) { + return 'Report abuse'; +} +~~~ + +In your custom comment callback function used by [`wp_list_comments`](https://developer.wordpress.org/reference/functions/wp_list_comments/), place the following action, which prints the link: + +~~~php + +~~~ + +A possible callback function could look like this: + +~~~php +function mytheme_comment( $comment, $args, $depth ) { + $GLOBALS['comment'] = $comment; ?> +
  • id="li-comment-"> +
    +
    + ' ); ?> + %s says:' ), get_comment_author_link() ) ?> +
    + comment_approved == '0' ) : ?> + +
    + + + + + +
    + $depth, 'max_depth' => $args['max_depth'] ) ) ) ?> +
    +
    + +
    +
    + __( 'Thank you for your feedback. We will look into it.', 'safe-report-comments' ), + 'invalid_nonce_message' => __( 'It seems you already reported this comment.', 'safe-report-comments' ), + 'invalid_values_message' => __( 'Cheating huh?', 'safe-report-comments' ), + 'already_flagged_message' => __( 'It seems you already reported this comment.', 'safe-report-comments' ), + ); + + foreach ( $defaults as $var => $message ) { + $this->{$var} = $message; + } + /** * Apply some filters to easily alter the frontend messages. Example: * add_filter( 'safe_report_comments_thank_you_message', 'alter_message' ); @@ -214,7 +246,7 @@ public function frontend_init() { add_action( 'wp_enqueue_scripts', array( $this, 'action_enqueue_scripts' ) ); if ( $this->auto_init ) { - add_filter( 'comment_reply_link', array( $this, 'add_flagging_link' ) ); + add_filter( 'comment_text', array( $this, 'append_flagging_link' ), 100, 2 ); } add_action( 'comment_report_abuse_link', array( $this, 'print_flagging_link' ) ); @@ -235,7 +267,7 @@ public function action_enqueue_scripts() { $ajaxurl = apply_filters( 'safe_report_comments_ajax_url', $ajaxurl ); - wp_enqueue_script( $this->plugin_prefix . '-ajax-request', $this->plugin_url . '/js/ajax.js', array( 'jquery' ), '1.0', true ); + wp_enqueue_script( $this->plugin_prefix . '-ajax-request', $this->plugin_url . '/js/ajax.js', array( 'jquery' ), '2.0', true ); wp_localize_script( $this->plugin_prefix . '-ajax-request', 'SafeCommentsAjax', array( 'ajaxurl' => $ajaxurl ) ); // slightly dirty but needed due to possible problems with mapped domains. } @@ -455,7 +487,7 @@ public function mark_flagged( $comment_id ) { if ( ! isset( $data[ $comment_id ] ) ) { $data[ $comment_id ] = 0; } - $data[ $comment_id ]++; + ++$data[ $comment_id ]; $cookie = $this->serialize_cookie( $data ); @setcookie( $this->storagecookie, $cookie, time() + $this->cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN ); if ( SITECOOKIEPATH != COOKIEPATH ) { @@ -465,7 +497,7 @@ public function mark_flagged( $comment_id ) { if ( ! isset( $data[ $comment_id ] ) ) { $data[ $comment_id ] = 0; } - $data[ $comment_id ]++; + ++$data[ $comment_id ]; $cookie = $this->serialize_cookie( $data ); @setcookie( $this->storagecookie, $cookie, time() + $this->cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN ); if ( SITECOOKIEPATH != COOKIEPATH ) { @@ -485,14 +517,14 @@ public function mark_flagged( $comment_id ) { if ( ! isset( $transient[ $comment_id ] ) ) { $transient[ $comment_id ] = 0; } - $transient[ $comment_id ]++; + ++$transient[ $comment_id ]; set_transient( md5( $this->storagecookie . $remote_addr ), $transient, $this->transient_lifetime ); } $threshold = (int) get_option( $this->plugin_prefix . '_threshold' ); $current_reports = get_comment_meta( $comment_id, $this->plugin_prefix . '_reported', true ); - $current_reports++; + ++$current_reports; update_comment_meta( $comment_id, $this->plugin_prefix . '_reported', $current_reports ); @@ -507,8 +539,13 @@ public function mark_flagged( $comment_id ) { } if ( $current_reports >= $threshold ) { - do_action( 'safe_report_comments_mark_flagged', $comment_id ); - wp_set_comment_status( $comment_id, 'hold' ); + // Only pull the comment into the moderation queue if it is currently approved. + // Comments already in spam or trash have been dealt with, so reporting them + // should not resurrect them back into the moderation queue. + if ( 'approved' === wp_get_comment_status( $comment_id ) ) { + do_action( 'safe_report_comments_mark_flagged', $comment_id ); + wp_set_comment_status( $comment_id, 'hold' ); + } } } @@ -526,6 +563,34 @@ private function cond_die( $message ) { } } + /** + * Determine whether a comment may be reported through the public flagging workflow. + * + * The plugin only ever renders a report link for ordinary, publicly visible comments. + * Other records share the comments table and the same numeric ID space, for example + * WooCommerce order notes ( comment_type 'order_note' ), pingbacks and trackbacks. + * Without this gate, any numeric comment ID accompanied by the global report nonce + * could reach moderation, so the report target is validated before it is acted upon. + * + * @param int $comment_id The comment ID being reported. + * @return bool Whether the comment exists and is an ordinary, reportable comment type. + */ + public function is_reportable_comment( $comment_id ) { + $comment = get_comment( $comment_id ); + + // Reject non-existent comments so arbitrary IDs cannot accrue report metadata. + if ( ! $comment ) { + return false; + } + + // Only ordinary comments carry a report link. WordPress stores classic comments with + // an empty comment_type; 'comment' is accepted for forward compatibility. + $reportable_types = apply_filters( 'safe_report_comments_reportable_comment_types', array( '', 'comment' ) ); + $is_reportable = in_array( (string) $comment->comment_type, (array) $reportable_types, true ); + + return (bool) apply_filters( 'safe_report_comments_is_reportable_comment', $is_reportable, $comment ); + } + /** * Ajax callback to flag/report a comment. * @@ -537,6 +602,12 @@ public function flag_comment() { } $comment_id = (int) $_REQUEST['comment_id']; + + // Only ordinary public comments carry a report link; refuse anything else, e.g. order notes. + if ( ! $this->is_reportable_comment( $comment_id ) ) { + $this->cond_die( $this->invalid_values_message ); + } + if ( $this->already_flagged( $comment_id ) ) { $this->cond_die( $this->already_flagged_message ); } @@ -555,9 +626,9 @@ public function flag_comment() { * * @param int $comment_id The comment ID. * @param string $result_id Used as attribute ID in markup. - * @param string $text Text of link. + * @param string $text Text of link. Defaults to a translated "Report comment". */ - public function print_flagging_link( $comment_id = '', $result_id = '', $text = 'Report comment' ) { + public function print_flagging_link( $comment_id = '', $result_id = '', $text = '' ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaping done in get_flagging_link echo $this->get_flagging_link( $comment_id, $result_id, $text ); } @@ -567,10 +638,15 @@ public function print_flagging_link( $comment_id = '', $result_id = '', $text = * * @param int $comment_id The comment ID. * @param string $result_id Used as attribute ID in markup. - * @param string $text Text of link. + * @param string $text Text of link. Defaults to a translated "Report comment". */ - public function get_flagging_link( $comment_id = '', $result_id = '', $text = 'Report comment' ) { + public function get_flagging_link( $comment_id = '', $result_id = '', $text = '' ) { global $in_comment_loop; + + if ( '' === $text ) { + $text = __( 'Report comment', 'safe-report-comments' ); + } + if ( empty( $comment_id ) && ! $in_comment_loop ) { return esc_html__( 'Wrong usage of print_flagging_link().', 'safe-report-comments' ); } @@ -611,20 +687,55 @@ public function get_flagging_link( $comment_id = '', $result_id = '', $text = 'R } /** - * Callback function to automatically hook in the report link after the comment reply link. - * If you want to control the placement on your own define no_autostart_safe_report_comments in your functions.php file and initialize the class - * with $safe_report_comments = new Safe_Report_Comments( $auto_init = false ); + * Append the report link to a comment so the bundled script can position it. + * + * Registered on the `comment_text` filter in automatic mode. The previous approach + * parsed the theme-rendered reply-link markup with a regular expression and injected + * the report link into it. That broke whenever a theme altered the reply link, for + * example Twenty Twenty prepends a `do-not-scroll` class, which stopped the pattern + * from matching and silently dropped the link. + * + * Instead the link is rendered at the end of the comment and moved next to the reply + * link on the client, keyed off the reply link's core `data-commentid` attribute, so + * placement no longer depends on any particular markup. This works for both classic + * (`wp_list_comments()`) and block themes, and reaches comments at the maximum + * threading depth, which have no reply link for the old approach to target. * - * @param string $comment_reply_link Comment reply link markup. - * @return string Modified comment reply link markup. + * To place the link yourself, disable automatic mode (see the `no_autostart_safe_report_comments` + * constant) and call `do_action( 'comment_report_abuse_link' )` in your comment template. + * + * @param string $comment_text Text of the current comment. + * @param WP_Comment|null $comment The current comment object, when provided. + * @return string Comment text, with the report link appended for reportable comments. */ - public function add_flagging_link( $comment_reply_link ) { - if ( ! preg_match_all( '#^(.*)(]+>)(.+)()(.*)$#msiU', $comment_reply_link, $matches ) ) { - return '' . $comment_reply_link; + public function append_flagging_link( $comment_text, $comment = null ) { + // Never add the link within feeds. + if ( is_feed() ) { + return $comment_text; + } + + $comment_id = is_object( $comment ) ? (int) $comment->comment_ID : (int) get_comment_ID(); + + // Only ordinary public comments carry a report link, so skip pingbacks, order notes, etc. + if ( ! $comment_id || ! $this->is_reportable_comment( $comment_id ) ) { + return $comment_text; + } + + $link = $this->get_flagging_link( $comment_id ); + + // Nothing to show; for example, the visitor has already reported this comment. + if ( '' === trim( $link ) ) { + return $comment_text; } - $comment_reply_link = $matches[1][0] . $matches[2][0] . $matches[4][0] . $matches[5][0] . '' . $this->get_flagging_link() . '' . $matches[6][0]; - return apply_filters( 'safe_report_comments_comment_reply_link', $comment_reply_link ); + // Hidden until the script positions and reveals it; reporting requires JavaScript. + $wrapper = sprintf( + '', + $comment_id, + $link + ); + + return $comment_text . $wrapper; } /** @@ -658,5 +769,4 @@ public function manage_comment_reported_column( $column_name, $comment_id ) { break; } } - } diff --git a/composer.json b/composer.json index 7382b2b..f0ea828 100644 --- a/composer.json +++ b/composer.json @@ -40,6 +40,9 @@ "cs": [ "@php ./vendor/bin/phpcs" ], + "i18n": [ + "wp i18n make-pot . languages/safe-report-comments.pot --slug=safe-report-comments --domain=safe-report-comments" + ], "lint": [ "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --exclude vendor --exclude .git" ], diff --git a/js/ajax.js b/js/ajax.js index aceb515..3aff7ad 100644 --- a/js/ajax.js +++ b/js/ajax.js @@ -1,5 +1,5 @@ function safe_report_comments_flag_comment( comment_id, nonce, result_id ) { - jQuery.post( + jQuery.post( SafeCommentsAjax.ajaxurl, { comment_id : comment_id, @@ -10,12 +10,38 @@ function safe_report_comments_flag_comment( comment_id, nonce, result_id ) { withCredentials: true } }, - function(data) { jQuery( '#'+result_id).html(data); } + function( data ) { jQuery( '#' + result_id ).html( data ); } ); return false; } -jQuery( document ).ready( function() { - jQuery( '.hide-if-js' ).hide(); - jQuery( '.hide-if-no-js' ).show(); -}); +jQuery( function( $ ) { + // Legacy visibility toggling for manually placed links, e.g. a theme that + // calls do_action( 'comment_report_abuse_link' ) in its comment template. + $( '.hide-if-js' ).hide(); + $( '.hide-if-no-js' ).show(); + + // In automatic mode the report link is rendered at the end of each comment and + // hidden. Move it next to that comment's reply link, then reveal it. The reply + // link is found via its core data-commentid attribute, so placement does not + // depend on the theme's markup (the previous server-side regex broke whenever a + // theme altered the reply link, e.g. Twenty Twenty prepends a class). This works + // for classic and block themes alike. Comments at the maximum threading depth have + // no reply link, so their link is left in place at the end of the comment. + var positioned = {}; + + $( '.safe-comments-report-link' ).each( function() { + var $wrapper = $( this ); + var commentId = $wrapper.data( 'comment-id' ); + var $replyLink = $( '.comment-reply-link[data-commentid="' + commentId + '"]' ).first(); + + // Only move the first link for each comment, so a comment shown more than once + // on a page (for example, also in a widget) does not stack links by the reply link. + if ( $replyLink.length && ! positioned[ commentId ] ) { + $replyLink.after( $wrapper ); + positioned[ commentId ] = true; + } + + $wrapper.show(); + } ); +} ); diff --git a/languages/safe-report-comments.pot b/languages/safe-report-comments.pot new file mode 100644 index 0000000..952012c --- /dev/null +++ b/languages/safe-report-comments.pot @@ -0,0 +1,98 @@ +# Copyright (C) 2026 Thorsten Ott, Daniel Bachhuber, Automattic +# This file is distributed under the GPL-2.0-or-later. +msgid "" +msgstr "" +"Project-Id-Version: Safe Report Comments 0.5.0\n" +"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/safe-report-comments\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"POT-Creation-Date: 2026-08-20T00:37:59+00:00\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"X-Generator: WP-CLI 2.12.0\n" +"X-Domain: safe-report-comments\n" + +#. Plugin Name of the plugin +#: safe-report-comments.php +msgid "Safe Report Comments" +msgstr "" + +#. Plugin URI of the plugin +#: safe-report-comments.php +msgid "https://wordpress.org/plugins/safe-report-comments/" +msgstr "" + +#. Description of the plugin +#: safe-report-comments.php +msgid "Gives visitors the possibility to flag a comment as inappropriate. After reaching a threshold the comment is moved to moderation. If a comment is approved once by a moderator, future reports are ignored." +msgstr "" + +#. Author of the plugin +#: safe-report-comments.php +msgid "Thorsten Ott, Daniel Bachhuber, Automattic" +msgstr "" + +#. Author URI of the plugin +#: safe-report-comments.php +msgid "https://automattic.com" +msgstr "" + +#: class-safe-report-comments.php:180 +msgid "Thank you for your feedback. We will look into it." +msgstr "" + +#: class-safe-report-comments.php:181 +#: class-safe-report-comments.php:183 +msgid "It seems you already reported this comment." +msgstr "" + +#: class-safe-report-comments.php:182 +msgid "Cheating huh?" +msgstr "" + +#: class-safe-report-comments.php:213 +msgid "Allow comment flagging" +msgstr "" + +#: class-safe-report-comments.php:220 +msgid "Flagging threshold" +msgstr "" + +#: class-safe-report-comments.php:314 +msgid "Safe Comments:" +msgstr "" + +#: class-safe-report-comments.php:337 +msgid "Allow your visitors to flag a comment as inappropriate." +msgstr "" + +#: class-safe-report-comments.php:350 +msgid "Amount of user reports needed to send a comment to moderation?" +msgstr "" + +#: class-safe-report-comments.php:376 +msgid "Please revise your flagging threshold and enter a number between 1 and 100" +msgstr "" + +#: class-safe-report-comments.php:560 +msgid "Safe Report Comments Notice" +msgstr "" + +#: class-safe-report-comments.php:647 +msgid "Report comment" +msgstr "" + +#: class-safe-report-comments.php:651 +msgid "Wrong usage of print_flagging_link()." +msgstr "" + +#: class-safe-report-comments.php:658 +msgid "This comment does not exist." +msgstr "" + +#: class-safe-report-comments.php:748 +msgctxt "column name" +msgid "Reported" +msgstr "" diff --git a/readme.md b/readme.md deleted file mode 100644 index 380a6f7..0000000 --- a/readme.md +++ /dev/null @@ -1,114 +0,0 @@ -[![Run PHPUnit and PHPCS](https://github.com/Automattic/safe-report-comments/actions/workflows/integrate.yml/badge.svg)](https://github.com/Automattic/safe-report-comments/actions/workflows/integrate.yml) - -# Safe Report Comments - -This plugin gives your visitors the possibility to report a comment as inappropriate. After a set threshold is reached the comment is put into moderation where the moderator can decide whether or not he want to approve the comment or not. If a comment is approved by a moderator it will not be auto-moderated again while still counting the amount of reports. - -## Installation - -1. Download and unzip the plugin. -2. Copy the safe-report-comments directory into your plugins folder. -3. Visit your Plugins page and activate the plugin. -4. A new checkbox called "Allow comment flagging" will appear in the Settings->Discussion page. -5. Activate the flag and set the threshold value which will appear on the same page after activation - -## Screenshots - -![Simple activation via discussion settings](screenshot-1.png) - -![Amount of reports per comment is shown in comments administration screen](screenshot-2.png) - -![Fits well within most themes without any further action.](screenshot-3.png) - -![Ajax feedback right in place where available.](screenshot-4.png) - - -## Changelog - -### 0.4.1 (July 23, 2014) - -* Typo fix, props spencermorin - -### 0.4 (July 23, 2014) - -* Security fix, h/t vortfu - -### 0.3.2 (Mar. 6, 2013) -* New 'safe_report_comments_allow_moderated_to_be_reflagged' filter allows comments to be reflagged after being moderated. - -### 0.3.1 (Nov. 21, 2012) -* Use home_url() for generating the ajaxurl on mapped domains, but admin_url() where the domain isn't mapped. - -### 0.3 (Nov. 7, 2012) -* Coding standards and cleanup - -## Customizations - -By default this script should hook in just fine in most existing themes as it attaches itsself after the comment-reply link via the comment_reply_link filter. -In case this does not work out you can place the flagging link manually by defining no_autostart_safe_report_comments in your themes' functions.php file and initializing the class via `$safe_report_comments = new Safe_Report_Comments(false);`. - -Here is an example of a custom setup via `functions.php` and placing the link comments callback function. - -In `functions.php`: - -``` -//flag comments plugin included in themes' functions.php - disable plugin. -define( 'no_autostart_safe_report_comments', true ); -include_once( 'replace-with-path-to/safe-report-comments/safe-report-comments.php'); -// make sure not to auto-attach to comment reply link -$safe_report_comments = new Safe_Report_Comments(false); - -// change link layout to have a pipe prepended -add_filter( 'safe_report_comments_flagging_link', 'adjust_flagging_link' ); -function adjust_flagging_link( $link ) { - return ' | ' . $link; -} - -// adjust the text to "Report abuse" rather than "Report comment" -add_filter( 'safe_report_comments_flagging_link_text', 'adjust_flagging_text' ); -function adjust_flagging_text( $text ) { - return 'Report abuse'; -} -``` - -In your custom comment callback function used by `wp_list_comments`: [http://codex.wordpress.org/Template_Tags/wp_list_comments](http://codex.wordpress.org/Template_Tags/wp_list_comments) place the following action which will print the link. - -`` - -A possible callback function could look like this: - -``` -function mytheme_comment($comment, $args, $depth) { - $GLOBALS['comment'] = $comment; ?> -
  • id="li-comment-"> -
    -
    - ' ); ?> - %s says:'), get_comment_author_link()) ?> -
    - comment_approved == '0') : ?> - -
    - - - - - -
    - $depth, 'max_depth' => $args['max_depth']))) ?> -
    -
    - -
    -
    - Discussion page. -5. Activate the flag and set the threshold value which will appear on the same page after activation - -== Screenshots == - -1. Simple activation via discussion settings -2. Amount of reports per comment is shown in comments administration screen -3. Fits well within most themes without any further action. -4. Ajax feedback right in place where available. - -== Changelog == - -= 0.4.1 (July 23, 2014) = - -* Typo fix, props spencermorin - -= 0.4 (July 23, 2014) = - -* Security fix, h/t vortfu - -= 0.3.2 (Mar. 6, 2013) = -* New 'safe_report_comments_allow_moderated_to_be_reflagged' filter allows comments to be reflagged after being moderated. - -= 0.3.1 (Nov. 21, 2012) = -* Use home_url() for generating the ajaxurl on mapped domains, but admin_url() where the domain isn't mapped. - -= 0.3 (Nov. 7, 2012) = -* Coding standards and cleanup - -== Customizations == - -By default this script should hook in just fine in most existing themes as it attaches itsself after the comment-reply link via the comment_reply_link filter. -In case this does not work out you can place the flagging link manually by defining no_autostart_safe_report_comments in your themes' functions.php file and initializing the class via ``$safe_report_comments = new Safe_Report_Comments(false);``. - -Here is an example of a custom setup via functions.php and placing the link comments callback function. - -In functions.php: -` -//flag comments plugin included in themes' functions.php - disable plugin. -define( 'no_autostart_safe_report_comments', true ); -include_once( 'replace-with-path-to/safe-report-comments/safe-report-comments.php'); -// make sure not to auto-attach to comment reply link -$safe_report_comments = new Safe_Report_Comments(false); - -// change link layout to have a pipe prepended -add_filter( 'safe_report_comments_flagging_link', 'adjust_flagging_link' ); -function adjust_flagging_link( $link ) { - return ' | ' . $link; -} - -// adjust the text to "Report abuse" rather than "Report comment" -add_filter( 'safe_report_comments_flagging_link_text', 'adjust_flagging_text' ); -function adjust_flagging_text( $text ) { - return 'Report abuse'; -} -` - -In your custom comment callback function used by wp_list_comments: http://codex.wordpress.org/Template_Tags/wp_list_comments place the following action which will print the link. - -`` - -A possible callback function could look like this: -` -function mytheme_comment($comment, $args, $depth) { - $GLOBALS['comment'] = $comment; ?> -
  • id="li-comment-"> -
    -
    - ' ); ?> - %s says:'), get_comment_author_link()) ?> -
    - comment_approved == '0') : ?> - -
    - - - - - -
    - $depth, 'max_depth' => $args['max_depth']))) ?> -
    -
    - -
    -
    - plugin = new Safe_Report_Comments( false ); + + $this->comment_id = self::factory()->comment->create( + array( + 'comment_post_ID' => self::factory()->post->create(), + 'comment_content' => 'A reportable comment.', + ) + ); + } + + /** + * A reportable comment gets the hidden report wrapper, tagged with its ID. + */ + public function test_appends_wrapper_for_reportable_comment() { + $output = $this->plugin->append_flagging_link( 'Original text.', get_comment( $this->comment_id ) ); + + $this->assertStringStartsWith( 'Original text.', $output ); + $this->assertStringContainsString( 'class="safe-comments-report-link"', $output ); + $this->assertStringContainsString( 'data-comment-id="' . $this->comment_id . '"', $output ); + $this->assertStringContainsString( 'style="display:none;"', $output ); + $this->assertStringContainsString( 'safe_report_comments_flag_comment(', $output ); + } + + /** + * Non-reportable comment types, such as pingbacks, never receive a report link. + */ + public function test_skips_non_reportable_comment_type() { + $pingback_id = self::factory()->comment->create( + array( + 'comment_post_ID' => self::factory()->post->create(), + 'comment_type' => 'pingback', + 'comment_content' => 'A pingback.', + ) + ); + + $output = $this->plugin->append_flagging_link( 'Original text.', get_comment( $pingback_id ) ); + + $this->assertSame( 'Original text.', $output ); + } + + /** + * The report link is not appended within feeds. + */ + public function test_skips_in_feed() { + $this->go_to( '/?feed=rss2' ); + + $output = $this->plugin->append_flagging_link( 'Original text.', get_comment( $this->comment_id ) ); + + $this->assertSame( 'Original text.', $output ); + } +} diff --git a/tests/test-reportable-comment.php b/tests/test-reportable-comment.php new file mode 100644 index 0000000..db8315e --- /dev/null +++ b/tests/test-reportable-comment.php @@ -0,0 +1,158 @@ +plugin = new Safe_Report_Comments( false ); + $this->post_id = self::factory()->post->create(); + } + + /** + * An ordinary comment on a public post is reportable. + * + * @covers ::is_reportable_comment + */ + public function test_ordinary_comment_is_reportable(): void { + $comment_id = self::factory()->comment->create( + array( + 'comment_post_ID' => $this->post_id, + ) + ); + + $this->assertTrue( $this->plugin->is_reportable_comment( $comment_id ) ); + } + + /** + * A comment stored with an explicit 'comment' type is reportable. + * + * @covers ::is_reportable_comment + */ + public function test_explicit_comment_type_is_reportable(): void { + $comment_id = self::factory()->comment->create( + array( + 'comment_post_ID' => $this->post_id, + 'comment_type' => 'comment', + ) + ); + + $this->assertTrue( $this->plugin->is_reportable_comment( $comment_id ) ); + } + + /** + * Records that merely share the comments table are not reportable. + * + * Regression: WooCommerce stores internal order notes as comment_type + * 'order_note'. They never carry a public report link, so the anonymous + * flagging workflow must refuse to move them to moderation. + * + * @covers ::is_reportable_comment + * @dataProvider data_non_reportable_types + * + * @param string $comment_type Comment type that must be rejected. + */ + public function test_non_reportable_comment_types_are_rejected( string $comment_type ): void { + $comment_id = self::factory()->comment->create( + array( + 'comment_post_ID' => $this->post_id, + 'comment_type' => $comment_type, + ) + ); + + $this->assertFalse( $this->plugin->is_reportable_comment( $comment_id ) ); + } + + /** + * Comment types that must never be reportable. + * + * @return array[] + */ + public function data_non_reportable_types(): array { + return array( + 'WooCommerce order note' => array( 'order_note' ), + 'pingback' => array( 'pingback' ), + 'trackback' => array( 'trackback' ), + ); + } + + /** + * A non-existent comment ID is not reportable. + * + * @covers ::is_reportable_comment + */ + public function test_missing_comment_is_not_reportable(): void { + $this->assertFalse( $this->plugin->is_reportable_comment( PHP_INT_MAX ) ); + } + + /** + * Site owners can opt additional comment types in via the types filter. + * + * @covers ::is_reportable_comment + */ + public function test_reportable_types_filter_allows_opt_in(): void { + $note_id = self::factory()->comment->create( + array( + 'comment_post_ID' => $this->post_id, + 'comment_type' => 'order_note', + ) + ); + + $callback = static function ( array $types ): array { + $types[] = 'order_note'; + return $types; + }; + + add_filter( 'safe_report_comments_reportable_comment_types', $callback ); + $is_reportable = $this->plugin->is_reportable_comment( $note_id ); + remove_filter( 'safe_report_comments_reportable_comment_types', $callback ); + + $this->assertTrue( $is_reportable ); + } + + /** + * The final decision filter can veto an otherwise reportable comment. + * + * @covers ::is_reportable_comment + */ + public function test_is_reportable_comment_filter_can_veto(): void { + $comment_id = self::factory()->comment->create( + array( + 'comment_post_ID' => $this->post_id, + ) + ); + + add_filter( 'safe_report_comments_is_reportable_comment', '__return_false' ); + $is_reportable = $this->plugin->is_reportable_comment( $comment_id ); + remove_filter( 'safe_report_comments_is_reportable_comment', '__return_false' ); + + $this->assertFalse( $is_reportable ); + } +}