]> git.mxchange.org Git - friendica.git/commitdiff
Add fields to Report entity
authorHypolite Petovan <hypolite@mrpetovan.com>
Sat, 10 Dec 2022 14:08:12 +0000 (09:08 -0500)
committerHypolite Petovan <hypolite@mrpetovan.com>
Sun, 9 Jul 2023 22:32:42 +0000 (18:32 -0400)
- Add clock dependency to Moderation\Factory\Report
- Change DateTime field to DateTimeImmutable to satisfy Clock return type
- Add category, status and resolution constants

13 files changed:
.gitignore
database.sql
doc/database.md
doc/database/db_report-post.md
doc/database/db_report-rule.md [new file with mode: 0644]
doc/database/db_report.md
src/Moderation/Entity/Report.php
src/Moderation/Factory/Report.php
src/Moderation/Repository/Report.php
src/Module/Api/Mastodon/Reports.php
src/Protocol/ActivityPub/Processor.php
static/dbstructure.config.php
tests/src/Moderation/Factory/ReportTest.php

index 779e46ee8f747b30ffca8335c54a9e73869b392f..2889d12b5f03a51dab4f529af6fb4f07c2c9ce7c 100644 (file)
@@ -19,7 +19,7 @@ robots.txt
 /doc/cache
 
 #ignore reports, should be generated with every build
-report/
+/report/
 
 #ignore config files from eclipse, we don't want IDE files in our repository
 .project
index 4d1f3cd607bfbf998a8dfb12cf16d04f56739744..5b6f1695c389c0c3bdc3cd933349e87ba9bcb5ea 100644 (file)
@@ -1695,19 +1695,33 @@ CREATE TABLE IF NOT EXISTS `report` (
        `uid` mediumint unsigned COMMENT 'Reporting user',
        `reporter-id` int unsigned COMMENT 'Reporting contact',
        `cid` int unsigned NOT NULL COMMENT 'Reported contact',
+       `gsid` int unsigned NOT NULL COMMENT 'Reported contact server',
        `comment` text COMMENT 'Report',
-       `category` varchar(20) COMMENT 'Category of the report (spam, violation, other)',
-       `rules` text COMMENT 'Violated rules',
+       `category-id` int unsigned NOT NULL DEFAULT 1 COMMENT 'Report category, one of Entity\Report::CATEGORY_*',
        `forward` boolean COMMENT 'Forward the report to the remote server',
-       `created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
-       `status` tinyint unsigned COMMENT 'Status of the report',
+       `public-remarks` text COMMENT 'Remarks shared with the reporter',
+       `private-remarks` text COMMENT 'Remarks shared with the moderation team',
+       `last-editor-uid` mediumint unsigned COMMENT 'Last editor user',
+       `assigned-uid` mediumint unsigned COMMENT 'Assigned moderator user',
+       `status` tinyint unsigned NOT NULL COMMENT 'Status of the report, one of Entity\Report::STATUS_*',
+       `resolution` tinyint unsigned COMMENT 'Resolution of the report, one of Entity\Report::RESOLUTION_*',
+       `created` datetime(6) NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
+       `edited` datetime(6) COMMENT 'Last time the report has been edited',
         PRIMARY KEY(`id`),
         INDEX `uid` (`uid`),
         INDEX `cid` (`cid`),
         INDEX `reporter-id` (`reporter-id`),
+        INDEX `gsid` (`gsid`),
+        INDEX `assigned-uid` (`assigned-uid`),
+        INDEX `status-resolution` (`status`,`resolution`),
+        INDEX `created` (`created`),
+        INDEX `edited` (`edited`),
        FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE,
        FOREIGN KEY (`reporter-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
-       FOREIGN KEY (`cid`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
+       FOREIGN KEY (`cid`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
+       FOREIGN KEY (`gsid`) REFERENCES `gserver` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
+       FOREIGN KEY (`last-editor-uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE,
+       FOREIGN KEY (`assigned-uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE
 ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='';
 
 --
@@ -1721,7 +1735,18 @@ CREATE TABLE IF NOT EXISTS `report-post` (
         INDEX `uri-id` (`uri-id`),
        FOREIGN KEY (`rid`) REFERENCES `report` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
        FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
-) DEFAULT COLLATE utf8mb4_general_ci COMMENT='';
+) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Individual posts attached to a moderation report';
+
+--
+-- TABLE report-rule
+--
+CREATE TABLE IF NOT EXISTS `report-rule` (
+       `rid` int unsigned NOT NULL COMMENT 'Report id',
+       `line-id` int unsigned NOT NULL COMMENT 'Terms of service rule line number, may become invalid after a TOS change.',
+       `text` text NOT NULL COMMENT 'Terms of service rule text recorded at the time of the report',
+        PRIMARY KEY(`rid`,`line-id`),
+       FOREIGN KEY (`rid`) REFERENCES `report` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
+) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Terms of service rule lines relevant to a moderation report';
 
 --
 -- TABLE search
index 3c03d31b1fe1cea82c7c9e3d89bb7bc1894795bb..b9285915740b9e75036c5061f4a8f70a572cc800 100644 (file)
@@ -77,7 +77,8 @@ Database Tables
 | [push_subscriber](help/database/db_push_subscriber) | Used for OStatus: Contains feed subscribers |
 | [register](help/database/db_register) | registrations requiring admin approval |
 | [report](help/database/db_report) |  |
-| [report-post](help/database/db_report-post) |  |
+| [report-post](help/database/db_report-post) | Individual posts attached to a moderation report |
+| [report-rule](help/database/db_report-rule) | Terms of service rule lines relevant to a moderation report |
 | [search](help/database/db_search) |  |
 | [session](help/database/db_session) | web session storage |
 | [storage](help/database/db_storage) | Data stored by Database storage backend |
index fcaff6c2e48f88f5c71c8c9e11c801dcc1fa90c0..d005b31e3438a3e62e8530aa9cfd2a835a45fcb5 100644 (file)
@@ -1,7 +1,7 @@
 Table report-post
 ===========
 
-
+Individual posts attached to a moderation report
 
 Fields
 ------
diff --git a/doc/database/db_report-rule.md b/doc/database/db_report-rule.md
new file mode 100644 (file)
index 0000000..f82d757
--- /dev/null
@@ -0,0 +1,29 @@
+Table report-rule
+===========
+
+Terms of service rule lines relevant to a moderation report
+
+Fields
+------
+
+| Field   | Description                                                               | Type         | Null | Key | Default | Extra |
+| ------- | ------------------------------------------------------------------------- | ------------ | ---- | --- | ------- | ----- |
+| rid     | Report id                                                                 | int unsigned | NO   | PRI | NULL    |       |
+| line-id | Terms of service rule line number, may become invalid after a TOS change. | int unsigned | NO   | PRI | NULL    |       |
+| text    | Terms of service rule text recorded at the time of the report             | text         | NO   |     | NULL    |       |
+
+Indexes
+------------
+
+| Name    | Fields       |
+| ------- | ------------ |
+| PRIMARY | rid, line-id |
+
+Foreign Keys
+------------
+
+| Field | Target Table | Target Field |
+|-------|--------------|--------------|
+| rid | [report](help/database/db_report) | id |
+
+Return to [database documentation](help/database)
index 92c0cced36c689b14dc4ca94d13223ad81924fad..cb2bcb1895cddf01f46d38bb03d033665c654978 100644 (file)
@@ -6,28 +6,39 @@ Table report
 Fields
 ------
 
-| Field       | Description                                     | Type               | Null | Key | Default             | Extra          |
-| ----------- | ----------------------------------------------- | ------------------ | ---- | --- | ------------------- | -------------- |
-| id          | sequential ID                                   | int unsigned       | NO   | PRI | NULL                | auto_increment |
-| uid         | Reporting user                                  | mediumint unsigned | YES  |     | NULL                |                |
-| reporter-id | Reporting contact                               | int unsigned       | YES  |     | NULL                |                |
-| cid         | Reported contact                                | int unsigned       | NO   |     | NULL                |                |
-| comment     | Report                                          | text               | YES  |     | NULL                |                |
-| category    | Category of the report (spam, violation, other) | varchar(20)        | YES  |     | NULL                |                |
-| rules       | Violated rules                                  | text               | YES  |     | NULL                |                |
-| forward     | Forward the report to the remote server         | boolean            | YES  |     | NULL                |                |
-| created     |                                                 | datetime           | NO   |     | 0001-01-01 00:00:00 |                |
-| status      | Status of the report                            | tinyint unsigned   | YES  |     | NULL                |                |
+| Field           | Description                                                  | Type               | Null | Key | Default             | Extra          |
+| --------------- | ------------------------------------------------------------ | ------------------ | ---- | --- | ------------------- | -------------- |
+| id              | sequential ID                                                | int unsigned       | NO   | PRI | NULL                | auto_increment |
+| uid             | Reporting user                                               | mediumint unsigned | YES  |     | NULL                |                |
+| reporter-id     | Reporting contact                                            | int unsigned       | YES  |     | NULL                |                |
+| cid             | Reported contact                                             | int unsigned       | NO   |     | NULL                |                |
+| gsid            | Reported contact server                                      | int unsigned       | NO   |     | NULL                |                |
+| comment         | Report                                                       | text               | YES  |     | NULL                |                |
+| category-id     | Report category, one of Entity\Report::CATEGORY_*            | int unsigned       | NO   |     | 1                   |                |
+| forward         | Forward the report to the remote server                      | boolean            | YES  |     | NULL                |                |
+| public-remarks  | Remarks shared with the reporter                             | text               | YES  |     | NULL                |                |
+| private-remarks | Remarks shared with the moderation team                      | text               | YES  |     | NULL                |                |
+| last-editor-uid | Last editor user                                             | mediumint unsigned | YES  |     | NULL                |                |
+| assigned-uid    | Assigned moderator user                                      | mediumint unsigned | YES  |     | NULL                |                |
+| status          | Status of the report, one of Entity\Report::STATUS_*         | tinyint unsigned   | NO   |     | NULL                |                |
+| resolution      | Resolution of the report, one of Entity\Report::RESOLUTION_* | tinyint unsigned   | YES  |     | NULL                |                |
+| created         |                                                              | datetime(6)        | NO   |     | 0001-01-01 00:00:00 |                |
+| edited          | Last time the report has been edited                         | datetime(6)        | YES  |     | NULL                |                |
 
 Indexes
 ------------
 
-| Name        | Fields      |
-| ----------- | ----------- |
-| PRIMARY     | id          |
-| uid         | uid         |
-| cid         | cid         |
-| reporter-id | reporter-id |
+| Name              | Fields             |
+| ----------------- | ------------------ |
+| PRIMARY           | id                 |
+| uid               | uid                |
+| cid               | cid                |
+| reporter-id       | reporter-id        |
+| gsid              | gsid               |
+| assigned-uid      | assigned-uid       |
+| status-resolution | status, resolution |
+| created           | created            |
+| edited            | edited             |
 
 Foreign Keys
 ------------
@@ -37,5 +48,8 @@ Foreign Keys
 | uid | [user](help/database/db_user) | uid |
 | reporter-id | [contact](help/database/db_contact) | id |
 | cid | [contact](help/database/db_contact) | id |
+| gsid | [gserver](help/database/db_gserver) | id |
+| last-editor-uid | [user](help/database/db_user) | uid |
+| assigned-uid | [user](help/database/db_user) | uid |
 
 Return to [database documentation](help/database)
index 8d9a1cedca31954e0767de96ff3625fe0a78cd57..ffc60047a0fea1b416669a9804827d59168c0387 100644 (file)
 
 namespace Friendica\Moderation\Entity;
 
+use Friendica\Moderation\Collection;
+
 /**
- * @property-read int            $id
- * @property-read int            $reporterId
- * @property-read int            $cid
- * @property-read string         $comment
- * @property-read string|null    $category
- * @property-read bool           $forward
- * @property-read array          $postUriIds
- * @property-read int            $uid
- * @property-read \DateTime|null $created
+ * @property-read int                     $id
+ * @property-read int                     $reporterCid
+ * @property-read int                     $cid
+ * @property-read int                     $gsid
+ * @property-read string                  $comment
+ * @property-read string                  $publicRemarks
+ * @property-read string                  $privateRemarks
+ * @property-read bool                    $forward
+ * @property-read int                     $category
+ * @property-read int                     $status
+ * @property-read int|null                $resolution
+ * @property-read int                     $reporterUid
+ * @property-read int|null                $lastEditorUid
+ * @property-read int|null                $assignedUid
+ * @property-read \DateTimeImmutable      $created
+ * @property-read \DateTimeImmutable|null $edited
+ * @property-read Collection\Report\Posts $posts
+ * @property-read Collection\Report\Rules $rules
  */
-class Report extends \Friendica\BaseEntity
+final class Report extends \Friendica\BaseEntity
 {
+       const CATEGORY_OTHER = 1;
+       const CATEGORY_SPAM = 2;
+       const CATEGORY_ILLEGAL = 4;
+       const CATEGORY_SAFETY = 8;
+       const CATEGORY_UNWANTED = 16;
+       const CATEGORY_VIOLATION = 32;
+
+       const CATEGORIES  = [
+               self::CATEGORY_OTHER,
+               self::CATEGORY_SPAM,
+               self::CATEGORY_ILLEGAL,
+               self::CATEGORY_SAFETY,
+               self::CATEGORY_UNWANTED,
+               self::CATEGORY_VIOLATION,
+       ];
+
+       const STATUS_CLOSED = 0;
+       const STATUS_OPEN = 1;
+
+       const RESOLUTION_ACCEPTED = 0;
+       const RESOLUTION_REJECTED = 1;
+
        /** @var int|null */
        protected $id;
-       /** @var int ID of the contact making a moderation report*/
-       protected $reporterId;
-       /** @var int ID of the contact being reported*/
+       /** @var int ID of the contact making a moderation report */
+       protected $reporterCid;
+       /** @var int ID of the contact being reported */
        protected $cid;
-       /** @var string Optional comment */
+       /** @var int ID of the gserver of the contact being reported */
+       protected $gsid;
+       /** @var string Reporter comment */
        protected $comment;
-       /** @var string Optional category */
+       /** @var int One of CATEGORY_* */
        protected $category;
-       /** @var string Violated rules */
-       protected $rules;
+       /** @var int ID of the user making a moderation report, null in case of an incoming forwarded report */
+       protected $reporterUid;
        /** @var bool Whether this report should be forwarded to the remote server */
        protected $forward;
-       /** @var \DateTime|null When the report was created */
+       /** @var \DateTimeImmutable When the report was created */
        protected $created;
-       /** @var array Optional list of URI IDs of posts supporting the report*/
-       protected $postUriIds;
-       /** @var int ID of the user making a moderation report*/
-       protected $uid;
+       /** @var Collection\Report\Rules List of terms of service rule lines being possibly violated */
+       protected $rules;
+       /** @var Collection\Report\Posts List of URI IDs of posts supporting the report */
+       protected $posts;
+       /** @var string Remarks shared with the reporter */
+       protected $publicRemarks;
+       /** @var string Remarks shared with the moderation team */
+       protected $privateRemarks;
+       /** @var \DateTimeImmutable|null When the report was last edited */
+       protected $edited;
+       /** @var int One of STATUS_* */
+       protected $status;
+       /** @var int|null One of RESOLUTION_* if any */
+       protected $resolution;
+       /** @var int|null Assigned moderator user id if any */
+       protected $assignedUid;
+       /** @var int|null Last editor user ID if any */
+       protected $lastEditorUid;
 
-       public function __construct(int $reporterId, int $cid, \DateTime $created, string $comment = '', string $category = null, string $rules = '', bool $forward = false, array $postUriIds = [], int $uid = null, int $id = null)
-       {
-               $this->reporterId = $reporterId;
-               $this->cid        = $cid;
-               $this->created    = $created;
-               $this->comment    = $comment;
-               $this->category   = $category;
-               $this->rules      = $rules;
-               $this->forward    = $forward;
-               $this->postUriIds = $postUriIds;
-               $this->uid        = $uid;
-               $this->id         = $id;
+       public function __construct(
+               int $reporterCid,
+               int $cid,
+               int $gsid,
+               \DateTimeImmutable $created,
+               int $category,
+               int $reporterUid = null,
+               string $comment = '',
+               bool $forward = false,
+               Collection\Report\Posts $posts = null,
+               Collection\Report\Rules $rules = null,
+               string $publicRemarks = '',
+               string $privateRemarks = '',
+               \DateTimeImmutable $edited = null,
+               int $status = self::STATUS_OPEN,
+               int $resolution = null,
+               int $assignedUid = null,
+               int $lastEditorUid = null,
+               int $id = null
+       ) {
+               $this->reporterCid    = $reporterCid;
+               $this->cid            = $cid;
+               $this->gsid           = $gsid;
+               $this->created        = $created;
+               $this->category       = $category;
+               $this->reporterUid    = $reporterUid;
+               $this->comment        = $comment;
+               $this->forward        = $forward;
+               $this->posts          = $posts ?? new Collection\Report\Posts();
+               $this->rules          = $rules ?? new Collection\Report\Rules();
+               $this->publicRemarks  = $publicRemarks;
+               $this->privateRemarks = $privateRemarks;
+               $this->edited         = $edited;
+               $this->status         = $status;
+               $this->resolution     = $resolution;
+               $this->assignedUid    = $assignedUid;
+               $this->lastEditorUid  = $lastEditorUid;
+               $this->id             = $id;
        }
 }
index 7cf4009705ef385d9f01885d7807eac7d2e0ee2f..1c1e9cfb7f77ee3146517bac90cad7af70f18b7a 100644 (file)
 namespace Friendica\Moderation\Factory;
 
 use Friendica\Capabilities\ICanCreateFromTableRow;
+use Friendica\Core\System;
+use Friendica\Moderation\Collection;
 use Friendica\Moderation\Entity;
+use Psr\Clock\ClockInterface;
+use Psr\Log\LoggerInterface;
 
 class Report extends \Friendica\BaseFactory implements ICanCreateFromTableRow
 {
+       /** @var ClockInterface */
+       private $clock;
+
+       public function __construct(LoggerInterface $logger, ClockInterface $clock)
+       {
+               parent::__construct($logger);
+
+               $this->clock = $clock;
+       }
+
        /**
-        * @param array $row        `report` table row
-        * @param array $postUriIds List of post URI ids from the `report-post` table
+        * @param array                        $row   `report` table row
+        * @param Collection\Report\Posts|null $posts List of posts attached to the report
+        * @param Collection\Report\Rules|null $rules List of rules from the terms of service, see System::getRules()
         * @return Entity\Report
         * @throws \Exception
         */
-       public function createFromTableRow(array $row, array $postUriIds = []): Entity\Report
+       public function createFromTableRow(array $row, Collection\Report\Posts $posts = null, Collection\Report\Rules $rules = null): Entity\Report
        {
                return new Entity\Report(
                        $row['reporter-id'],
                        $row['cid'],
-                       new \DateTime($row['created'] ?? 'now', new \DateTimeZone('UTC')),
+                       $row['gsid'],
+                       new \DateTimeImmutable($row['created'], new \DateTimeZone('UTC')),
+                       $row['category-id'],
+                       $row['uid'],
                        $row['comment'],
-                       $row['category'],
-                       $row['rules'],
                        $row['forward'],
-                       $postUriIds,
-                       $row['uid'],
+                       $posts ?? new Collection\Report\Posts(),
+                       $rules ?? new Collection\Report\Rules(),
+                       $row['public-remarks'],
+                       $row['private-remarks'],
+                       $row['edited'] ? new \DateTimeImmutable($row['edited'], new \DateTimeZone('UTC')) : null,
+                       $row['status'],
+                       $row['resolution'],
+                       $row['assigned-uid'],
+                       $row['last-editor-uid'],
                        $row['id'],
                );
        }
@@ -51,29 +74,44 @@ class Report extends \Friendica\BaseFactory implements ICanCreateFromTableRow
        /**
         * Creates a Report entity from a Mastodon API /reports request
         *
-        * @see \Friendica\Module\Api\Mastodon\Reports::post()
-        *
-        * @param int    $uid
+        * @param array  $rules      Line-number indexed node rules array, see System::getRules(true)
         * @param int    $reporterId
         * @param int    $cid
+        * @param int    $gsid
         * @param string $comment
+        * @param string $category
         * @param bool   $forward
         * @param array  $postUriIds
+        * @param array  $ruleIds
+        * @param ?int   $uid
         * @return Entity\Report
-        * @throws \Exception
+        * @see \Friendica\Module\Api\Mastodon\Reports::post()
         */
-       public function createFromReportsRequest(int $reporterId, int $cid, string $comment = '', string $category = null, string $rules = '', bool $forward = false, array $postUriIds = [], int $uid = null): Entity\Report
+       public function createFromReportsRequest(array $rules, int $reporterId, int $cid, int $gsid, string $comment = '', string $category = '', bool $forward = false, array $postUriIds = [], array $ruleIds = [], int $uid = null): Entity\Report
        {
+               if (count($ruleIds)) {
+                       $categoryId = Entity\Report::CATEGORY_VIOLATION;
+               } elseif ($category == 'spam') {
+                       $categoryId = Entity\Report::CATEGORY_SPAM;
+               } else {
+                       $categoryId = Entity\Report::CATEGORY_OTHER;
+               }
+
                return new Entity\Report(
                        $reporterId,
                        $cid,
-                       new \DateTime('now', new \DateTimeZone('UTC')),
+                       $gsid,
+                       $this->clock->now(),
+                       $categoryId,
+                       $uid,
                        $comment,
-                       $category,
-                       $rules,
                        $forward,
-                       $postUriIds,
-                       $uid,
+                       new Collection\Report\Posts(array_map(function ($uriId) {
+                               return new Entity\Report\Post($uriId);
+                       }, $postUriIds)),
+                       new Collection\Report\Rules(array_map(function ($lineId) use ($rules) {
+                               return new Entity\Report\Rule($lineId, $rules[$lineId] ?? '');
+                       }, $ruleIds)),
                );
        }
 }
index 101f153896114011c378f17946f70e01a2965d01..b4f2bf2dead34bcd7e038fb0705fa453939623e2 100644 (file)
@@ -25,24 +25,30 @@ use Friendica\BaseEntity;
 use Friendica\Core\Logger;
 use Friendica\Database\Database;
 use Friendica\Model\Post;
+use Friendica\Moderation\Factory;
+use Friendica\Moderation\Collection;
 use Friendica\Network\HTTPException\NotFoundException;
 use Friendica\Util\DateTimeFormat;
 use Psr\Log\LoggerInterface;
 
-class Report extends \Friendica\BaseRepository
+final class Report extends \Friendica\BaseRepository
 {
        protected static $table_name = 'report';
 
-       /**
-        * @var \Friendica\Moderation\Factory\Report
-        */
+       /** @var Factory\Report */
        protected $factory;
+       /** @var Factory\Report\Post */
+       protected $postFactory;
+       /** @var Factory\Report\Rule */
+       protected $ruleFactory;
 
-       public function __construct(Database $database, LoggerInterface $logger, \Friendica\Moderation\Factory\Report $factory)
+       public function __construct(Database $database, LoggerInterface $logger, Factory\Report $factory, Factory\Report\Post $postFactory, Factory\Report\Rule $ruleFactory)
        {
                parent::__construct($database, $logger, $factory);
 
-               $this->factory = $factory;
+               $this->factory     = $factory;
+               $this->postFactory = $postFactory;
+               $this->ruleFactory = $postFactory;
        }
 
        public function selectOneById(int $lastInsertId): \Friendica\Moderation\Entity\Report
@@ -53,34 +59,43 @@ class Report extends \Friendica\BaseRepository
        public function save(\Friendica\Moderation\Entity\Report $Report)
        {
                $fields = [
-                       'uid'         => $Report->uid,
-                       'reporter-id' => $Report->reporterId,
-                       'cid'         => $Report->cid,
-                       'comment'     => $Report->comment,
-                       'category'    => $Report->category,
-                       'rules'       => $Report->rules,
-                       'forward'     => $Report->forward,
+                       'reporter-id'     => $Report->reporterCid,
+                       'uid'             => $Report->reporterUid,
+                       'cid'             => $Report->cid,
+                       'gsid'            => $Report->gsid,
+                       'comment'         => $Report->comment,
+                       'forward'         => $Report->forward,
+                       'category-id'     => $Report->category,
+                       'public-remarks'  => $Report->publicRemarks,
+                       'private-remarks' => $Report->privateRemarks,
+                       'last-editor-uid' => $Report->lastEditorUid,
+                       'assigned-uid'    => $Report->assignedUid,
+                       'status'          => $Report->status,
+                       'resolution'      => $Report->resolution,
+                       'created'         => $Report->created->format(DateTimeFormat::MYSQL),
+                       'edited'          => $Report->edited ? $Report->edited->format(DateTimeFormat::MYSQL) : null,
                ];
 
-               $postUriIds = $Report->postUriIds;
-
                if ($Report->id) {
                        $this->db->update(self::$table_name, $fields, ['id' => $Report->id]);
                } else {
-                       $fields['created'] = DateTimeFormat::utcNow();
                        $this->db->insert(self::$table_name, $fields, Database::INSERT_IGNORE);
 
-                       $Report = $this->selectOneById($this->db->lastInsertId());
-               }
+                       $newReportId = $this->db->lastInsertId();
 
-               $this->db->delete('report-post', ['rid' => $Report->id]);
+                       foreach ($Report->posts as $post) {
+                               if (Post::exists(['uri-id' => $post->uriId])) {
+                                       $this->db->insert('report-post', ['rid' => $newReportId, 'uri-id' => $post->uriId, 'status' => $post->status]);
+                               } else {
+                                       Logger::notice('Post does not exist', ['uri-id' => $post->uriId, 'report' => $Report]);
+                               }
+                       }
 
-               foreach ($postUriIds as $uriId) {
-                       if (Post::exists(['uri-id' => $uriId])) {
-                               $this->db->insert('report-post', ['rid' => $Report->id, 'uri-id' => $uriId]);
-                       } else {
-                               Logger::notice('Post does not exist', ['uri-id' => $uriId, 'report' => $Report]);
+                       foreach ($Report->rules as $rule) {
+                               $this->db->insert('report-rule', ['rid' => $newReportId, 'line-id' => $rule->lineId, 'text' => $rule->text]);
                        }
+
+                       $Report = $this->selectOneById($this->db->lastInsertId());
                }
 
                return $Report;
@@ -88,13 +103,14 @@ class Report extends \Friendica\BaseRepository
 
        protected function _selectOne(array $condition, array $params = []): BaseEntity
        {
-               $fields = $this->db->selectFirst(static::$table_name, [], $condition, $params);
+               $fields = $this->db->selectFirst(self::$table_name, [], $condition, $params);
                if (!$this->db->isResult($fields)) {
                        throw new NotFoundException();
                }
 
-               $postUriIds = array_column($this->db->selectToArray('report-post', ['uri-id'], ['rid' => $condition['id'] ?? 0]), 'uri-id');
+               $reportPosts = new Collection\Report\Posts(array_map([$this->postFactory, 'createFromTableRow'], $this->db->selectToArray('report-post', ['uri-id', 'status'], ['rid' => $condition['id'] ?? 0])));
+               $reportRules = new Collection\Report\Rules(array_map([$this->ruleFactory, 'createFromTableRow'], $this->db->selectToArray('report-rule', ['line-id', 'line-text'], ['rid' => $condition['id'] ?? 0])));
 
-               return $this->factory->createFromTableRow($fields, $postUriIds);
+               return $this->factory->createFromTableRow($fields, $reportPosts, $reportRules);
        }
 }
index 1f0f2839e665149a05dc3ed224534da1b2047bbb..401d3da1916ba13f1abb3e8355c3fbbb696f185d 100644 (file)
@@ -62,21 +62,23 @@ class Reports extends BaseApi
                        'forward'    => false,   // If the account is remote, should the report be forwarded to the remote admin?
                ], $request);
 
-               $contact = Contact::getById($request['account_id'], ['id']);
+               $contact = Contact::getById($request['account_id'], ['id', 'gsid']);
                if (empty($contact)) {
                        throw new HTTPException\NotFoundException('Account ' . $request['account_id'] . ' not found');
                }
 
-               $violation = '';
-               $rules     = System::getRules(true);
-
-               foreach ($request['rule_ids'] as $key) {
-                       if (!empty($rules[$key])) {
-                               $violation .= $rules[$key] . "\n";
-                       }
-               }
-
-               $report = $this->reportFactory->createFromReportsRequest(Contact::getPublicIdByUserId(self::getCurrentUserID()), $request['account_id'], $request['comment'], $request['category'], trim($violation), $request['forward'], $request['status_ids'], self::getCurrentUserID());
+               $report = $this->reportFactory->createFromReportsRequest(
+                       System::getRules(),
+                       Contact::getPublicIdByUserId(self::getCurrentUserID()),
+                       $contact['id'],
+                       $contact['gsid'],
+                       $request['comment'],
+                       $request['category'],
+                       $request['forward'],
+                       $request['status_ids'],
+                       $request['rule_ids'],
+                       self::getCurrentUserID()
+               );
 
                $this->reportRepo->save($report);
 
index 7ba5266b46574a156f107a0c473775585d5ed3e0..a76ef6307bd939c9ef6ceda39a378969c47956b9 100644 (file)
@@ -42,6 +42,7 @@ use Friendica\Model\Mail;
 use Friendica\Model\Tag;
 use Friendica\Model\User;
 use Friendica\Model\Post;
+use Friendica\Moderation\Entity\Report;
 use Friendica\Protocol\Activity;
 use Friendica\Protocol\ActivityPub;
 use Friendica\Protocol\Delivery;
@@ -1893,8 +1894,8 @@ class Processor
         */
        public static function ReportAccount(array $activity)
        {
-               $account_id = Contact::getIdForURL($activity['object_id']);
-               if (empty($account_id)) {
+               $account = Contact::getByURL($activity['object_id'], null, ['id', 'gsid']);
+               if (empty($account)) {
                        Logger::info('Unknown account', ['activity' => $activity]);
                        Queue::remove($activity);
                        return;
@@ -1915,10 +1916,10 @@ class Processor
                        }
                }
 
-               $report = DI::reportFactory()->createFromReportsRequest($reporter_id, $account_id, $activity['content'], null, '', false, $uri_ids);
+               $report = DI::reportFactory()->createFromReportsRequest(System::getRules(true), $reporter_id, $account['id'], $account['gsid'], $activity['content'], 'other', false, $uri_ids);
                DI::report()->save($report);
 
-               Logger::info('Stored report', ['reporter' => $reporter_id, 'account_id' => $account_id, 'comment' => $activity['content'], 'object_ids' => $activity['object_ids']]);
+               Logger::info('Stored report', ['reporter' => $reporter_id, 'account' => $account, 'comment' => $activity['content'], 'object_ids' => $activity['object_ids']]);
        }
 
        /**
index 49a3f9329dab998ade8b06bdb255a15b950a13d2..33cf1d86ad98a3932e0a91088f3780494ffe94db 100644 (file)
@@ -1691,22 +1691,33 @@ return [
                        "uid" => ["type" => "mediumint unsigned", "foreign" => ["user" => "uid"], "comment" => "Reporting user"],
                        "reporter-id" => ["type" => "int unsigned", "foreign" => ["contact" => "id"], "comment" => "Reporting contact"],
                        "cid" => ["type" => "int unsigned", "not null" => "1", "foreign" => ["contact" => "id"], "comment" => "Reported contact"],
+                       "gsid" => ["type" => "int unsigned", "not null" => "1", "foreign" => ["gserver" => "id"], "comment" => "Reported contact server"],
                        "comment" => ["type" => "text", "comment" => "Report"],
-                       "category" => ["type" => "varchar(20)", "comment" => "Category of the report (spam, violation, other)"],
-                       "rules" => ["type" => "text", "comment" => "Violated rules"],
+                       "category-id" => ["type" => "int unsigned", "not null" => 1, "default" => \Friendica\Moderation\Entity\Report::CATEGORY_OTHER, "comment" => "Report category, one of Entity\Report::CATEGORY_*"],
                        "forward" => ["type" => "boolean", "comment" => "Forward the report to the remote server"],
-                       "created" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""],
-                       "status" => ["type" => "tinyint unsigned", "comment" => "Status of the report"],
+                       "public-remarks" => ["type" => "text", "comment" => "Remarks shared with the reporter"],
+                       "private-remarks" => ["type" => "text", "comment" => "Remarks shared with the moderation team"],
+                       "last-editor-uid" => ["type" => "mediumint unsigned", "foreign" => ["user" => "uid"], "comment" => "Last editor user"],
+                       "assigned-uid" => ["type" => "mediumint unsigned", "foreign" => ["user" => "uid"], "comment" => "Assigned moderator user"],
+                       "status" => ["type" => "tinyint unsigned", "not null" => "1", "comment" => "Status of the report, one of Entity\Report::STATUS_*"],
+                       "resolution" => ["type" => "tinyint unsigned", "comment" => "Resolution of the report, one of Entity\Report::RESOLUTION_*"],
+                       "created" => ["type" => "datetime(6)", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""],
+                       "edited" => ["type" => "datetime(6)", "comment" => "Last time the report has been edited"],
                ],
                "indexes" => [
                        "PRIMARY" => ["id"],
                        "uid" => ["uid"],
                        "cid" => ["cid"],
                        "reporter-id" => ["reporter-id"],
+                       "gsid" => ["gsid"],
+                       "assigned-uid" => ["assigned-uid"],
+                       "status-resolution" => ["status", "resolution"],
+                       "created" => ["created"],
+                       "edited" => ["edited"],
                ]
        ],
        "report-post" => [
-               "comment" => "",
+               "comment" => "Individual posts attached to a moderation report",
                "fields" => [
                        "rid" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "foreign" => ["report" => "id"], "comment" => "Report id"],
                        "uri-id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "foreign" => ["item-uri" => "id"], "comment" => "Uri-id of the reported post"],
@@ -1717,6 +1728,17 @@ return [
                        "uri-id" => ["uri-id"],
                ]
        ],
+       "report-rule" => [
+               "comment" => "Terms of service rule lines relevant to a moderation report",
+               "fields" => [
+                       "rid" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "foreign" => ["report" => "id"], "comment" => "Report id"],
+                       "line-id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "comment" => "Terms of service rule line number, may become invalid after a TOS change."],
+                       "text" => ["type" => "text", "not null" => "1", "comment" => "Terms of service rule text recorded at the time of the report"],
+               ],
+               "indexes" => [
+                       "PRIMARY" => ["rid", "line-id"],
+               ]
+       ],
        "search" => [
                "comment" => "",
                "fields" => [
index ab94643f4b3a66a27509ced0a4a7dbd15cae7d51..35e3a50506da5c3c0da4982c38328d2a900c24ac 100644 (file)
 
 namespace Friendica\Test\src\Moderation\Factory;
 
+use Friendica\Moderation\Collection;
 use Friendica\Moderation\Factory;
 use Friendica\Moderation\Entity;
 use Friendica\Test\MockedTest;
+use Friendica\Util\Clock\FrozenClock;
+use Friendica\Util\DateTimeFormat;
+use Psr\Clock\ClockInterface;
 use Psr\Log\NullLogger;
 
 class ReportTest extends MockedTest
 {
        public function dataCreateFromTableRow(): array
        {
+               $clock = new FrozenClock();
+
+               // We need to strip the microseconds part to match database stored timestamps
+               $nowSeconds = $clock->now()->setTime(
+                       $clock->now()->format('H'),
+                       $clock->now()->format('i'),
+                       $clock->now()->format('s')
+               );
+
                return [
                        'default' => [
+                               'clock' => $clock,
                                'row' => [
-                                       'id'          => 11,
-                                       'uid'         => 12,
-                                       'reporter-id' => 14,
-                                       'cid'         => 13,
-                                       'comment'     => '',
-                                       'category'    => null,
-                                       'rules'       => '',
-                                       'forward'     => false,
-                                       'created'     => null
+                                       'id'              => 11,
+                                       'reporter-id'     => 12,
+                                       'uid'             => null,
+                                       'cid'             => 13,
+                                       'gsid'            => 14,
+                                       'comment'         => '',
+                                       'forward'         => false,
+                                       'category-id'     => Entity\Report::CATEGORY_SPAM,
+                                       'public-remarks'  => '',
+                                       'private-remarks' => '',
+                                       'last-editor-uid' => null,
+                                       'assigned-uid'    => null,
+                                       'status'          => Entity\Report::STATUS_OPEN,
+                                       'resolution'      => null,
+                                       'created'         => $nowSeconds->format(DateTimeFormat::MYSQL),
+                                       'edited'          => null,
                                ],
-                               'postUriIds' => [],
+                               'posts' => new Collection\Report\Posts(),
+                               'rules' => new Collection\Report\Rules(),
                                'assertion'  => new Entity\Report(
-                                       14,
+                                       12,
                                        13,
-                                       new \DateTime('now', new \DateTimeZone('UTC')),
-                                       '',
+                                       14,
+                                       $nowSeconds,
+                                       Entity\Report::CATEGORY_SPAM,
                                        null,
                                        '',
                                        false,
-                                       [],
-                                       12,
-                                       11,
+                                       new Collection\Report\Posts(),
+                                       new Collection\Report\Rules(),
+                                       '',
+                                       '',
+                                       null,
+                                       Entity\Report::STATUS_OPEN,
+                                       null,
+                                       null,
+                                       null,
+                                       11
                                ),
                        ],
                        'full' => [
+                               'clock' => $clock,
                                'row' => [
-                                       'id'          => 11,
-                                       'uid'         => 12,
-                                       'reporter-id' => 14,
-                                       'cid'         => 13,
-                                       'comment'     => 'Report',
-                                       'category'    => 'violation',
-                                       'rules'       => 'Rules',
-                                       'forward'     => true,
-                                       'created'     => '2021-10-12 12:23:00'
+                                       'id'              => 11,
+                                       'reporter-id'     => 42,
+                                       'uid'             => 12,
+                                       'cid'             => 13,
+                                       'gsid'            => 14,
+                                       'comment'         => 'Report',
+                                       'forward'         => true,
+                                       'category-id'     => Entity\Report::CATEGORY_VIOLATION,
+                                       'public-remarks'  => 'Public remarks',
+                                       'private-remarks' => 'Private remarks',
+                                       'last-editor-uid' => 15,
+                                       'assigned-uid'    => 16,
+                                       'status'          => Entity\Report::STATUS_CLOSED,
+                                       'resolution'      => Entity\Report::RESOLUTION_ACCEPTED,
+                                       'created'         => '2021-10-12 12:23:00',
+                                       'edited'          => '2021-12-10 21:08:00',
                                ],
-                               'postUriIds' => [89, 90],
+                               'posts' => new Collection\Report\Posts([
+                                       new Entity\Report\Post(89),
+                                       new Entity\Report\Post(90),
+                               ]),
+                               'rules' => new Collection\Report\Rules([
+                                       new Entity\Report\Rule(1, 'No hate speech'),
+                                       new Entity\Report\Rule(3, 'No commercial promotion'),
+                               ]),
                                'assertion'  => new Entity\Report(
-                                       14,
+                                       42,
                                        13,
-                                       new \DateTime('2021-10-12 12:23:00', new \DateTimeZone('UTC')),
+                                       14,
+                                       new \DateTimeImmutable('2021-10-12 12:23:00', new \DateTimeZone('UTC')),
+                                       Entity\Report::CATEGORY_VIOLATION,
+                                       12,
                                        'Report',
-                                       'violation',
-                                       'Rules',
                                        true,
-                                       [89, 90],
-                                       12,
+                                       new Collection\Report\Posts([
+                                               new Entity\Report\Post(89),
+                                               new Entity\Report\Post(90),
+                                       ]),
+                                       new Collection\Report\Rules([
+                                               new Entity\Report\Rule(1, 'No hate speech'),
+                                               new Entity\Report\Rule(3, 'No commercial promotion'),
+                                       ]),
+                                       'Public remarks',
+                                       'Private remarks',
+                                       new \DateTimeImmutable('2021-12-10 21:08:00', new \DateTimeZone('UTC')),
+                                       Entity\Report::STATUS_CLOSED,
+                                       Entity\Report::RESOLUTION_ACCEPTED,
+                                       16,
+                                       15,
                                        11
                                ),
                        ],
                ];
        }
 
-       public function assertReport(Entity\Report $assertion, Entity\Report $report)
-       {
-               self::assertEquals(
-                       $assertion->id,
-                       $report->id
-               );
-               self::assertEquals($assertion->uid, $report->uid);
-               self::assertEquals($assertion->reporterId, $report->reporterId);
-               self::assertEquals($assertion->cid, $report->cid);
-               self::assertEquals($assertion->comment, $report->comment);
-               self::assertEquals($assertion->category, $report->category);
-               self::assertEquals($assertion->rules, $report->rules);
-               self::assertEquals($assertion->forward, $report->forward);
-               // No way to test "now" at the moment
-               //self::assertEquals($assertion->created, $report->created);
-               self::assertEquals($assertion->postUriIds, $report->postUriIds);
-       }
-
        /**
         * @dataProvider dataCreateFromTableRow
         */
-       public function testCreateFromTableRow(array $row, array $postUriIds, Entity\Report $assertion)
+       public function testCreateFromTableRow(ClockInterface $clock, array $row, Collection\Report\Posts $posts, Collection\Report\Rules $rules, Entity\Report $assertion)
        {
-               $factory = new Factory\Report(new NullLogger());
+               $factory = new Factory\Report(new NullLogger(), $clock);
 
-               $this->assertReport($factory->createFromTableRow($row, $postUriIds), $assertion);
+               $this->assertEquals($factory->createFromTableRow($row, $posts, $rules), $assertion);
        }
 
        public function dataCreateFromReportsRequest(): array
        {
+               $clock = new FrozenClock();
+
                return [
                        'default' => [
-                               'reporter-id' => 14,
-                               'cid'         => 13,
-                               'comment'     => '',
-                               'category'    => null,
-                               'rules'       => '',
-                               'forward'     => false,
-                               'postUriIds'  => [],
-                               'uid'         => 12,
-                               'assertion'   => new Entity\Report(
-                                       14,
-                                       13,
-                                       new \DateTime('now', new \DateTimeZone('UTC')),
-                                       '',
-                                       null,
-                                       '',
-                                       false,
-                                       [],
+                               'clock'      => $clock,
+                               'rules'      => [],
+                               'reporterId' => 12,
+                               'cid'        => 13,
+                               'gsid'       => 14,
+                               'comment'    => '',
+                               'category'   => 'spam',
+                               'forward'    => false,
+                               'postUriIds' => [],
+                               'ruleIds'    => [],
+                               'uid'        => null,
+                               'assertion'  => new Entity\Report(
                                        12,
-                                       null
+                                       13,
+                                       14,
+                                       $clock->now(),
+                                       Entity\Report::CATEGORY_SPAM,
                                ),
                        ],
                        'full' => [
-                               'reporter-id' => 14,
-                               'cid'         => 13,
-                               'comment'     => 'Report',
-                               'category'    => 'violation',
-                               'rules'       => 'Rules',
-                               'forward'     => true,
-                               'postUriIds'  => [89, 90],
-                               'uid'         => 12,
-                               'assertion'   => new Entity\Report(
-                                       14,
+                               'clock'      => $clock,
+                               'rules'      => ['', 'Rule 1', 'Rule 2', 'Rule 3'],
+                               'reporterId' => 12,
+                               'cid'        => 13,
+                               'gsid'       => 14,
+                               'comment'    => 'Report',
+                               'category'   => 'violation',
+                               'forward'    => true,
+                               'postUriIds' => [89, 90],
+                               'ruleIds'    => [1, 3],
+                               'uid'        => 42,
+                               'assertion'  => new Entity\Report(
+                                       12,
                                        13,
-                                       new \DateTime('now', new \DateTimeZone('UTC')),
+                                       14,
+                                       $clock->now(),
+                                       Entity\Report::CATEGORY_VIOLATION,
+                                       42,
                                        'Report',
-                                       'violation',
-                                       'Rules',
                                        true,
-                                       [89, 90],
+                                       new Collection\Report\Posts([
+                                               new Entity\Report\Post(89),
+                                               new Entity\Report\Post(90)
+                                       ]),
+                                       new Collection\Report\Rules([
+                                               new Entity\Report\Rule(1, 'Rule 1'),
+                                               new Entity\Report\Rule(3, 'Rule 3'),
+                                       ]),
+                               ),
+                       ],
+                       'forced-violation' => [
+                               'clock'      => $clock,
+                               'rules'      => ['', 'Rule 1', 'Rule 2', 'Rule 3'],
+                               'reporterId' => 12,
+                               'cid'        => 13,
+                               'gsid'       => 14,
+                               'comment'    => 'Report',
+                               'category'   => 'other',
+                               'forward'    => false,
+                               'postUriIds' => [],
+                               'ruleIds'    => [2, 3],
+                               'uid'        => null,
+                               'assertion'  => new Entity\Report(
+                                       12,
+                                       13,
+                                       14,
+                                       $clock->now(),
+                                       Entity\Report::CATEGORY_VIOLATION,
+                                       null,
+                                       'Report',
+                                       false,
+                                       new Collection\Report\Posts(),
+                                       new Collection\Report\Rules([
+                                               new Entity\Report\Rule(2, 'Rule 2'),
+                                               new Entity\Report\Rule(3, 'Rule 3'),
+                                       ]),
+                               ),
+                       ],
+                       'unknown-category' => [
+                               'clock'      => $clock,
+                               'rules'      => ['', 'Rule 1', 'Rule 2', 'Rule 3'],
+                               'reporterId' => 12,
+                               'cid'        => 13,
+                               'gsid'       => 14,
+                               'comment'    => '',
+                               'category'   => 'unknown',
+                               'forward'    => false,
+                               'postUriIds' => [],
+                               'ruleIds'    => [],
+                               'uid'        => null,
+                               'assertion'  => new Entity\Report(
                                        12,
-                                       null
+                                       13,
+                                       14,
+                                       $clock->now(),
+                                       Entity\Report::CATEGORY_OTHER,
                                ),
                        ],
                ];
@@ -167,10 +265,10 @@ class ReportTest extends MockedTest
        /**
         * @dataProvider dataCreateFromReportsRequest
         */
-       public function testCreateFromReportsRequest(int $reporter, int $cid, string $comment, string $category = null, string $rules = '', bool $forward, array $postUriIds, int $uid, Entity\Report $assertion)
+       public function testCreateFromReportsRequest(ClockInterface $clock, array $rules, int $reporterId, int $cid, int $gsid, string $comment, string $category, bool $forward, array $postUriIds, array $ruleIds, int $uid = null, Entity\Report $assertion)
        {
-               $factory = new Factory\Report(new NullLogger());
+               $factory = new Factory\Report(new NullLogger(), $clock);
 
-               $this->assertReport($factory->createFromReportsRequest($reporter, $cid, $comment, $category, $rules, $forward, $postUriIds, $uid), $assertion);
+               $this->assertEquals($factory->createFromReportsRequest($rules, $reporterId, $cid, $gsid, $comment, $category, $forward, $postUriIds, $ruleIds, $uid), $assertion);
        }
 }