From d29a84ae461d75b14473157b842a025380433b5b Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Wed, 14 Dec 2022 10:01:01 -0500 Subject: [PATCH 1/4] Add Posts and Rules sub-entities and collections for Moderation\Report --- src/Moderation/Collection/Report/Posts.php | 33 ++++++++++++++++ src/Moderation/Collection/Report/Rules.php | 33 ++++++++++++++++ src/Moderation/Entity/Report/Post.php | 44 ++++++++++++++++++++++ src/Moderation/Entity/Report/Rule.php | 40 ++++++++++++++++++++ src/Moderation/Factory/Report/Post.php | 35 +++++++++++++++++ src/Moderation/Factory/Report/Rule.php | 35 +++++++++++++++++ 6 files changed, 220 insertions(+) create mode 100644 src/Moderation/Collection/Report/Posts.php create mode 100644 src/Moderation/Collection/Report/Rules.php create mode 100644 src/Moderation/Entity/Report/Post.php create mode 100644 src/Moderation/Entity/Report/Rule.php create mode 100644 src/Moderation/Factory/Report/Post.php create mode 100644 src/Moderation/Factory/Report/Rule.php diff --git a/src/Moderation/Collection/Report/Posts.php b/src/Moderation/Collection/Report/Posts.php new file mode 100644 index 000000000..374654dc3 --- /dev/null +++ b/src/Moderation/Collection/Report/Posts.php @@ -0,0 +1,33 @@ +. + * + */ + +namespace Friendica\Moderation\Collection\Report; + +class Posts extends \Friendica\BaseCollection +{ + /** + * @return \Friendica\Moderation\Entity\Report\Post + */ + public function current(): \Friendica\Moderation\Entity\Report\Post + { + return parent::current(); + } +} diff --git a/src/Moderation/Collection/Report/Rules.php b/src/Moderation/Collection/Report/Rules.php new file mode 100644 index 000000000..4803c6bda --- /dev/null +++ b/src/Moderation/Collection/Report/Rules.php @@ -0,0 +1,33 @@ +. + * + */ + +namespace Friendica\Moderation\Collection\Report; + +class Rules extends \Friendica\BaseCollection +{ + /** + * @return \Friendica\Moderation\Entity\Report\Rule + */ + public function current(): \Friendica\Moderation\Entity\Report\Rule + { + return parent::current(); + } +} diff --git a/src/Moderation/Entity/Report/Post.php b/src/Moderation/Entity/Report/Post.php new file mode 100644 index 000000000..956052a30 --- /dev/null +++ b/src/Moderation/Entity/Report/Post.php @@ -0,0 +1,44 @@ +. + * + */ + +namespace Friendica\Moderation\Entity\Report; + +/** + * @property-read int $uriId URI Id of the reported post + * @property-read int $status One of STATUS_* + */ +final class Post extends \Friendica\BaseEntity +{ + const STATUS_NO_ACTION = 0; + const STATUS_UNLISTED = 1; + const STATUS_DELETED = 2; + + /** @var int */ + protected $uriId; + /** @var int|null */ + protected $status; + + public function __construct(int $uriId, int $status = self::STATUS_NO_ACTION) + { + $this->uriId = $uriId; + $this->status = $status; + } +} diff --git a/src/Moderation/Entity/Report/Rule.php b/src/Moderation/Entity/Report/Rule.php new file mode 100644 index 000000000..3d1d1f8d1 --- /dev/null +++ b/src/Moderation/Entity/Report/Rule.php @@ -0,0 +1,40 @@ +. + * + */ + +namespace Friendica\Moderation\Entity\Report; + +/** + * @property-read int $lineId Terms of service text line number + * @property-read string $text Terms of service rule text + */ +final class Rule extends \Friendica\BaseEntity +{ + /** @var int */ + protected $lineId; + /** @var string */ + protected $text; + + public function __construct(int $lineId, string $text) + { + $this->lineId = $lineId; + $this->text = $text; + } +} diff --git a/src/Moderation/Factory/Report/Post.php b/src/Moderation/Factory/Report/Post.php new file mode 100644 index 000000000..f70460a28 --- /dev/null +++ b/src/Moderation/Factory/Report/Post.php @@ -0,0 +1,35 @@ +. + * + */ + +namespace Friendica\Moderation\Factory\Report; + +use Friendica\Capabilities\ICanCreateFromTableRow; + +class Post extends \Friendica\BaseFactory implements ICanCreateFromTableRow +{ + public function createFromTableRow(array $row): \Friendica\Moderation\Entity\Report\Post + { + return new \Friendica\Moderation\Entity\Report\Post( + $row['uri-id'], + $row['status'] + ); + } +} diff --git a/src/Moderation/Factory/Report/Rule.php b/src/Moderation/Factory/Report/Rule.php new file mode 100644 index 000000000..334570630 --- /dev/null +++ b/src/Moderation/Factory/Report/Rule.php @@ -0,0 +1,35 @@ +. + * + */ + +namespace Friendica\Moderation\Factory\Report; + +use Friendica\Capabilities\ICanCreateFromTableRow; + +class Rule extends \Friendica\BaseFactory implements ICanCreateFromTableRow +{ + public function createFromTableRow(array $row): \Friendica\Moderation\Entity\Report\Rule + { + return new \Friendica\Moderation\Entity\Report\Rule( + $row['line-id'], + $row['text'] + ); + } +} From 76de49a25cd08d63b08b3352e31a1ed0f3145603 Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Sat, 10 Dec 2022 09:08:12 -0500 Subject: [PATCH 2/4] Add fields to Report entity - Add clock dependency to Moderation\Factory\Report - Change DateTime field to DateTimeImmutable to satisfy Clock return type - Add category, status and resolution constants --- .gitignore | 2 +- database.sql | 37 ++- doc/database.md | 3 +- doc/database/db_report-post.md | 2 +- doc/database/db_report-rule.md | 29 ++ doc/database/db_report.md | 50 ++-- src/Moderation/Entity/Report.php | 143 +++++++--- src/Moderation/Factory/Report.php | 78 ++++-- src/Moderation/Repository/Report.php | 72 +++-- src/Module/Api/Mastodon/Reports.php | 24 +- src/Protocol/ActivityPub/Processor.php | 9 +- static/dbstructure.config.php | 32 ++- tests/src/Moderation/Factory/ReportTest.php | 284 +++++++++++++------- 13 files changed, 543 insertions(+), 222 deletions(-) create mode 100644 doc/database/db_report-rule.md diff --git a/.gitignore b/.gitignore index 779e46ee8..2889d12b5 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/database.sql b/database.sql index 4d1f3cd60..5b6f1695c 100644 --- a/database.sql +++ b/database.sql @@ -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 diff --git a/doc/database.md b/doc/database.md index 3c03d31b1..b92859157 100644 --- a/doc/database.md +++ b/doc/database.md @@ -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 | diff --git a/doc/database/db_report-post.md b/doc/database/db_report-post.md index fcaff6c2e..d005b31e3 100644 --- a/doc/database/db_report-post.md +++ b/doc/database/db_report-post.md @@ -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 index 000000000..f82d757eb --- /dev/null +++ b/doc/database/db_report-rule.md @@ -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) diff --git a/doc/database/db_report.md b/doc/database/db_report.md index 92c0cced3..cb2bcb189 100644 --- a/doc/database/db_report.md +++ b/doc/database/db_report.md @@ -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) diff --git a/src/Moderation/Entity/Report.php b/src/Moderation/Entity/Report.php index 8d9a1cedc..ffc60047a 100644 --- a/src/Moderation/Entity/Report.php +++ b/src/Moderation/Entity/Report.php @@ -21,51 +21,126 @@ 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; } } diff --git a/src/Moderation/Factory/Report.php b/src/Moderation/Factory/Report.php index 7cf400970..1c1e9cfb7 100644 --- a/src/Moderation/Factory/Report.php +++ b/src/Moderation/Factory/Report.php @@ -22,28 +22,51 @@ 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['comment'], - $row['category'], - $row['rules'], - $row['forward'], - $postUriIds, + $row['gsid'], + new \DateTimeImmutable($row['created'], new \DateTimeZone('UTC')), + $row['category-id'], $row['uid'], + $row['comment'], + $row['forward'], + $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')), - $comment, - $category, - $rules, - $forward, - $postUriIds, + $gsid, + $this->clock->now(), + $categoryId, $uid, + $comment, + $forward, + 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)), ); } } diff --git a/src/Moderation/Repository/Report.php b/src/Moderation/Repository/Report.php index 101f15389..b4f2bf2de 100644 --- a/src/Moderation/Repository/Report.php +++ b/src/Moderation/Repository/Report.php @@ -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 ($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->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 ($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); } } diff --git a/src/Module/Api/Mastodon/Reports.php b/src/Module/Api/Mastodon/Reports.php index 1f0f2839e..401d3da19 100644 --- a/src/Module/Api/Mastodon/Reports.php +++ b/src/Module/Api/Mastodon/Reports.php @@ -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); diff --git a/src/Protocol/ActivityPub/Processor.php b/src/Protocol/ActivityPub/Processor.php index 7ba5266b4..a76ef6307 100644 --- a/src/Protocol/ActivityPub/Processor.php +++ b/src/Protocol/ActivityPub/Processor.php @@ -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']]); } /** diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index 49a3f9329..33cf1d86a 100644 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -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" => [ diff --git a/tests/src/Moderation/Factory/ReportTest.php b/tests/src/Moderation/Factory/ReportTest.php index ab94643f4..35e3a5050 100644 --- a/tests/src/Moderation/Factory/ReportTest.php +++ b/tests/src/Moderation/Factory/ReportTest.php @@ -21,144 +21,242 @@ 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')), - 'Report', - 'violation', - 'Rules', - true, - [89, 90], + 14, + new \DateTimeImmutable('2021-10-12 12:23:00', new \DateTimeZone('UTC')), + Entity\Report::CATEGORY_VIOLATION, 12, + 'Report', + true, + 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, - 13, - new \DateTime('now', new \DateTimeZone('UTC')), - 'Report', - 'violation', - 'Rules', - true, - [89, 90], + '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, - null + 13, + 14, + $clock->now(), + Entity\Report::CATEGORY_VIOLATION, + 42, + 'Report', + true, + 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, + 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); } } From c312d11dab8364cb67e3c00df8688d0d1156d7cf Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Wed, 1 Feb 2023 08:34:00 -0500 Subject: [PATCH 3/4] Add Report Create module WIP --- src/Moderation/Factory/Report.php | 30 ++ src/Moderation/Repository/Report.php | 4 +- src/Module/Moderation/Report/Create.php | 344 ++++++++++++++++++ src/Object/Post.php | 6 + static/routes.config.php | 2 + .../moderation/report/create/aside.tpl | 25 ++ .../report/create/pick_category.tpl | 16 + .../moderation/report/create/pick_contact.tpl | 9 + .../moderation/report/create/pick_posts.tpl | 24 ++ .../moderation/report/create/pick_rules.tpl | 16 + .../moderation/report/create/summary.tpl | 28 ++ view/templates/wall_thread.tpl | 4 +- view/theme/frio/templates/wall_thread.tpl | 5 + 13 files changed, 510 insertions(+), 3 deletions(-) create mode 100644 src/Module/Moderation/Report/Create.php create mode 100644 view/templates/moderation/report/create/aside.tpl create mode 100644 view/templates/moderation/report/create/pick_category.tpl create mode 100644 view/templates/moderation/report/create/pick_contact.tpl create mode 100644 view/templates/moderation/report/create/pick_posts.tpl create mode 100644 view/templates/moderation/report/create/pick_rules.tpl create mode 100644 view/templates/moderation/report/create/summary.tpl diff --git a/src/Moderation/Factory/Report.php b/src/Moderation/Factory/Report.php index 1c1e9cfb7..4d2e0bb75 100644 --- a/src/Moderation/Factory/Report.php +++ b/src/Moderation/Factory/Report.php @@ -23,6 +23,7 @@ namespace Friendica\Moderation\Factory; use Friendica\Capabilities\ICanCreateFromTableRow; use Friendica\Core\System; +use Friendica\Model\Contact; use Friendica\Moderation\Collection; use Friendica\Moderation\Entity; use Psr\Clock\ClockInterface; @@ -114,4 +115,33 @@ class Report extends \Friendica\BaseFactory implements ICanCreateFromTableRow }, $ruleIds)), ); } + + public function createFromForm(array $rules, int $cid, int $reporterId, int $categoryId, array $ruleIds, string $comment, array $uriIds, bool $forward): Entity\Report + { + $contact = Contact::getById($cid, ['gsid']); + if (!$contact) { + throw new \InvalidArgumentException('Contact with id: ' . $cid . ' not found'); + } + + if (!in_array($categoryId, Entity\Report::CATEGORIES)) { + throw new \OutOfBoundsException('Category with id: ' . $categoryId . ' not found in set: [' . implode(', ', Entity\Report::CATEGORIES) . ']'); + } + + return new Entity\Report( + Contact::getPublicIdByUserId($reporterId), + $cid, + $contact['gsid'], + $this->clock->now(), + $categoryId, + $reporterId, + $comment, + $forward, + new Collection\Report\Posts(array_map(function ($uriId) { + return new Entity\Report\Post($uriId); + }, $uriIds)), + new Collection\Report\Rules(array_map(function ($lineId) use ($rules) { + return new Entity\Report\Rule($lineId, $rules[$lineId] ?? ''); + }, $ruleIds)), + ); + } } diff --git a/src/Moderation/Repository/Report.php b/src/Moderation/Repository/Report.php index b4f2bf2de..4681ea3b9 100644 --- a/src/Moderation/Repository/Report.php +++ b/src/Moderation/Repository/Report.php @@ -56,7 +56,7 @@ final class Report extends \Friendica\BaseRepository return $this->_selectOne(['id' => $lastInsertId]); } - public function save(\Friendica\Moderation\Entity\Report $Report) + public function save(\Friendica\Moderation\Entity\Report $Report): \Friendica\Moderation\Entity\Report { $fields = [ 'reporter-id' => $Report->reporterCid, @@ -95,7 +95,7 @@ final class Report extends \Friendica\BaseRepository $this->db->insert('report-rule', ['rid' => $newReportId, 'line-id' => $rule->lineId, 'text' => $rule->text]); } - $Report = $this->selectOneById($this->db->lastInsertId()); + $Report = $this->selectOneById($newReportId); } return $Report; diff --git a/src/Module/Moderation/Report/Create.php b/src/Module/Moderation/Report/Create.php new file mode 100644 index 000000000..0e0e4c925 --- /dev/null +++ b/src/Module/Moderation/Report/Create.php @@ -0,0 +1,344 @@ +. + * + */ + +namespace Friendica\Module\Moderation\Report; + +use Friendica\App; +use Friendica\BaseModule; +use Friendica\Content\Conversation as ConversationContent; +use Friendica\Content\Pager; +use Friendica\Content\Text\BBCode; +use Friendica\Core\L10n; +use Friendica\Core\Protocol; +use Friendica\Core\Renderer; +use Friendica\Core\Session\Model\UserSession; +use Friendica\Core\System; +use Friendica\Database\DBA; +use Friendica\DI; +use Friendica\Model\Contact; +use Friendica\Model\Item; +use Friendica\Model\Post; +use Friendica\Moderation\Entity\Report; +use Friendica\Module\Response; +use Friendica\Navigation\SystemMessages; +use Friendica\Network\HTTPException\ForbiddenException; +use Friendica\Util\Network; +use Friendica\Util\Profiler; +use Psr\Log\LoggerInterface; + +class Create extends BaseModule +{ + const CONTACT_ACTION_NONE = 0; + const CONTACT_ACTION_COLLAPSE = 1; + const CONTACT_ACTION_IGNORE = 2; + const CONTACT_ACTION_BLOCK = 3; + + /** @var SystemMessages */ + private $systemMessages; + /** @var App\Page */ + private $page; + /** @var UserSession */ + private $session; + /** @var \Friendica\Moderation\Factory\Report */ + private $factory; + /** @var \Friendica\Moderation\Repository\Report */ + private $repository; + + public function __construct(\Friendica\Moderation\Repository\Report $repository, \Friendica\Moderation\Factory\Report $factory, UserSession $session, App\Page $page, SystemMessages $systemMessages, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = []) + { + parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); + + $this->systemMessages = $systemMessages; + $this->page = $page; + $this->session = $session; + $this->factory = $factory; + $this->repository = $repository; + } + + protected function post(array $request = []) + { + if (!$this->session->getLocalUserId()) { + throw new ForbiddenException(); + } + + $report = []; + foreach (['cid', 'category', 'rule-ids', 'uri-ids'] as $key) { + if (isset($request[$key])) { + $report[$key] = $request[$key]; + } + } + + if (isset($request['url'])) { + $cid = Contact::getIdForURL($request['url']); + if ($cid) { + $report['cid'] = $cid; + } else { + $report['url'] = $request['url']; + $this->systemMessages->addNotice($this->t('Contact not found or their server is already blocked on this node.')); + } + } + + if (isset($request['comment'])) { + $this->session->set('report_comment', $request['comment']); + unset($request['comment']); + } + + if (isset($request['report_create'])) { + $report = $this->factory->createFromForm( + System::getRules(true), + $request['cid'], + $this->session->getLocalUserId(), + $request['category'], + !empty($request['rule-ids']) ? explode(',', $request['rule-ids']) : [], + $this->session->get('report_comment') ?? '', + !empty($request['uri-ids']) ? explode(',', $request['uri-ids']) : [], + (bool)($request['forward'] ?? false), + ); + $this->repository->save($report); + + switch ($request['contact_action'] ?? 0) { + case self::CONTACT_ACTION_COLLAPSE: + Contact\User::setCollapsed($request['cid'], $this->session->getLocalUserId(), true); + break; + case self::CONTACT_ACTION_IGNORE: + Contact\User::setIgnored($request['cid'], $this->session->getLocalUserId(), true); + break; + case self::CONTACT_ACTION_BLOCK: + Contact\User::setBlocked($request['cid'], $this->session->getLocalUserId(), true); + break; + } + } + + $this->baseUrl->redirect($this->args->getCommand() . '?' . http_build_query($report)); + } + + protected function content(array $request = []): string + { + if (!$this->session->getLocalUserId()) { + throw new ForbiddenException($this->t('Please login to access this page.')); + } + + $this->page['aside'] = $this->getAside($request); + + if (empty($request['cid'])) { + return $this->pickContact($request); + } + + if (empty($request['category'])) { + return $this->pickCategory($request); + } + + if ($request['category'] == Report::CATEGORY_VIOLATION && !isset($request['rule-ids'])) { + return $this->pickRules($request); + } + + if (!isset($request['uri-ids'])) { + return $this->pickPosts($request); + } + + return $this->summary($request); + } + + private function pickContact(array $request): string + { + $tpl = Renderer::getMarkupTemplate('moderation/report/create/pick_contact.tpl'); + return Renderer::replaceMacros($tpl, [ + '$l10n' => [ + 'title' => $this->t('Create Moderation Report'), + 'page' => $this->t('Pick Contact'), + 'description' => $this->t('Please enter below the contact address or profile URL you would like to create a moderation report about.'), + 'submit' => $this->t('Submit'), + ], + + '$url' => ['url', $this->t('Contact address/URL'), $request['url'] ?? ''], + ]); + } + + private function pickCategory(array $request): string + { + $tpl = Renderer::getMarkupTemplate('moderation/report/create/pick_category.tpl'); + return Renderer::replaceMacros($tpl, [ + '$l10n' => [ + 'title' => $this->t('Create Moderation Report'), + 'page' => $this->t('Pick Category'), + 'description' => $this->t('Please pick below the category of your report.'), + 'submit' => $this->t('Submit'), + ], + + '$category_spam' => ['category', $this->t('Spam') , Report::CATEGORY_SPAM , $this->t('This contact is publishing many repeated/overly long posts/replies or advertising their product/websites in otherwise irrelevant conversations.'), $request['category'] == Report::CATEGORY_SPAM], + '$category_illegal' => ['category', $this->t('Illegal Content') , Report::CATEGORY_ILLEGAL , $this->t("This contact is publishing content that is considered illegal in this node's hosting juridiction."), $request['category'] == Report::CATEGORY_ILLEGAL], + '$category_safety' => ['category', $this->t('Community Safety') , Report::CATEGORY_SAFETY , $this->t("This contact aggravated you or other people, by being provocative or insensitive, intentionally or not. This includes disclosing people's private information (doxxing), posting threats or offensive pictures in posts or replies."), $request['category'] == Report::CATEGORY_SAFETY], + '$category_unwanted' => ['category', $this->t('Unwanted Content/Behavior'), Report::CATEGORY_UNWANTED , $this->t("This contact has repeatedly published content irrelevant to the node's theme or is openly criticizing the node's administration/moderation without directly engaging with the relevant people for example or repeatedly nitpicking on a sensitive topic."), $request['category'] == Report::CATEGORY_UNWANTED], + '$category_violation' => ['category', $this->t('Rules Violation') , Report::CATEGORY_VIOLATION, $this->t('This contact violated one or more rules of this node. You will be able to pick which one(s) in the next step.'), $request['category'] == Report::CATEGORY_VIOLATION], + '$category_other' => ['category', $this->t('Other') , Report::CATEGORY_OTHER , $this->t('Please elaborate below why you submitted this report. The more details you provide, the better your report can be handled.'), $request['category'] == Report::CATEGORY_OTHER], + + '$comment' => ['comment', $this->t('Additional Information'), $this->session->get('report_comment') ?? '', $this->t('Please provide any additional information relevant to this particular report. You will be able to attach posts by this contact in the next step, but any context is welcome.')], + ]); + } + + private function pickRules(array $request): string + { + $rules = []; + + foreach (System::getRules(true) as $rule_line => $rule_text) { + $rules[] = ['rule-ids[]', $rule_line, $rule_text, in_array($rule_line, $request['rule_ids'] ?? [])]; + } + + $tpl = Renderer::getMarkupTemplate('moderation/report/create/pick_rules.tpl'); + return Renderer::replaceMacros($tpl, [ + '$l10n' => [ + 'title' => $this->t('Create Moderation Report'), + 'page' => $this->t('Pick Rules'), + 'description' => $this->t('Please pick below the node rules you believe this contact violated.'), + 'submit' => $this->t('Submit'), + ], + + '$rules' => $rules, + ]); + } + + private function pickPosts(array $request): string + { + $threads = []; + + $contact = DBA::selectFirst('contact', ['contact-type', 'network'], ['id' => $request['cid']]); + if (DBA::isResult($contact)) { + $contact_field = $contact['contact-type'] == Contact::TYPE_COMMUNITY || $contact['network'] == Protocol::MAIL ? 'owner-id' : 'author-id'; + + $condition = [ + $contact_field => $request['cid'], + 'gravity' => [Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT], + ]; + + if (empty($contact['network']) || in_array($contact['network'], Protocol::FEDERATED)) { + $condition = DBA::mergeConditions($condition, ['(`uid` = 0 OR (`uid` = ? AND NOT `global`))', DI::userSession()->getLocalUserId()]); + } else { + $condition['uid'] = DI::userSession()->getLocalUserId(); + } + + if (DI::mode()->isMobile()) { + $itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_mobile_network', + DI::config()->get('system', 'itemspage_network_mobile')); + } else { + $itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_network', + DI::config()->get('system', 'itemspage_network')); + } + + $pager = new Pager(DI::l10n(), DI::args()->getQueryString(), $itemsPerPage); + + $params = ['order' => ['received' => true], 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]]; + + $fields = array_merge(Item::DISPLAY_FIELDLIST, ['featured']); + $items = Post::toArray(Post::selectForUser(DI::userSession()->getLocalUserId(), $fields, $condition, $params)); + + $formSecurityToken = BaseModule::getFormSecurityToken('contact_action'); + + $threads = DI::conversation()->getContextLessThreadList($items, ConversationContent::MODE_CONTACT_POSTS, false, false, $formSecurityToken); + } + + $tpl = Renderer::getMarkupTemplate('moderation/report/create/pick_posts.tpl'); + return Renderer::replaceMacros($tpl, [ + '$l10n' => [ + 'title' => $this->t('Create Moderation Report'), + 'page' => $this->t('Pick Posts'), + 'description' => $this->t('Please optionally pick posts to attach to your report.'), + 'submit' => $this->t('Submit'), + ], + + '$threads' => $threads, + ]); + } + + private function summary(array $request): string + { + $this->page['aside'] = ''; + + $contact = Contact::getById($request['cid'], ['url']); + + $tpl = Renderer::getMarkupTemplate('moderation/report/create/summary.tpl'); + return Renderer::replaceMacros($tpl, [ + '$l10n' => [ + 'title' => $this->t('Create Moderation Report'), + 'page' => $this->t('Summary'), + 'submit' => $this->t('Submit Report'), + 'contact_action_title' => $this->t('Further Action'), + 'contact_action_desc' => $this->t('You can also perform one of the following action on the contact you reported:'), + ], + + '$cid' => $request['cid'], + '$category' => $request['category'], + '$ruleIds' => implode(',', $request['rule-ids'] ?? []), + '$uriIds' => implode(',', $request['uri-ids'] ?? []), + + '$nothing' => ['contact_action', $this->t('Nothing'), self::CONTACT_ACTION_NONE, '', true], + '$collapse' => ['contact_action', $this->t('Collapse contact'), self::CONTACT_ACTION_COLLAPSE, $this->t('Their posts and replies will keep appearing in your Network page but their content will be collapsed by default.')], + '$ignore' => ['contact_action', $this->t('Ignore contact'), self::CONTACT_ACTION_IGNORE, $this->t("Their posts won't appear in your Network page anymore, but their replies can appear in forum threads. They still can follow you.")], + '$block' => ['contact_action', $this->t('Block contact'), self::CONTACT_ACTION_BLOCK, $this->t("Their posts won't appear in your Network page anymore, but their replies can appear in forum threads, with their content collapsed by default. They cannot follow you but still can have access to your public posts by other means.")], + + '$display_forward' => !Network::isLocalLink($contact['url']), + '$forward' => ['report_forward', $this->t('Forward report'), self::CONTACT_ACTION_BLOCK, $this->t('Would you ike to forward this report to the remote server?')], + + '$summary' => $this->getAside($request), + ]); + } + + private function getAside(array $request): string + { + $contact = null; + if (!empty($request['cid'])) { + $contact = Contact::getById($request['cid']); + } + + switch ($request['category'] ?? 0) { + case Report::CATEGORY_SPAM: $category = $this->t('Spam'); break; + case Report::CATEGORY_ILLEGAL: $category = $this->t('Illegal Content'); break; + case Report::CATEGORY_SAFETY: $category = $this->t('Community Safety'); break; + case Report::CATEGORY_UNWANTED: $category = $this->t('Unwanted Content/Behavior'); break; + case Report::CATEGORY_VIOLATION: $category = $this->t('Rules Violation'); break; + case Report::CATEGORY_OTHER: $category = $this->t('Other'); break; + + default: $category = ''; + } + + if (!empty($request['rule-ids'])) { + $rules = array_filter(System::getRules(true), function ($rule_id) use ($request) { + return in_array($rule_id, $request['rule-ids']); + }, ARRAY_FILTER_USE_KEY); + } + + $tpl = Renderer::getMarkupTemplate('moderation/report/create/aside.tpl'); + return Renderer::replaceMacros($tpl, [ + '$l10n' => [ + 'contact_title' => $this->t('1. Pick a contact'), + 'category_title' => $this->t('2. Pick a category'), + 'rules_title' => $this->t('2a. Pick rules'), + 'comment_title' => $this->t('2b. Add comment'), + 'posts_title' => $this->t('3. Pick posts'), + ], + + '$contact' => $contact, + '$category' => $category, + '$rules' => $rules ?? [], + '$comment' => BBCode::convert($this->session->get('report_comment') ?? '', false, ), + '$posts' => count($request['uri-ids']), + ]); + } +} diff --git a/src/Object/Post.php b/src/Object/Post.php index 28db23894..4c377e946 100644 --- a/src/Object/Post.php +++ b/src/Object/Post.php @@ -255,6 +255,7 @@ class Post $block = false; $ignore = false; $collapse = false; + $report = false; if (DI::userSession()->getLocalUserId()) { $drop = [ 'dropping' => $dropping, @@ -280,6 +281,10 @@ class Post 'collapse' => DI::l10n()->t('Collapse %s', $item['author-name']), 'author_id' => $item['author-id'], ]; + $report = [ + 'label' => DI::l10n()->t('Report post'), + 'href' => 'moderation/report/create?' . http_build_query(['cid' => $item['author-id'], 'uri-ids' => [$item['uri-id']]]), + ]; } $filer = DI::userSession()->getLocalUserId() ? DI::l10n()->t('Save to folder') : false; @@ -554,6 +559,7 @@ class Post 'block' => $block, 'ignore_author' => $ignore, 'collapse' => $collapse, + 'report' => $report, 'vote' => $buttons, 'like_html' => $responses['like']['output'], 'dislike_html' => $responses['dislike']['output'], diff --git a/static/routes.config.php b/static/routes.config.php index 157f141b1..71595e5a8 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -508,6 +508,8 @@ return [ '/item/delete' => [Module\Moderation\Item\Delete::class, [R::GET, R::POST]], '/item/source[/{guid}]' => [Module\Moderation\Item\Source::class, [R::GET, R::POST]], + '/report/create' => [Module\Moderation\Report\Create::class, [R::GET, R::POST]], + '/users[/{action}/{uid}]' => [Module\Moderation\Users\Index::class, [R::GET, R::POST]], '/users/active[/{action}/{uid}]' => [Module\Moderation\Users\Active::class, [R::GET, R::POST]], '/users/pending[/{action}/{uid}]' => [Module\Moderation\Users\Pending::class, [R::GET, R::POST]], diff --git a/view/templates/moderation/report/create/aside.tpl b/view/templates/moderation/report/create/aside.tpl new file mode 100644 index 000000000..f590433f8 --- /dev/null +++ b/view/templates/moderation/report/create/aside.tpl @@ -0,0 +1,25 @@ +
+

{{$l10n.contact_title}}

+{{if $contact}} + {{include file="contact/entry.tpl"}} +{{/if}} +

{{$l10n.category_title}}

+{{if $category}} +

{{$category}}

+{{/if}} +{{if $rules}} +

{{$l10n.rules_title}}

+
    +{{foreach $rules as $rule_id => $rule_text}} +
  1. {{$rule_text}}
  2. +{{/foreach}} +
+{{/if}} +{{if $comment}} +

{{$l10n.comment_title}}

+

{{$comment nofilter}}

+{{/if}} +{{if $posts}} +

{{$l10n.posts_title}} ({{$posts}})

+{{/if}} +
\ No newline at end of file diff --git a/view/templates/moderation/report/create/pick_category.tpl b/view/templates/moderation/report/create/pick_category.tpl new file mode 100644 index 000000000..66d4a275b --- /dev/null +++ b/view/templates/moderation/report/create/pick_category.tpl @@ -0,0 +1,16 @@ +
+

{{$l10n.title}} - {{$l10n.page}}

+

{{$l10n.description}}

+ +
+ {{include file="field_radio.tpl" field=$category_spam}} + {{include file="field_radio.tpl" field=$category_illegal}} + {{include file="field_radio.tpl" field=$category_safety}} + {{include file="field_radio.tpl" field=$category_unwanted}} + {{include file="field_radio.tpl" field=$category_violation}} + {{include file="field_radio.tpl" field=$category_other}} + + {{include file="field_textarea.tpl" field=$comment}} +

+
+
diff --git a/view/templates/moderation/report/create/pick_contact.tpl b/view/templates/moderation/report/create/pick_contact.tpl new file mode 100644 index 000000000..38c41e7f1 --- /dev/null +++ b/view/templates/moderation/report/create/pick_contact.tpl @@ -0,0 +1,9 @@ +
+

{{$l10n.title}} - {{$l10n.page}}

+

{{$l10n.description}}

+ +
+ {{include file="field_input.tpl" field=$url}} +

+
+
diff --git a/view/templates/moderation/report/create/pick_posts.tpl b/view/templates/moderation/report/create/pick_posts.tpl new file mode 100644 index 000000000..4dca61a8d --- /dev/null +++ b/view/templates/moderation/report/create/pick_posts.tpl @@ -0,0 +1,24 @@ +
+

{{$l10n.title}} - {{$l10n.page}}

+

{{$l10n.description}}

+ +
+ +{{foreach $threads as $thread}} + + + + +{{/foreach}} +
+
+ {{foreach $thread.items as $item}} + {{include file="{{$item.template}}"}} + {{/foreach}} +
+
+ +
+

+
+
diff --git a/view/templates/moderation/report/create/pick_rules.tpl b/view/templates/moderation/report/create/pick_rules.tpl new file mode 100644 index 000000000..e9996c386 --- /dev/null +++ b/view/templates/moderation/report/create/pick_rules.tpl @@ -0,0 +1,16 @@ +
+

{{$l10n.title}} - {{$l10n.page}}

+

{{$l10n.description}}

+ +
+ {{foreach $rules as $rule}} +
+ + +
+ {{/foreach}} +

+
+
diff --git a/view/templates/moderation/report/create/summary.tpl b/view/templates/moderation/report/create/summary.tpl new file mode 100644 index 000000000..3de520681 --- /dev/null +++ b/view/templates/moderation/report/create/summary.tpl @@ -0,0 +1,28 @@ +
+

{{$l10n.title}} - {{$l10n.page}}

+

{{$l10n.description}}

+ +
+ {{$summary nofilter}} +
+ +

{{$l10n.contact_action_title}}

+

{{$l10n.contact_action_desc}}

+
+ + + + + + {{include file="field_radio.tpl" field=$nothing}} + {{include file="field_radio.tpl" field=$collapse}} + {{include file="field_radio.tpl" field=$ignore}} + {{include file="field_radio.tpl" field=$block}} + +{{if $display_forward}} + {{include file="field_checkbox.tpl" field=$forward}} +{{/if}} + +

+
+
diff --git a/view/templates/wall_thread.tpl b/view/templates/wall_thread.tpl index 5e9dd96d6..74e6097b3 100644 --- a/view/templates/wall_thread.tpl +++ b/view/templates/wall_thread.tpl @@ -161,13 +161,15 @@
- {{if $item.drop && $item.drop.pagedrop}} {{/if}} {{if $item.drop && $item.drop.dropping}} {{$item.drop.delete}} {{/if}} + {{if $item.report}} + {{$item.report.label}} + {{/if}} {{if $item.edpost}} {{$item.edpost.1}} {{/if}} diff --git a/view/theme/frio/templates/wall_thread.tpl b/view/theme/frio/templates/wall_thread.tpl index 3e9d19e02..21aac60e8 100644 --- a/view/theme/frio/templates/wall_thread.tpl +++ b/view/theme/frio/templates/wall_thread.tpl @@ -409,6 +409,11 @@ as the value of $top_child_total (this is done at the end of this file) {{$item.collapse.collapse}} {{/if}} + {{if $item.report}} +
  • + {{$item.report.label}} +
  • + {{/if}} From 969becfc157cded332756f78018a8d9935c9c88a Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Sun, 2 Jul 2023 22:57:39 -0400 Subject: [PATCH 4/4] Update main transation file after updating strings --- view/lang/C/messages.po | 463 +++++++++++++++++++++++++++++----------- 1 file changed, 339 insertions(+), 124 deletions(-) diff --git a/view/lang/C/messages.po b/view/lang/C/messages.po index bb912a9ee..445ffcbd3 100644 --- a/view/lang/C/messages.po +++ b/view/lang/C/messages.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 2023.09-dev\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-09 17:33+0000\n" +"POT-Creation-Date: 2023-07-09 18:36-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -294,7 +294,7 @@ msgstr "" #: mod/message.php:201 mod/message.php:357 mod/photos.php:1301 #: src/Content/Conversation.php:392 src/Content/Conversation.php:1506 #: src/Module/Item/Compose.php:206 src/Module/Post/Edit.php:145 -#: src/Module/Profile/UnkMail.php:154 src/Object/Post.php:568 +#: src/Module/Profile/UnkMail.php:154 src/Object/Post.php:574 msgid "Please wait" msgstr "" @@ -310,8 +310,12 @@ msgstr "" #: src/Module/Install.php:234 src/Module/Install.php:274 #: src/Module/Install.php:309 src/Module/Invite.php:178 #: src/Module/Item/Compose.php:189 src/Module/Moderation/Item/Source.php:79 -#: src/Module/Profile/Profile.php:276 src/Module/Profile/UnkMail.php:155 -#: src/Module/Settings/Profile/Index.php:230 src/Object/Post.php:1084 +#: src/Module/Moderation/Report/Create.php:168 +#: src/Module/Moderation/Report/Create.php:183 +#: src/Module/Moderation/Report/Create.php:211 +#: src/Module/Moderation/Report/Create.php:263 +#: src/Module/Profile/Profile.php:274 src/Module/Profile/UnkMail.php:155 +#: src/Module/Settings/Profile/Index.php:230 src/Object/Post.php:1090 #: view/theme/duepuntozero/config.php:85 view/theme/frio/config.php:171 #: view/theme/quattro/config.php:87 view/theme/vier/config.php:135 msgid "Submit" @@ -596,29 +600,29 @@ msgstr "" #: mod/photos.php:1139 mod/photos.php:1195 mod/photos.php:1275 #: src/Module/Contact.php:619 src/Module/Item/Compose.php:188 -#: src/Object/Post.php:1081 +#: src/Object/Post.php:1087 msgid "This is you" msgstr "" #: mod/photos.php:1141 mod/photos.php:1197 mod/photos.php:1277 -#: src/Object/Post.php:562 src/Object/Post.php:1083 +#: src/Object/Post.php:568 src/Object/Post.php:1089 msgid "Comment" msgstr "" #: mod/photos.php:1143 mod/photos.php:1199 mod/photos.php:1279 #: src/Content/Conversation.php:407 src/Module/Calendar/Event/Form.php:248 #: src/Module/Item/Compose.php:201 src/Module/Post/Edit.php:165 -#: src/Object/Post.php:1097 +#: src/Object/Post.php:1103 msgid "Preview" msgstr "" #: mod/photos.php:1144 src/Content/Conversation.php:360 -#: src/Module/Post/Edit.php:130 src/Object/Post.php:1085 +#: src/Module/Post/Edit.php:130 src/Object/Post.php:1091 msgid "Loading..." msgstr "" #: mod/photos.php:1236 src/Content/Conversation.php:1422 -#: src/Object/Post.php:262 +#: src/Object/Post.php:263 msgid "Select" msgstr "" @@ -630,19 +634,19 @@ msgstr "" msgid "Delete" msgstr "" -#: mod/photos.php:1298 src/Object/Post.php:400 +#: mod/photos.php:1298 src/Object/Post.php:405 msgid "Like" msgstr "" -#: mod/photos.php:1299 src/Object/Post.php:400 +#: mod/photos.php:1299 src/Object/Post.php:405 msgid "I like this (toggle)" msgstr "" -#: mod/photos.php:1300 src/Object/Post.php:401 +#: mod/photos.php:1300 src/Object/Post.php:406 msgid "Dislike" msgstr "" -#: mod/photos.php:1302 src/Object/Post.php:401 +#: mod/photos.php:1302 src/Object/Post.php:406 msgid "I don't like this (toggle)" msgstr "" @@ -1222,7 +1226,7 @@ msgid "Visible to everybody" msgstr "" #: src/Content/Conversation.php:330 src/Module/Item/Compose.php:200 -#: src/Object/Post.php:1096 +#: src/Object/Post.php:1102 msgid "Please enter a image/video/audio/webpage URL:" msgstr "" @@ -1267,52 +1271,52 @@ msgid "attach file" msgstr "" #: src/Content/Conversation.php:365 src/Module/Item/Compose.php:190 -#: src/Module/Post/Edit.php:171 src/Object/Post.php:1086 +#: src/Module/Post/Edit.php:171 src/Object/Post.php:1092 msgid "Bold" msgstr "" #: src/Content/Conversation.php:366 src/Module/Item/Compose.php:191 -#: src/Module/Post/Edit.php:172 src/Object/Post.php:1087 +#: src/Module/Post/Edit.php:172 src/Object/Post.php:1093 msgid "Italic" msgstr "" #: src/Content/Conversation.php:367 src/Module/Item/Compose.php:192 -#: src/Module/Post/Edit.php:173 src/Object/Post.php:1088 +#: src/Module/Post/Edit.php:173 src/Object/Post.php:1094 msgid "Underline" msgstr "" #: src/Content/Conversation.php:368 src/Module/Item/Compose.php:193 -#: src/Module/Post/Edit.php:174 src/Object/Post.php:1090 +#: src/Module/Post/Edit.php:174 src/Object/Post.php:1096 msgid "Quote" msgstr "" #: src/Content/Conversation.php:369 src/Module/Item/Compose.php:194 -#: src/Module/Post/Edit.php:175 src/Object/Post.php:1091 +#: src/Module/Post/Edit.php:175 src/Object/Post.php:1097 msgid "Add emojis" msgstr "" #: src/Content/Conversation.php:370 src/Module/Item/Compose.php:195 -#: src/Object/Post.php:1089 +#: src/Object/Post.php:1095 msgid "Content Warning" msgstr "" #: src/Content/Conversation.php:371 src/Module/Item/Compose.php:196 -#: src/Module/Post/Edit.php:176 src/Object/Post.php:1092 +#: src/Module/Post/Edit.php:176 src/Object/Post.php:1098 msgid "Code" msgstr "" #: src/Content/Conversation.php:372 src/Module/Item/Compose.php:197 -#: src/Object/Post.php:1093 +#: src/Object/Post.php:1099 msgid "Image" msgstr "" #: src/Content/Conversation.php:373 src/Module/Item/Compose.php:198 -#: src/Module/Post/Edit.php:177 src/Object/Post.php:1094 +#: src/Module/Post/Edit.php:177 src/Object/Post.php:1100 msgid "Link" msgstr "" #: src/Content/Conversation.php:374 src/Module/Item/Compose.php:199 -#: src/Module/Post/Edit.php:178 src/Object/Post.php:1095 +#: src/Module/Post/Edit.php:178 src/Object/Post.php:1101 msgid "Link or Media" msgstr "" @@ -1467,21 +1471,21 @@ msgstr "" msgid "Pinned item" msgstr "" -#: src/Content/Conversation.php:1466 src/Object/Post.php:513 -#: src/Object/Post.php:514 +#: src/Content/Conversation.php:1466 src/Object/Post.php:518 +#: src/Object/Post.php:519 #, php-format msgid "View %s's profile @ %s" msgstr "" -#: src/Content/Conversation.php:1479 src/Object/Post.php:501 +#: src/Content/Conversation.php:1479 src/Object/Post.php:506 msgid "Categories:" msgstr "" -#: src/Content/Conversation.php:1480 src/Object/Post.php:502 +#: src/Content/Conversation.php:1480 src/Object/Post.php:507 msgid "Filed under:" msgstr "" -#: src/Content/Conversation.php:1488 src/Object/Post.php:527 +#: src/Content/Conversation.php:1488 src/Object/Post.php:532 #, php-format msgid "%s from %s" msgstr "" @@ -1696,7 +1700,7 @@ msgstr "" msgid "Collapse" msgstr "" -#: src/Content/Item.php:434 src/Object/Post.php:482 +#: src/Content/Item.php:434 src/Object/Post.php:487 msgid "Languages" msgstr "" @@ -1754,7 +1758,7 @@ msgstr "" #: src/Content/Nav.php:228 src/Module/BaseProfile.php:49 #: src/Module/BaseSettings.php:100 src/Module/Contact.php:504 -#: src/Module/Contact/Profile.php:392 src/Module/Profile/Profile.php:270 +#: src/Module/Contact/Profile.php:392 src/Module/Profile/Profile.php:268 #: src/Module/Welcome.php:57 view/theme/frio/theme.php:230 msgid "Profile" msgstr "" @@ -2267,12 +2271,12 @@ msgid "More Trending Tags" msgstr "" #: src/Content/Widget/VCard.php:109 src/Model/Profile.php:378 -#: src/Module/Contact/Profile.php:381 src/Module/Profile/Profile.php:201 +#: src/Module/Contact/Profile.php:381 src/Module/Profile/Profile.php:199 msgid "XMPP:" msgstr "" #: src/Content/Widget/VCard.php:110 src/Model/Profile.php:379 -#: src/Module/Contact/Profile.php:383 src/Module/Profile/Profile.php:205 +#: src/Module/Contact/Profile.php:383 src/Module/Profile/Profile.php:203 msgid "Matrix:" msgstr "" @@ -2280,7 +2284,7 @@ msgstr "" #: src/Model/Event.php:109 src/Model/Event.php:473 src/Model/Event.php:965 #: src/Model/Profile.php:373 src/Module/Contact/Profile.php:379 #: src/Module/Directory.php:147 src/Module/Notifications/Introductions.php:187 -#: src/Module/Profile/Profile.php:223 +#: src/Module/Profile/Profile.php:221 msgid "Location:" msgstr "" @@ -2295,7 +2299,7 @@ msgstr "" msgid "Unfollow" msgstr "" -#: src/Core/ACL.php:166 src/Module/Profile/Profile.php:271 +#: src/Core/ACL.php:166 src/Module/Profile/Profile.php:269 msgid "Yourself" msgstr "" @@ -3051,7 +3055,7 @@ msgstr "" msgid "Disallowed profile URL." msgstr "" -#: src/Model/Contact.php:2994 src/Module/Friendica.php:83 +#: src/Model/Contact.php:2994 src/Module/Friendica.php:102 msgid "Blocked domain" msgstr "" @@ -3299,8 +3303,8 @@ msgstr "" msgid "Wall Photos" msgstr "" -#: src/Model/Profile.php:361 src/Module/Profile/Profile.php:285 -#: src/Module/Profile/Profile.php:287 +#: src/Model/Profile.php:361 src/Module/Profile/Profile.php:283 +#: src/Module/Profile/Profile.php:285 msgid "Edit profile" msgstr "" @@ -3309,7 +3313,7 @@ msgid "Change profile photo" msgstr "" #: src/Model/Profile.php:376 src/Module/Directory.php:152 -#: src/Module/Profile/Profile.php:211 +#: src/Module/Profile/Profile.php:209 msgid "Homepage:" msgstr "" @@ -3404,6 +3408,7 @@ msgid "Title/Description:" msgstr "" #: src/Model/Profile.php:1025 src/Module/Admin/Summary.php:221 +#: src/Module/Moderation/Report/Create.php:280 #: src/Module/Moderation/Summary.php:77 msgid "Summary" msgstr "" @@ -3858,6 +3863,8 @@ msgid "Manage Additional Features" msgstr "" #: src/Module/Admin/Federation.php:76 +#: src/Module/Moderation/Report/Create.php:191 +#: src/Module/Moderation/Report/Create.php:316 msgid "Other" msgstr "" @@ -4224,7 +4231,7 @@ msgid "Policies" msgstr "" #: src/Module/Admin/Site.php:406 src/Module/Calendar/Event/Form.php:252 -#: src/Module/Contact.php:547 src/Module/Profile/Profile.php:278 +#: src/Module/Contact.php:547 src/Module/Profile/Profile.php:276 msgid "Advanced" msgstr "" @@ -5707,7 +5714,7 @@ msgstr "" msgid "Share this event" msgstr "" -#: src/Module/Calendar/Event/Form.php:251 src/Module/Profile/Profile.php:277 +#: src/Module/Calendar/Event/Form.php:251 src/Module/Profile/Profile.php:275 msgid "Basic" msgstr "" @@ -5871,7 +5878,7 @@ msgid "Only show blocked contacts" msgstr "" #: src/Module/Contact.php:369 src/Module/Contact.php:441 -#: src/Object/Post.php:360 +#: src/Object/Post.php:365 msgid "Ignored" msgstr "" @@ -6082,7 +6089,7 @@ msgstr[1] "" #: src/Module/Contact/Follow.php:70 src/Module/Contact/Redir.php:62 #: src/Module/Contact/Redir.php:222 src/Module/Conversation/Community.php:194 #: src/Module/Debug/ItemBody.php:38 src/Module/Diaspora/Receive.php:57 -#: src/Module/Item/Display.php:98 src/Module/Item/Feed.php:59 +#: src/Module/Item/Display.php:96 src/Module/Item/Feed.php:59 #: src/Module/Item/Follow.php:41 src/Module/Item/Ignore.php:41 #: src/Module/Item/Pin.php:41 src/Module/Item/Pin.php:56 #: src/Module/Item/Star.php:42 src/Module/Update/Display.php:37 @@ -6128,7 +6135,7 @@ msgstr "" #: src/Module/Contact/Follow.php:171 src/Module/Contact/Profile.php:387 #: src/Module/Notifications/Introductions.php:191 -#: src/Module/Profile/Profile.php:236 +#: src/Module/Profile/Profile.php:234 msgid "Tags:" msgstr "" @@ -6299,6 +6306,7 @@ msgid "Block/Unblock contact" msgstr "" #: src/Module/Contact/Profile.php:348 +#: src/Module/Moderation/Report/Create.php:293 msgid "Ignore contact" msgstr "" @@ -6554,7 +6562,7 @@ msgstr "" msgid "Posts that mention or involve you" msgstr "" -#: src/Module/Conversation/Network.php:289 src/Object/Post.php:372 +#: src/Module/Conversation/Network.php:289 src/Object/Post.php:377 msgid "Starred" msgstr "" @@ -6907,55 +6915,55 @@ msgstr "" msgid "Suggest a friend for %s" msgstr "" -#: src/Module/Friendica.php:64 +#: src/Module/Friendica.php:82 msgid "Installed addons/apps:" msgstr "" -#: src/Module/Friendica.php:69 +#: src/Module/Friendica.php:87 msgid "No installed addons/apps" msgstr "" -#: src/Module/Friendica.php:74 +#: src/Module/Friendica.php:92 #, php-format msgid "Read about the Terms of Service of this node." msgstr "" -#: src/Module/Friendica.php:81 +#: src/Module/Friendica.php:100 msgid "On this server the following remote servers are blocked." msgstr "" -#: src/Module/Friendica.php:84 +#: src/Module/Friendica.php:103 #: src/Module/Moderation/Blocklist/Server/Index.php:87 #: src/Module/Moderation/Blocklist/Server/Index.php:111 msgid "Reason for the block" msgstr "" -#: src/Module/Friendica.php:86 +#: src/Module/Friendica.php:105 msgid "Download this list in CSV format" msgstr "" -#: src/Module/Friendica.php:100 +#: src/Module/Friendica.php:119 #, php-format msgid "" "This is Friendica, version %s that is running at the web location %s. The " "database version is %s, the post update version is %s." msgstr "" -#: src/Module/Friendica.php:105 +#: src/Module/Friendica.php:124 msgid "" "Please visit Friendi.ca to learn more " "about the Friendica project." msgstr "" -#: src/Module/Friendica.php:106 +#: src/Module/Friendica.php:125 msgid "Bug reports and issues: please visit" msgstr "" -#: src/Module/Friendica.php:106 +#: src/Module/Friendica.php:125 msgid "the bugtracker at github" msgstr "" -#: src/Module/Friendica.php:107 +#: src/Module/Friendica.php:126 msgid "" "Suggestions, praise, etc. - please email \"info\" at \"friendi - dot - ca" msgstr "" @@ -7270,7 +7278,7 @@ msgid "" "Theme Customization settings." msgstr "" -#: src/Module/Item/Display.php:138 src/Module/Update/Display.php:55 +#: src/Module/Item/Display.php:136 src/Module/Update/Display.php:55 msgid "The requested item doesn't exist or has been deleted." msgstr "" @@ -7814,6 +7822,209 @@ msgstr "" msgid "Item Guid" msgstr "" +#: src/Module/Moderation/Report/Create.php:95 +msgid "Contact not found or their server is already blocked on this node." +msgstr "" + +#: src/Module/Moderation/Report/Create.php:136 +msgid "Please login to access this page." +msgstr "" + +#: src/Module/Moderation/Report/Create.php:165 +#: src/Module/Moderation/Report/Create.php:180 +#: src/Module/Moderation/Report/Create.php:208 +#: src/Module/Moderation/Report/Create.php:260 +#: src/Module/Moderation/Report/Create.php:279 +msgid "Create Moderation Report" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:166 +msgid "Pick Contact" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:167 +msgid "" +"Please enter below the contact address or profile URL you would like to " +"create a moderation report about." +msgstr "" + +#: src/Module/Moderation/Report/Create.php:171 +msgid "Contact address/URL" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:181 +msgid "Pick Category" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:182 +msgid "Please pick below the category of your report." +msgstr "" + +#: src/Module/Moderation/Report/Create.php:186 +#: src/Module/Moderation/Report/Create.php:311 +msgid "Spam" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:186 +msgid "" +"This contact is publishing many repeated/overly long posts/replies or " +"advertising their product/websites in otherwise irrelevant conversations." +msgstr "" + +#: src/Module/Moderation/Report/Create.php:187 +#: src/Module/Moderation/Report/Create.php:312 +msgid "Illegal Content" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:187 +msgid "" +"This contact is publishing content that is considered illegal in this node's " +"hosting juridiction." +msgstr "" + +#: src/Module/Moderation/Report/Create.php:188 +#: src/Module/Moderation/Report/Create.php:313 +msgid "Community Safety" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:188 +msgid "" +"This contact aggravated you or other people, by being provocative or " +"insensitive, intentionally or not. This includes disclosing people's private " +"information (doxxing), posting threats or offensive pictures in posts or " +"replies." +msgstr "" + +#: src/Module/Moderation/Report/Create.php:189 +#: src/Module/Moderation/Report/Create.php:314 +msgid "Unwanted Content/Behavior" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:189 +msgid "" +"This contact has repeatedly published content irrelevant to the node's theme " +"or is openly criticizing the node's administration/moderation without " +"directly engaging with the relevant people for example or repeatedly " +"nitpicking on a sensitive topic." +msgstr "" + +#: src/Module/Moderation/Report/Create.php:190 +#: src/Module/Moderation/Report/Create.php:315 +msgid "Rules Violation" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:190 +msgid "" +"This contact violated one or more rules of this node. You will be able to " +"pick which one(s) in the next step." +msgstr "" + +#: src/Module/Moderation/Report/Create.php:191 +msgid "" +"Please elaborate below why you submitted this report. The more details you " +"provide, the better your report can be handled." +msgstr "" + +#: src/Module/Moderation/Report/Create.php:193 +msgid "Additional Information" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:193 +msgid "" +"Please provide any additional information relevant to this particular " +"report. You will be able to attach posts by this contact in the next step, " +"but any context is welcome." +msgstr "" + +#: src/Module/Moderation/Report/Create.php:209 +msgid "Pick Rules" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:210 +msgid "Please pick below the node rules you believe this contact violated." +msgstr "" + +#: src/Module/Moderation/Report/Create.php:261 +msgid "Pick Posts" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:262 +msgid "Please optionally pick posts to attach to your report." +msgstr "" + +#: src/Module/Moderation/Report/Create.php:281 +msgid "Submit Report" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:282 +msgid "Further Action" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:283 +msgid "" +"You can also perform one of the following action on the contact you reported:" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:291 +msgid "Nothing" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:292 +msgid "Collapse contact" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:292 +msgid "" +"Their posts and replies will keep appearing in your Network page but their " +"content will be collapsed by default." +msgstr "" + +#: src/Module/Moderation/Report/Create.php:293 +msgid "" +"Their posts won't appear in your Network page anymore, but their replies can " +"appear in forum threads. They still can follow you." +msgstr "" + +#: src/Module/Moderation/Report/Create.php:294 +msgid "Block contact" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:294 +msgid "" +"Their posts won't appear in your Network page anymore, but their replies can " +"appear in forum threads, with their content collapsed by default. They " +"cannot follow you but still can have access to your public posts by other " +"means." +msgstr "" + +#: src/Module/Moderation/Report/Create.php:297 +msgid "Forward report" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:297 +msgid "Would you ike to forward this report to the remote server?" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:330 +msgid "1. Pick a contact" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:331 +msgid "2. Pick a category" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:332 +msgid "2a. Pick rules" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:333 +msgid "2b. Add comment" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:334 +msgid "3. Pick posts" +msgstr "" + #: src/Module/Moderation/Summary.php:53 msgid "Normal Account" msgstr "" @@ -8364,20 +8575,20 @@ msgid "No contacts." msgstr "" #: src/Module/Profile/Conversations.php:106 -#: src/Module/Profile/Conversations.php:109 src/Module/Profile/Profile.php:353 -#: src/Module/Profile/Profile.php:356 src/Protocol/Feed.php:1090 +#: src/Module/Profile/Conversations.php:109 src/Module/Profile/Profile.php:351 +#: src/Module/Profile/Profile.php:354 src/Protocol/Feed.php:1090 #: src/Protocol/OStatus.php:1009 #, php-format msgid "%s's timeline" msgstr "" -#: src/Module/Profile/Conversations.php:107 src/Module/Profile/Profile.php:354 +#: src/Module/Profile/Conversations.php:107 src/Module/Profile/Profile.php:352 #: src/Protocol/Feed.php:1094 src/Protocol/OStatus.php:1014 #, php-format msgid "%s's posts" msgstr "" -#: src/Module/Profile/Conversations.php:108 src/Module/Profile/Profile.php:355 +#: src/Module/Profile/Conversations.php:108 src/Module/Profile/Profile.php:353 #: src/Protocol/Feed.php:1097 src/Protocol/OStatus.php:1018 #, php-format msgid "%s's comments" @@ -8412,43 +8623,43 @@ msgstr "" msgid "View Album" msgstr "" -#: src/Module/Profile/Profile.php:114 src/Module/Profile/Restricted.php:50 +#: src/Module/Profile/Profile.php:112 src/Module/Profile/Restricted.php:50 msgid "Profile not found." msgstr "" -#: src/Module/Profile/Profile.php:160 +#: src/Module/Profile/Profile.php:158 #, php-format msgid "" "You're currently viewing your profile as %s Cancel" msgstr "" -#: src/Module/Profile/Profile.php:169 src/Module/Settings/Account.php:576 +#: src/Module/Profile/Profile.php:167 src/Module/Settings/Account.php:576 msgid "Full Name:" msgstr "" -#: src/Module/Profile/Profile.php:174 +#: src/Module/Profile/Profile.php:172 msgid "Member since:" msgstr "" -#: src/Module/Profile/Profile.php:180 +#: src/Module/Profile/Profile.php:178 msgid "j F, Y" msgstr "" -#: src/Module/Profile/Profile.php:181 +#: src/Module/Profile/Profile.php:179 msgid "j F" msgstr "" -#: src/Module/Profile/Profile.php:189 src/Util/Temporal.php:168 +#: src/Module/Profile/Profile.php:187 src/Util/Temporal.php:168 msgid "Birthday:" msgstr "" -#: src/Module/Profile/Profile.php:192 src/Module/Settings/Profile/Index.php:253 +#: src/Module/Profile/Profile.php:190 src/Module/Settings/Profile/Index.php:253 #: src/Util/Temporal.php:170 msgid "Age: " msgstr "" -#: src/Module/Profile/Profile.php:192 src/Module/Settings/Profile/Index.php:253 +#: src/Module/Profile/Profile.php:190 src/Module/Settings/Profile/Index.php:253 #: src/Util/Temporal.php:170 #, php-format msgid "%d year old" @@ -8456,19 +8667,19 @@ msgid_plural "%d years old" msgstr[0] "" msgstr[1] "" -#: src/Module/Profile/Profile.php:197 src/Module/Settings/Profile/Index.php:246 +#: src/Module/Profile/Profile.php:195 src/Module/Settings/Profile/Index.php:246 msgid "Description:" msgstr "" -#: src/Module/Profile/Profile.php:263 +#: src/Module/Profile/Profile.php:261 msgid "Groups:" msgstr "" -#: src/Module/Profile/Profile.php:275 +#: src/Module/Profile/Profile.php:273 msgid "View profile as:" msgstr "" -#: src/Module/Profile/Profile.php:292 +#: src/Module/Profile/Profile.php:290 msgid "View as" msgstr "" @@ -11297,222 +11508,226 @@ msgstr "" msgid "Remove locally" msgstr "" -#: src/Object/Post.php:270 +#: src/Object/Post.php:271 #, php-format msgid "Block %s" msgstr "" -#: src/Object/Post.php:275 +#: src/Object/Post.php:276 #, php-format msgid "Ignore %s" msgstr "" -#: src/Object/Post.php:280 +#: src/Object/Post.php:281 #, php-format msgid "Collapse %s" msgstr "" #: src/Object/Post.php:285 +msgid "Report post" +msgstr "" + +#: src/Object/Post.php:290 msgid "Save to folder" msgstr "" -#: src/Object/Post.php:325 +#: src/Object/Post.php:330 msgid "I will attend" msgstr "" -#: src/Object/Post.php:325 +#: src/Object/Post.php:330 msgid "I will not attend" msgstr "" -#: src/Object/Post.php:325 +#: src/Object/Post.php:330 msgid "I might attend" msgstr "" -#: src/Object/Post.php:355 +#: src/Object/Post.php:360 msgid "Ignore thread" msgstr "" -#: src/Object/Post.php:356 +#: src/Object/Post.php:361 msgid "Unignore thread" msgstr "" -#: src/Object/Post.php:357 +#: src/Object/Post.php:362 msgid "Toggle ignore status" msgstr "" -#: src/Object/Post.php:367 +#: src/Object/Post.php:372 msgid "Add star" msgstr "" -#: src/Object/Post.php:368 +#: src/Object/Post.php:373 msgid "Remove star" msgstr "" -#: src/Object/Post.php:369 +#: src/Object/Post.php:374 msgid "Toggle star status" msgstr "" -#: src/Object/Post.php:380 +#: src/Object/Post.php:385 msgid "Pin" msgstr "" -#: src/Object/Post.php:381 +#: src/Object/Post.php:386 msgid "Unpin" msgstr "" -#: src/Object/Post.php:382 +#: src/Object/Post.php:387 msgid "Toggle pin status" msgstr "" -#: src/Object/Post.php:385 +#: src/Object/Post.php:390 msgid "Pinned" msgstr "" -#: src/Object/Post.php:390 +#: src/Object/Post.php:395 msgid "Add tag" msgstr "" -#: src/Object/Post.php:403 +#: src/Object/Post.php:408 msgid "Quote share this" msgstr "" -#: src/Object/Post.php:403 +#: src/Object/Post.php:408 msgid "Quote Share" msgstr "" -#: src/Object/Post.php:406 +#: src/Object/Post.php:411 msgid "Reshare this" msgstr "" -#: src/Object/Post.php:406 +#: src/Object/Post.php:411 msgid "Reshare" msgstr "" -#: src/Object/Post.php:407 +#: src/Object/Post.php:412 msgid "Cancel your Reshare" msgstr "" -#: src/Object/Post.php:407 +#: src/Object/Post.php:412 msgid "Unshare" msgstr "" -#: src/Object/Post.php:458 +#: src/Object/Post.php:463 #, php-format msgid "%s (Received %s)" msgstr "" -#: src/Object/Post.php:464 +#: src/Object/Post.php:469 msgid "Comment this item on your system" msgstr "" -#: src/Object/Post.php:464 +#: src/Object/Post.php:469 msgid "Remote comment" msgstr "" -#: src/Object/Post.php:486 +#: src/Object/Post.php:491 msgid "Share via ..." msgstr "" -#: src/Object/Post.php:486 +#: src/Object/Post.php:491 msgid "Share via external services" msgstr "" -#: src/Object/Post.php:515 +#: src/Object/Post.php:520 msgid "to" msgstr "" -#: src/Object/Post.php:516 +#: src/Object/Post.php:521 msgid "via" msgstr "" -#: src/Object/Post.php:517 +#: src/Object/Post.php:522 msgid "Wall-to-Wall" msgstr "" -#: src/Object/Post.php:518 +#: src/Object/Post.php:523 msgid "via Wall-To-Wall:" msgstr "" -#: src/Object/Post.php:563 +#: src/Object/Post.php:569 #, php-format msgid "Reply to %s" msgstr "" -#: src/Object/Post.php:566 +#: src/Object/Post.php:572 msgid "More" msgstr "" -#: src/Object/Post.php:584 +#: src/Object/Post.php:590 msgid "Notifier task is pending" msgstr "" -#: src/Object/Post.php:585 +#: src/Object/Post.php:591 msgid "Delivery to remote servers is pending" msgstr "" -#: src/Object/Post.php:586 +#: src/Object/Post.php:592 msgid "Delivery to remote servers is underway" msgstr "" -#: src/Object/Post.php:587 +#: src/Object/Post.php:593 msgid "Delivery to remote servers is mostly done" msgstr "" -#: src/Object/Post.php:588 +#: src/Object/Post.php:594 msgid "Delivery to remote servers is done" msgstr "" -#: src/Object/Post.php:608 +#: src/Object/Post.php:614 #, php-format msgid "%d comment" msgid_plural "%d comments" msgstr[0] "" msgstr[1] "" -#: src/Object/Post.php:609 +#: src/Object/Post.php:615 msgid "Show more" msgstr "" -#: src/Object/Post.php:610 +#: src/Object/Post.php:616 msgid "Show fewer" msgstr "" -#: src/Object/Post.php:646 +#: src/Object/Post.php:652 #, php-format msgid "Reshared by: %s" msgstr "" -#: src/Object/Post.php:651 +#: src/Object/Post.php:657 #, php-format msgid "Viewed by: %s" msgstr "" -#: src/Object/Post.php:656 +#: src/Object/Post.php:662 #, php-format msgid "Liked by: %s" msgstr "" -#: src/Object/Post.php:661 +#: src/Object/Post.php:667 #, php-format msgid "Disliked by: %s" msgstr "" -#: src/Object/Post.php:666 +#: src/Object/Post.php:672 #, php-format msgid "Attended by: %s" msgstr "" -#: src/Object/Post.php:671 +#: src/Object/Post.php:677 #, php-format msgid "Maybe attended by: %s" msgstr "" -#: src/Object/Post.php:676 +#: src/Object/Post.php:682 #, php-format msgid "Not attended by: %s" msgstr "" -#: src/Object/Post.php:681 +#: src/Object/Post.php:687 #, php-format msgid "Reacted with %s by: %s" msgstr ""