diff --git a/database.sql b/database.sql index d93c979f0..7eb756cf2 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ -- Friendica 2023.03-dev (Giant Rhubarb) --- DB_UPDATE_VERSION 1503 +-- DB_UPDATE_VERSION 1504 -- ------------------------------------------ @@ -1674,15 +1674,20 @@ CREATE TABLE IF NOT EXISTS `register` ( CREATE TABLE IF NOT EXISTS `report` ( `id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID', `uid` mediumint unsigned COMMENT 'Reporting user', + `reporter-id` int unsigned COMMENT 'Reporting contact', `cid` int unsigned NOT NULL COMMENT 'Reported contact', `comment` text COMMENT 'Report', + `category` varchar(20) COMMENT 'Category of the report (spam, violation, other)', + `rules` text COMMENT 'Violated rules', `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', PRIMARY KEY(`id`), INDEX `uid` (`uid`), INDEX `cid` (`cid`), + INDEX `reporter-id` (`reporter-id`), 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 ) DEFAULT COLLATE utf8mb4_general_ci COMMENT=''; diff --git a/doc/database/db_report.md b/doc/database/db_report.md index 9a87abe1f..92c0cced3 100644 --- a/doc/database/db_report.md +++ b/doc/database/db_report.md @@ -6,24 +6,28 @@ 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 | | -| cid | Reported contact | int unsigned | NO | | NULL | | -| comment | Report | 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 | | +| 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 | | Indexes ------------ -| Name | Fields | -| ------- | ------ | -| PRIMARY | id | -| uid | uid | -| cid | cid | +| Name | Fields | +| ----------- | ----------- | +| PRIMARY | id | +| uid | uid | +| cid | cid | +| reporter-id | reporter-id | Foreign Keys ------------ @@ -31,6 +35,7 @@ Foreign Keys | Field | Target Table | Target Field | |-------|--------------|--------------| | uid | [user](help/database/db_user) | uid | +| reporter-id | [contact](help/database/db_contact) | id | | cid | [contact](help/database/db_contact) | id | Return to [database documentation](help/database) diff --git a/src/Core/System.php b/src/Core/System.php index ec214fea9..65dce1bc0 100644 --- a/src/Core/System.php +++ b/src/Core/System.php @@ -665,10 +665,11 @@ class System /** * Fetch the system rules + * @param bool $numeric_id If set to "true", the rules are returned with a numeric id as key. * * @return array */ - public static function getRules(): array + public static function getRules(bool $numeric_id = false): array { $rules = []; $id = 0; @@ -681,7 +682,11 @@ class System foreach (explode("\n", trim($msg)) as $line) { $line = trim($line); if ($line) { - $rules[] = ['id' => (string)++$id, 'text' => $line]; + if ($numeric_id) { + $rules[++$id] = $line; + } else { + $rules[] = ['id' => (string)++$id, 'text' => $line]; + } } } } diff --git a/src/Moderation/Entity/Report.php b/src/Moderation/Entity/Report.php index 93d42b94e..3fbd77256 100644 --- a/src/Moderation/Entity/Report.php +++ b/src/Moderation/Entity/Report.php @@ -23,38 +23,49 @@ namespace Friendica\Moderation\Entity; /** * @property-read int $id - * @property-read int $uid + * @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 */ class Report extends \Friendica\BaseEntity { /** @var int|null */ protected $id; - /** @var int ID of the user making a moderation report*/ - protected $uid; + /** @var int ID of the contact making a moderation report*/ + protected $reporterId; /** @var int ID of the contact being reported*/ protected $cid; /** @var string Optional comment */ protected $comment; + /** @var string Optional category */ + protected $category; + /** @var string Violated rules */ + protected $rules; /** @var bool Whether this report should be forwarded to the remote server */ protected $forward; /** @var \DateTime|null 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; - public function __construct(int $uid, int $cid, \DateTime $created, string $comment = '', bool $forward = false, array $postUriIds = [], int $id = null) + 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->uid = $uid; + $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; } } diff --git a/src/Moderation/Factory/Report.php b/src/Moderation/Factory/Report.php index 17203d307..8e66d8ad5 100644 --- a/src/Moderation/Factory/Report.php +++ b/src/Moderation/Factory/Report.php @@ -35,12 +35,15 @@ class Report extends \Friendica\BaseFactory implements ICanCreateFromTableRow public function createFromTableRow(array $row, array $postUriIds = []): Entity\Report { return new Entity\Report( - $row['uid'], + $row['reporter-id'], $row['cid'], new \DateTime($row['created'] ?? 'now', new \DateTimeZone('UTC')), $row['comment'], + $row['category'], + $row['rules'], $row['forward'], $postUriIds, + $row['uid'], $row['id'], ); } @@ -51,6 +54,7 @@ class Report extends \Friendica\BaseFactory implements ICanCreateFromTableRow * @see \Friendica\Module\Api\Mastodon\Reports::post() * * @param int $uid + * @param int $reporterId * @param int $cid * @param string $comment * @param bool $forward @@ -58,15 +62,18 @@ class Report extends \Friendica\BaseFactory implements ICanCreateFromTableRow * @return Entity\Report * @throws \Exception */ - public function createFromReportsRequest(int $uid, int $cid, string $comment = '', bool $forward = false, array $postUriIds = []): Entity\Report + public function createFromReportsRequest(int $reporterId, int $cid, string $comment = '', string $category = null, string $rules = '', bool $forward = false, array $postUriIds = [], int $uid = null): Entity\Report { return new Entity\Report( - $uid, + $reporterId, $cid, new \DateTime('now', new \DateTimeZone('UTC')), $comment, + $category, + $rules, $forward, $postUriIds, + $uid, ); } } diff --git a/src/Moderation/Repository/Report.php b/src/Moderation/Repository/Report.php index 75a7f06dc..5322a4ffb 100644 --- a/src/Moderation/Repository/Report.php +++ b/src/Moderation/Repository/Report.php @@ -53,10 +53,13 @@ class Report extends \Friendica\BaseRepository public function save(\Friendica\Moderation\Entity\Report $Report) { $fields = [ - 'uid' => $Report->uid, - 'cid' => $Report->cid, - 'comment' => $Report->comment, - 'forward' => $Report->forward, + 'uid' => $Report->uid, + 'reporter-id' => $Report->reporterId, + 'cid' => $Report->cid, + 'comment' => $Report->comment, + 'category' => $Report->category, + 'rules' => $Report->rules, + 'forward' => $Report->forward, ]; $postUriIds = $Report->postUriIds; diff --git a/src/Module/Api/Mastodon/Reports.php b/src/Module/Api/Mastodon/Reports.php index 6ae54eb6f..98213f9be 100644 --- a/src/Module/Api/Mastodon/Reports.php +++ b/src/Module/Api/Mastodon/Reports.php @@ -54,10 +54,12 @@ class Reports extends BaseApi self::checkAllowedScope(self::SCOPE_WRITE); $request = $this->getRequest([ - 'account_id' => '', // ID of the account to report - 'status_ids' => [], // Array of Statuses to attach to the report, for context - 'comment' => '', // Reason for the report (default max 1000 characters) - 'forward' => false, // If the account is remote, should the report be forwarded to the remote admin? + 'account_id' => '', // ID of the account to report + 'status_ids' => [], // Array of Statuses to attach to the report, for context + 'comment' => '', // Reason for the report (default max 1000 characters) + 'category' => 'other', // Specify if the report is due to spam, violation of enumerated instance rules, or some other reason. + 'rule_ids' => [], // For violation category reports, specify the ID of the exact rules broken. + 'forward' => false, // If the account is remote, should the report be forwarded to the remote admin? ], $request); $contact = Contact::getById($request['account_id'], ['id']); @@ -65,7 +67,16 @@ class Reports extends BaseApi throw new HTTPException\NotFoundException('Account ' . $request['account_id'] . ' not found'); } - $report = $this->reportFactory->createFromReportsRequest(self::getCurrentUserID(), $request['account_id'], $request['comment'], $request['forward'], $request['status_ids']); + $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()); $this->reportRepo->save($report); diff --git a/src/Protocol/ActivityPub/Processor.php b/src/Protocol/ActivityPub/Processor.php index 843f4b8cf..e98fa4596 100644 --- a/src/Protocol/ActivityPub/Processor.php +++ b/src/Protocol/ActivityPub/Processor.php @@ -1837,22 +1837,25 @@ class Processor return; } - $status_ids = $activity['object_ids']; - array_shift($status_ids); + $reporter_id = Contact::getIdForURL($activity['actor']); + if (empty($reporter_id)) { + Logger::info('Unknown actor', ['activity' => $activity]); + Queue::remove($activity); + return; + } $uri_ids = []; - foreach ($status_ids as $status_id) { + foreach ($activity['object_ids'] as $status_id) { $post = Post::selectFirst(['uri-id'], ['uri' => $status_id]); if (!empty($post['uri-id'])) { $uri_ids[] = $post['uri-id']; } } - // @todo We should store the actor - $report = DI::reportFactory()->createFromReportsRequest(0, $account_id, $activity['content'], false, $uri_ids); + $report = DI::reportFactory()->createFromReportsRequest($reporter_id, $account_id, $activity['content'], null, '', false, $uri_ids); DI::report()->save($report); - Logger::info('Stored report', ['account_id' => $account_id, 'comment' => $activity['content'], 'status_ids' => $status_ids]); + Logger::info('Stored report', ['reporter' => $reporter_id, 'account_id' => $account_id, 'comment' => $activity['content'], 'object_ids' => $activity['object_ids']]); } /** diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index ac2bb0e1e..1d573fa87 100644 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -55,7 +55,7 @@ use Friendica\Database\DBA; if (!defined('DB_UPDATE_VERSION')) { - define('DB_UPDATE_VERSION', 1503); + define('DB_UPDATE_VERSION', 1504); } return [ @@ -1673,8 +1673,11 @@ return [ "fields" => [ "id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"], "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"], "comment" => ["type" => "text", "comment" => "Report"], + "category" => ["type" => "varchar(20)", "comment" => "Category of the report (spam, violation, other)"], + "rules" => ["type" => "text", "comment" => "Violated rules"], "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"], @@ -1683,6 +1686,7 @@ return [ "PRIMARY" => ["id"], "uid" => ["uid"], "cid" => ["cid"], + "reporter-id" => ["reporter-id"], ] ], "report-post" => [ diff --git a/tests/src/Moderation/Factory/ReportTest.php b/tests/src/Moderation/Factory/ReportTest.php index 0f2970910..286b52992 100644 --- a/tests/src/Moderation/Factory/ReportTest.php +++ b/tests/src/Moderation/Factory/ReportTest.php @@ -33,41 +33,53 @@ class ReportTest extends MockedTest return [ 'default' => [ 'row' => [ - 'id' => 11, - 'uid' => 12, - 'cid' => 13, - 'comment' => '', - 'forward' => false, - 'created' => null + 'id' => 11, + 'uid' => 12, + 'reporter-id' => 14, + 'cid' => 13, + 'comment' => '', + 'category' => null, + 'rules' => '', + 'forward' => false, + 'created' => null ], 'postUriIds' => [], 'assertion' => new Entity\Report( - 12, + 14, 13, new \DateTime('now', new \DateTimeZone('UTC')), '', + null, + '', false, [], + 12, 11, ), ], 'full' => [ 'row' => [ - 'id' => 11, - 'uid' => 12, - 'cid' => 13, - 'comment' => 'Report', - 'forward' => true, - 'created' => '2021-10-12 12:23:00' + 'id' => 11, + 'uid' => 12, + 'reporter-id' => 14, + 'cid' => 13, + 'comment' => 'Report', + 'category' => 'violation', + 'rules' => 'Rules', + 'forward' => true, + 'created' => '2021-10-12 12:23:00' ], 'postUriIds' => [89, 90], 'assertion' => new Entity\Report( - 12, + 14, 13, new \DateTime('2021-10-12 12:23:00', new \DateTimeZone('UTC')), 'Report', + 'violation', + 'Rules', true, [89, 90], + 12, 11 ), ], @@ -81,8 +93,11 @@ class ReportTest extends MockedTest $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); @@ -103,34 +118,46 @@ class ReportTest extends MockedTest { return [ 'default' => [ - 'uid' => 12, - 'cid' => 13, - 'comment' => '', - 'forward' => false, - 'postUriIds' => [], - 'assertion' => new Entity\Report( - 12, + '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, [], + 12, null ), ], 'full' => [ - 'uid' => 12, - 'cid' => 13, - 'comment' => 'Report', - 'forward' => true, - 'postUriIds' => [89, 90], - 'assertion' => new Entity\Report( - 12, + 'reporter-id' => 14, + 'cid' => 13, + 'comment' => 'Report', + 'category' => 'violation', + 'rules' => 'Rules', + 'forward' => true, + 'postUriIds' => [89, 90], + 'uid' => 12, + 'assertion' => new Entity\Report( + 14, 13, new \DateTime('now', new \DateTimeZone('UTC')), 'Report', + 'violation', + 'Rules', true, [89, 90], + 12, null ), ], @@ -140,10 +167,10 @@ class ReportTest extends MockedTest /** * @dataProvider dataCreateFromReportsRequest */ - public function testCreateFromReportsRequest(int $uid, int $cid, string $comment, bool $forward, array $postUriIds, Entity\Report $assertion) + public function testCreateFromReportsRequest(int $reporter, int $cid, string $comment, string $category = null, string $rules = '', bool $forward, array $postUriIds, int $uid, Entity\Report $assertion) { $factory = new Factory\Report(new NullLogger()); - $this->assertReport($factory->createFromReportsRequest($uid, $cid, $comment, $forward, $postUriIds), $assertion); + $this->assertReport($factory->createFromReportsRequest($reporter, $cid, $comment, $category, $rules, $forward, $postUriIds, $uid), $assertion); } }