Merge pull request #13245 from MrPetovan/task/moderation-reports

Create moderation reports
This commit is contained in:
Michael Vogel 2023-07-10 07:50:11 +02:00 committed by GitHub
commit 6fd76829b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 1611 additions and 348 deletions

2
.gitignore vendored
View file

@ -19,7 +19,7 @@ robots.txt
/doc/cache /doc/cache
#ignore reports, should be generated with every build #ignore reports, should be generated with every build
report/ /report/
#ignore config files from eclipse, we don't want IDE files in our repository #ignore config files from eclipse, we don't want IDE files in our repository
.project .project

View file

@ -1695,19 +1695,33 @@ CREATE TABLE IF NOT EXISTS `report` (
`uid` mediumint unsigned COMMENT 'Reporting user', `uid` mediumint unsigned COMMENT 'Reporting user',
`reporter-id` int unsigned COMMENT 'Reporting contact', `reporter-id` int unsigned COMMENT 'Reporting contact',
`cid` int unsigned NOT NULL COMMENT 'Reported contact', `cid` int unsigned NOT NULL COMMENT 'Reported contact',
`gsid` int unsigned NOT NULL COMMENT 'Reported contact server',
`comment` text COMMENT 'Report', `comment` text COMMENT 'Report',
`category` varchar(20) COMMENT 'Category of the report (spam, violation, other)', `category-id` int unsigned NOT NULL DEFAULT 1 COMMENT 'Report category, one of Entity\Report::CATEGORY_*',
`rules` text COMMENT 'Violated rules',
`forward` boolean COMMENT 'Forward the report to the remote server', `forward` boolean COMMENT 'Forward the report to the remote server',
`created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '', `public-remarks` text COMMENT 'Remarks shared with the reporter',
`status` tinyint unsigned COMMENT 'Status of the report', `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`), PRIMARY KEY(`id`),
INDEX `uid` (`uid`), INDEX `uid` (`uid`),
INDEX `cid` (`cid`), INDEX `cid` (`cid`),
INDEX `reporter-id` (`reporter-id`), 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 (`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 (`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=''; ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='';
-- --
@ -1721,7 +1735,18 @@ CREATE TABLE IF NOT EXISTS `report-post` (
INDEX `uri-id` (`uri-id`), INDEX `uri-id` (`uri-id`),
FOREIGN KEY (`rid`) REFERENCES `report` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE, 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 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 -- TABLE search

View file

@ -77,7 +77,8 @@ Database Tables
| [push_subscriber](help/database/db_push_subscriber) | Used for OStatus: Contains feed subscribers | | [push_subscriber](help/database/db_push_subscriber) | Used for OStatus: Contains feed subscribers |
| [register](help/database/db_register) | registrations requiring admin approval | | [register](help/database/db_register) | registrations requiring admin approval |
| [report](help/database/db_report) | | | [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) | | | [search](help/database/db_search) | |
| [session](help/database/db_session) | web session storage | | [session](help/database/db_session) | web session storage |
| [storage](help/database/db_storage) | Data stored by Database storage backend | | [storage](help/database/db_storage) | Data stored by Database storage backend |

View file

@ -1,7 +1,7 @@
Table report-post Table report-post
=========== ===========
Individual posts attached to a moderation report
Fields Fields
------ ------

View file

@ -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)

View file

@ -6,28 +6,39 @@ Table report
Fields Fields
------ ------
| Field | Description | Type | Null | Key | Default | Extra | | Field | Description | Type | Null | Key | Default | Extra |
| ----------- | ----------------------------------------------- | ------------------ | ---- | --- | ------------------- | -------------- | | --------------- | ------------------------------------------------------------ | ------------------ | ---- | --- | ------------------- | -------------- |
| id | sequential ID | int unsigned | NO | PRI | NULL | auto_increment | | id | sequential ID | int unsigned | NO | PRI | NULL | auto_increment |
| uid | Reporting user | mediumint unsigned | YES | | NULL | | | uid | Reporting user | mediumint unsigned | YES | | NULL | |
| reporter-id | Reporting contact | int unsigned | YES | | NULL | | | reporter-id | Reporting contact | int unsigned | YES | | NULL | |
| cid | Reported contact | int unsigned | NO | | NULL | | | cid | Reported contact | int unsigned | NO | | NULL | |
| comment | Report | text | YES | | NULL | | | gsid | Reported contact server | int unsigned | NO | | NULL | |
| category | Category of the report (spam, violation, other) | varchar(20) | YES | | NULL | | | comment | Report | text | YES | | NULL | |
| rules | Violated rules | 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 | | | forward | Forward the report to the remote server | boolean | YES | | NULL | |
| created | | datetime | NO | | 0001-01-01 00:00:00 | | | public-remarks | Remarks shared with the reporter | text | YES | | NULL | |
| status | Status of the report | tinyint unsigned | 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 Indexes
------------ ------------
| Name | Fields | | Name | Fields |
| ----------- | ----------- | | ----------------- | ------------------ |
| PRIMARY | id | | PRIMARY | id |
| uid | uid | | uid | uid |
| cid | cid | | cid | cid |
| reporter-id | reporter-id | | reporter-id | reporter-id |
| gsid | gsid |
| assigned-uid | assigned-uid |
| status-resolution | status, resolution |
| created | created |
| edited | edited |
Foreign Keys Foreign Keys
------------ ------------
@ -37,5 +48,8 @@ Foreign Keys
| uid | [user](help/database/db_user) | uid | | uid | [user](help/database/db_user) | uid |
| reporter-id | [contact](help/database/db_contact) | id | | reporter-id | [contact](help/database/db_contact) | id |
| cid | [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) Return to [database documentation](help/database)

View file

@ -0,0 +1,33 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
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();
}
}

View file

@ -0,0 +1,33 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
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();
}
}

View file

@ -21,51 +21,126 @@
namespace Friendica\Moderation\Entity; namespace Friendica\Moderation\Entity;
use Friendica\Moderation\Collection;
/** /**
* @property-read int $id * @property-read int $id
* @property-read int $reporterId * @property-read int $reporterCid
* @property-read int $cid * @property-read int $cid
* @property-read string $comment * @property-read int $gsid
* @property-read string|null $category * @property-read string $comment
* @property-read bool $forward * @property-read string $publicRemarks
* @property-read array $postUriIds * @property-read string $privateRemarks
* @property-read int $uid * @property-read bool $forward
* @property-read \DateTime|null $created * @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 */ /** @var int|null */
protected $id; protected $id;
/** @var int ID of the contact making a moderation report*/ /** @var int ID of the contact making a moderation report */
protected $reporterId; protected $reporterCid;
/** @var int ID of the contact being reported*/ /** @var int ID of the contact being reported */
protected $cid; 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; protected $comment;
/** @var string Optional category */ /** @var int One of CATEGORY_* */
protected $category; protected $category;
/** @var string Violated rules */ /** @var int ID of the user making a moderation report, null in case of an incoming forwarded report */
protected $rules; protected $reporterUid;
/** @var bool Whether this report should be forwarded to the remote server */ /** @var bool Whether this report should be forwarded to the remote server */
protected $forward; protected $forward;
/** @var \DateTime|null When the report was created */ /** @var \DateTimeImmutable When the report was created */
protected $created; protected $created;
/** @var array Optional list of URI IDs of posts supporting the report*/ /** @var Collection\Report\Rules List of terms of service rule lines being possibly violated */
protected $postUriIds; protected $rules;
/** @var int ID of the user making a moderation report*/ /** @var Collection\Report\Posts List of URI IDs of posts supporting the report */
protected $uid; 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) public function __construct(
{ int $reporterCid,
$this->reporterId = $reporterId; int $cid,
$this->cid = $cid; int $gsid,
$this->created = $created; \DateTimeImmutable $created,
$this->comment = $comment; int $category,
$this->category = $category; int $reporterUid = null,
$this->rules = $rules; string $comment = '',
$this->forward = $forward; bool $forward = false,
$this->postUriIds = $postUriIds; Collection\Report\Posts $posts = null,
$this->uid = $uid; Collection\Report\Rules $rules = null,
$this->id = $id; 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;
} }
} }

View file

@ -0,0 +1,44 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
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;
}
}

View file

@ -0,0 +1,40 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
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;
}
}

View file

@ -22,28 +22,52 @@
namespace Friendica\Moderation\Factory; namespace Friendica\Moderation\Factory;
use Friendica\Capabilities\ICanCreateFromTableRow; use Friendica\Capabilities\ICanCreateFromTableRow;
use Friendica\Core\System;
use Friendica\Model\Contact;
use Friendica\Moderation\Collection;
use Friendica\Moderation\Entity; use Friendica\Moderation\Entity;
use Psr\Clock\ClockInterface;
use Psr\Log\LoggerInterface;
class Report extends \Friendica\BaseFactory implements ICanCreateFromTableRow 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 $row `report` table row
* @param array $postUriIds List of post URI ids from the `report-post` table * @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 * @return Entity\Report
* @throws \Exception * @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( return new Entity\Report(
$row['reporter-id'], $row['reporter-id'],
$row['cid'], $row['cid'],
new \DateTime($row['created'] ?? 'now', new \DateTimeZone('UTC')), $row['gsid'],
$row['comment'], new \DateTimeImmutable($row['created'], new \DateTimeZone('UTC')),
$row['category'], $row['category-id'],
$row['rules'],
$row['forward'],
$postUriIds,
$row['uid'], $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'], $row['id'],
); );
} }
@ -51,29 +75,73 @@ class Report extends \Friendica\BaseFactory implements ICanCreateFromTableRow
/** /**
* Creates a Report entity from a Mastodon API /reports request * Creates a Report entity from a Mastodon API /reports request
* *
* @see \Friendica\Module\Api\Mastodon\Reports::post() * @param array $rules Line-number indexed node rules array, see System::getRules(true)
*
* @param int $uid
* @param int $reporterId * @param int $reporterId
* @param int $cid * @param int $cid
* @param int $gsid
* @param string $comment * @param string $comment
* @param string $category
* @param bool $forward * @param bool $forward
* @param array $postUriIds * @param array $postUriIds
* @param array $ruleIds
* @param ?int $uid
* @return Entity\Report * @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( return new Entity\Report(
$reporterId, $reporterId,
$cid, $cid,
new \DateTime('now', new \DateTimeZone('UTC')), $gsid,
$comment, $this->clock->now(),
$category, $categoryId,
$rules,
$forward,
$postUriIds,
$uid, $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)),
);
}
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)),
); );
} }
} }

View file

@ -0,0 +1,35 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
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']
);
}
}

View file

@ -0,0 +1,35 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
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']
);
}
}

View file

@ -25,24 +25,30 @@ use Friendica\BaseEntity;
use Friendica\Core\Logger; use Friendica\Core\Logger;
use Friendica\Database\Database; use Friendica\Database\Database;
use Friendica\Model\Post; use Friendica\Model\Post;
use Friendica\Moderation\Factory;
use Friendica\Moderation\Collection;
use Friendica\Network\HTTPException\NotFoundException; use Friendica\Network\HTTPException\NotFoundException;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
class Report extends \Friendica\BaseRepository final class Report extends \Friendica\BaseRepository
{ {
protected static $table_name = 'report'; protected static $table_name = 'report';
/** /** @var Factory\Report */
* @var \Friendica\Moderation\Factory\Report
*/
protected $factory; 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); 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 public function selectOneById(int $lastInsertId): \Friendica\Moderation\Entity\Report
@ -50,37 +56,46 @@ class Report extends \Friendica\BaseRepository
return $this->_selectOne(['id' => $lastInsertId]); 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 = [ $fields = [
'uid' => $Report->uid, 'reporter-id' => $Report->reporterCid,
'reporter-id' => $Report->reporterId, 'uid' => $Report->reporterUid,
'cid' => $Report->cid, 'cid' => $Report->cid,
'comment' => $Report->comment, 'gsid' => $Report->gsid,
'category' => $Report->category, 'comment' => $Report->comment,
'rules' => $Report->rules, 'forward' => $Report->forward,
'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) { if ($Report->id) {
$this->db->update(self::$table_name, $fields, ['id' => $Report->id]); $this->db->update(self::$table_name, $fields, ['id' => $Report->id]);
} else { } else {
$fields['created'] = DateTimeFormat::utcNow();
$this->db->insert(self::$table_name, $fields, Database::INSERT_IGNORE); $this->db->insert(self::$table_name, $fields, Database::INSERT_IGNORE);
$Report = $this->selectOneById($this->db->lastInsertId()); $newReportId = $this->db->lastInsertId();
}
$this->db->delete('report-post', ['rid' => $Report->id]); foreach ($Report->posts as $post) {
if (Post::exists(['uri-id' => $post->uriId])) {
foreach ($postUriIds as $uriId) { $this->db->insert('report-post', ['rid' => $newReportId, 'uri-id' => $post->uriId, 'status' => $post->status]);
if (Post::exists(['uri-id' => $uriId])) { } else {
$this->db->insert('report-post', ['rid' => $Report->id, 'uri-id' => $uriId]); Logger::notice('Post does not exist', ['uri-id' => $post->uriId, 'report' => $Report]);
} else { }
Logger::notice('Post does not exist', ['uri-id' => $uriId, 'report' => $Report]);
} }
foreach ($Report->rules as $rule) {
$this->db->insert('report-rule', ['rid' => $newReportId, 'line-id' => $rule->lineId, 'text' => $rule->text]);
}
$Report = $this->selectOneById($newReportId);
} }
return $Report; return $Report;
@ -88,13 +103,14 @@ class Report extends \Friendica\BaseRepository
protected function _selectOne(array $condition, array $params = []): BaseEntity 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)) { if (!$this->db->isResult($fields)) {
throw new NotFoundException(); 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);
} }
} }

View file

@ -62,21 +62,23 @@ class Reports extends BaseApi
'forward' => false, // If the account is remote, should the report be forwarded to the remote admin? 'forward' => false, // If the account is remote, should the report be forwarded to the remote admin?
], $request); ], $request);
$contact = Contact::getById($request['account_id'], ['id']); $contact = Contact::getById($request['account_id'], ['id', 'gsid']);
if (empty($contact)) { if (empty($contact)) {
throw new HTTPException\NotFoundException('Account ' . $request['account_id'] . ' not found'); throw new HTTPException\NotFoundException('Account ' . $request['account_id'] . ' not found');
} }
$violation = ''; $report = $this->reportFactory->createFromReportsRequest(
$rules = System::getRules(true); System::getRules(),
Contact::getPublicIdByUserId(self::getCurrentUserID()),
foreach ($request['rule_ids'] as $key) { $contact['id'],
if (!empty($rules[$key])) { $contact['gsid'],
$violation .= $rules[$key] . "\n"; $request['comment'],
} $request['category'],
} $request['forward'],
$request['status_ids'],
$report = $this->reportFactory->createFromReportsRequest(Contact::getPublicIdByUserId(self::getCurrentUserID()), $request['account_id'], $request['comment'], $request['category'], trim($violation), $request['forward'], $request['status_ids'], self::getCurrentUserID()); $request['rule_ids'],
self::getCurrentUserID()
);
$this->reportRepo->save($report); $this->reportRepo->save($report);

View file

@ -0,0 +1,344 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
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']),
]);
}
}

View file

@ -255,6 +255,7 @@ class Post
$block = false; $block = false;
$ignore = false; $ignore = false;
$collapse = false; $collapse = false;
$report = false;
if (DI::userSession()->getLocalUserId()) { if (DI::userSession()->getLocalUserId()) {
$drop = [ $drop = [
'dropping' => $dropping, 'dropping' => $dropping,
@ -280,6 +281,10 @@ class Post
'collapse' => DI::l10n()->t('Collapse %s', $item['author-name']), 'collapse' => DI::l10n()->t('Collapse %s', $item['author-name']),
'author_id' => $item['author-id'], '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; $filer = DI::userSession()->getLocalUserId() ? DI::l10n()->t('Save to folder') : false;
@ -554,6 +559,7 @@ class Post
'block' => $block, 'block' => $block,
'ignore_author' => $ignore, 'ignore_author' => $ignore,
'collapse' => $collapse, 'collapse' => $collapse,
'report' => $report,
'vote' => $buttons, 'vote' => $buttons,
'like_html' => $responses['like']['output'], 'like_html' => $responses['like']['output'],
'dislike_html' => $responses['dislike']['output'], 'dislike_html' => $responses['dislike']['output'],

View file

@ -42,6 +42,7 @@ use Friendica\Model\Mail;
use Friendica\Model\Tag; use Friendica\Model\Tag;
use Friendica\Model\User; use Friendica\Model\User;
use Friendica\Model\Post; use Friendica\Model\Post;
use Friendica\Moderation\Entity\Report;
use Friendica\Protocol\Activity; use Friendica\Protocol\Activity;
use Friendica\Protocol\ActivityPub; use Friendica\Protocol\ActivityPub;
use Friendica\Protocol\Delivery; use Friendica\Protocol\Delivery;
@ -1893,8 +1894,8 @@ class Processor
*/ */
public static function ReportAccount(array $activity) public static function ReportAccount(array $activity)
{ {
$account_id = Contact::getIdForURL($activity['object_id']); $account = Contact::getByURL($activity['object_id'], null, ['id', 'gsid']);
if (empty($account_id)) { if (empty($account)) {
Logger::info('Unknown account', ['activity' => $activity]); Logger::info('Unknown account', ['activity' => $activity]);
Queue::remove($activity); Queue::remove($activity);
return; 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); 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']]);
} }
/** /**

View file

@ -1691,22 +1691,33 @@ return [
"uid" => ["type" => "mediumint unsigned", "foreign" => ["user" => "uid"], "comment" => "Reporting user"], "uid" => ["type" => "mediumint unsigned", "foreign" => ["user" => "uid"], "comment" => "Reporting user"],
"reporter-id" => ["type" => "int unsigned", "foreign" => ["contact" => "id"], "comment" => "Reporting contact"], "reporter-id" => ["type" => "int unsigned", "foreign" => ["contact" => "id"], "comment" => "Reporting contact"],
"cid" => ["type" => "int unsigned", "not null" => "1", "foreign" => ["contact" => "id"], "comment" => "Reported 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"], "comment" => ["type" => "text", "comment" => "Report"],
"category" => ["type" => "varchar(20)", "comment" => "Category of the report (spam, violation, other)"], "category-id" => ["type" => "int unsigned", "not null" => 1, "default" => \Friendica\Moderation\Entity\Report::CATEGORY_OTHER, "comment" => "Report category, one of Entity\Report::CATEGORY_*"],
"rules" => ["type" => "text", "comment" => "Violated rules"],
"forward" => ["type" => "boolean", "comment" => "Forward the report to the remote server"], "forward" => ["type" => "boolean", "comment" => "Forward the report to the remote server"],
"created" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""], "public-remarks" => ["type" => "text", "comment" => "Remarks shared with the reporter"],
"status" => ["type" => "tinyint unsigned", "comment" => "Status of the report"], "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" => [ "indexes" => [
"PRIMARY" => ["id"], "PRIMARY" => ["id"],
"uid" => ["uid"], "uid" => ["uid"],
"cid" => ["cid"], "cid" => ["cid"],
"reporter-id" => ["reporter-id"], "reporter-id" => ["reporter-id"],
"gsid" => ["gsid"],
"assigned-uid" => ["assigned-uid"],
"status-resolution" => ["status", "resolution"],
"created" => ["created"],
"edited" => ["edited"],
] ]
], ],
"report-post" => [ "report-post" => [
"comment" => "", "comment" => "Individual posts attached to a moderation report",
"fields" => [ "fields" => [
"rid" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "foreign" => ["report" => "id"], "comment" => "Report id"], "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"], "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"], "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" => [ "search" => [
"comment" => "", "comment" => "",
"fields" => [ "fields" => [

View file

@ -508,6 +508,8 @@ return [
'/item/delete' => [Module\Moderation\Item\Delete::class, [R::GET, R::POST]], '/item/delete' => [Module\Moderation\Item\Delete::class, [R::GET, R::POST]],
'/item/source[/{guid}]' => [Module\Moderation\Item\Source::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[/{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/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]], '/users/pending[/{action}/{uid}]' => [Module\Moderation\Users\Pending::class, [R::GET, R::POST]],

View file

@ -21,144 +21,242 @@
namespace Friendica\Test\src\Moderation\Factory; namespace Friendica\Test\src\Moderation\Factory;
use Friendica\Moderation\Collection;
use Friendica\Moderation\Factory; use Friendica\Moderation\Factory;
use Friendica\Moderation\Entity; use Friendica\Moderation\Entity;
use Friendica\Test\MockedTest; use Friendica\Test\MockedTest;
use Friendica\Util\Clock\FrozenClock;
use Friendica\Util\DateTimeFormat;
use Psr\Clock\ClockInterface;
use Psr\Log\NullLogger; use Psr\Log\NullLogger;
class ReportTest extends MockedTest class ReportTest extends MockedTest
{ {
public function dataCreateFromTableRow(): array 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 [ return [
'default' => [ 'default' => [
'clock' => $clock,
'row' => [ 'row' => [
'id' => 11, 'id' => 11,
'uid' => 12, 'reporter-id' => 12,
'reporter-id' => 14, 'uid' => null,
'cid' => 13, 'cid' => 13,
'comment' => '', 'gsid' => 14,
'category' => null, 'comment' => '',
'rules' => '', 'forward' => false,
'forward' => false, 'category-id' => Entity\Report::CATEGORY_SPAM,
'created' => null '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( 'assertion' => new Entity\Report(
14, 12,
13, 13,
new \DateTime('now', new \DateTimeZone('UTC')), 14,
'', $nowSeconds,
Entity\Report::CATEGORY_SPAM,
null, null,
'', '',
false, false,
[], new Collection\Report\Posts(),
12, new Collection\Report\Rules(),
11, '',
'',
null,
Entity\Report::STATUS_OPEN,
null,
null,
null,
11
), ),
], ],
'full' => [ 'full' => [
'clock' => $clock,
'row' => [ 'row' => [
'id' => 11, 'id' => 11,
'uid' => 12, 'reporter-id' => 42,
'reporter-id' => 14, 'uid' => 12,
'cid' => 13, 'cid' => 13,
'comment' => 'Report', 'gsid' => 14,
'category' => 'violation', 'comment' => 'Report',
'rules' => 'Rules', 'forward' => true,
'forward' => true, 'category-id' => Entity\Report::CATEGORY_VIOLATION,
'created' => '2021-10-12 12:23:00' '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( 'assertion' => new Entity\Report(
14, 42,
13, 13,
new \DateTime('2021-10-12 12:23:00', new \DateTimeZone('UTC')), 14,
'Report', new \DateTimeImmutable('2021-10-12 12:23:00', new \DateTimeZone('UTC')),
'violation', Entity\Report::CATEGORY_VIOLATION,
'Rules',
true,
[89, 90],
12, 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 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 * @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 public function dataCreateFromReportsRequest(): array
{ {
$clock = new FrozenClock();
return [ return [
'default' => [ 'default' => [
'reporter-id' => 14, 'clock' => $clock,
'cid' => 13, 'rules' => [],
'comment' => '', 'reporterId' => 12,
'category' => null, 'cid' => 13,
'rules' => '', 'gsid' => 14,
'forward' => false, 'comment' => '',
'postUriIds' => [], 'category' => 'spam',
'uid' => 12, 'forward' => false,
'assertion' => new Entity\Report( 'postUriIds' => [],
14, 'ruleIds' => [],
13, 'uid' => null,
new \DateTime('now', new \DateTimeZone('UTC')), 'assertion' => new Entity\Report(
'',
null,
'',
false,
[],
12, 12,
null 13,
14,
$clock->now(),
Entity\Report::CATEGORY_SPAM,
), ),
], ],
'full' => [ 'full' => [
'reporter-id' => 14, 'clock' => $clock,
'cid' => 13, 'rules' => ['', 'Rule 1', 'Rule 2', 'Rule 3'],
'comment' => 'Report', 'reporterId' => 12,
'category' => 'violation', 'cid' => 13,
'rules' => 'Rules', 'gsid' => 14,
'forward' => true, 'comment' => 'Report',
'postUriIds' => [89, 90], 'category' => 'violation',
'uid' => 12, 'forward' => true,
'assertion' => new Entity\Report( 'postUriIds' => [89, 90],
14, 'ruleIds' => [1, 3],
13, 'uid' => 42,
new \DateTime('now', new \DateTimeZone('UTC')), 'assertion' => new Entity\Report(
'Report',
'violation',
'Rules',
true,
[89, 90],
12, 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 * @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);
} }
} }

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2023.09-dev\n" "Project-Id-Version: 2023.09-dev\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -294,7 +294,7 @@ msgstr ""
#: mod/message.php:201 mod/message.php:357 mod/photos.php:1301 #: mod/message.php:201 mod/message.php:357 mod/photos.php:1301
#: src/Content/Conversation.php:392 src/Content/Conversation.php:1506 #: src/Content/Conversation.php:392 src/Content/Conversation.php:1506
#: src/Module/Item/Compose.php:206 src/Module/Post/Edit.php:145 #: 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" msgid "Please wait"
msgstr "" msgstr ""
@ -310,8 +310,12 @@ msgstr ""
#: src/Module/Install.php:234 src/Module/Install.php:274 #: src/Module/Install.php:234 src/Module/Install.php:274
#: src/Module/Install.php:309 src/Module/Invite.php:178 #: 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/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/Moderation/Report/Create.php:168
#: src/Module/Settings/Profile/Index.php:230 src/Object/Post.php:1084 #: 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/duepuntozero/config.php:85 view/theme/frio/config.php:171
#: view/theme/quattro/config.php:87 view/theme/vier/config.php:135 #: view/theme/quattro/config.php:87 view/theme/vier/config.php:135
msgid "Submit" msgid "Submit"
@ -596,29 +600,29 @@ msgstr ""
#: mod/photos.php:1139 mod/photos.php:1195 mod/photos.php:1275 #: mod/photos.php:1139 mod/photos.php:1195 mod/photos.php:1275
#: src/Module/Contact.php:619 src/Module/Item/Compose.php:188 #: 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" msgid "This is you"
msgstr "" msgstr ""
#: mod/photos.php:1141 mod/photos.php:1197 mod/photos.php:1277 #: 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" msgid "Comment"
msgstr "" msgstr ""
#: mod/photos.php:1143 mod/photos.php:1199 mod/photos.php:1279 #: 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/Content/Conversation.php:407 src/Module/Calendar/Event/Form.php:248
#: src/Module/Item/Compose.php:201 src/Module/Post/Edit.php:165 #: src/Module/Item/Compose.php:201 src/Module/Post/Edit.php:165
#: src/Object/Post.php:1097 #: src/Object/Post.php:1103
msgid "Preview" msgid "Preview"
msgstr "" msgstr ""
#: mod/photos.php:1144 src/Content/Conversation.php:360 #: 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..." msgid "Loading..."
msgstr "" msgstr ""
#: mod/photos.php:1236 src/Content/Conversation.php:1422 #: mod/photos.php:1236 src/Content/Conversation.php:1422
#: src/Object/Post.php:262 #: src/Object/Post.php:263
msgid "Select" msgid "Select"
msgstr "" msgstr ""
@ -630,19 +634,19 @@ msgstr ""
msgid "Delete" msgid "Delete"
msgstr "" msgstr ""
#: mod/photos.php:1298 src/Object/Post.php:400 #: mod/photos.php:1298 src/Object/Post.php:405
msgid "Like" msgid "Like"
msgstr "" msgstr ""
#: mod/photos.php:1299 src/Object/Post.php:400 #: mod/photos.php:1299 src/Object/Post.php:405
msgid "I like this (toggle)" msgid "I like this (toggle)"
msgstr "" msgstr ""
#: mod/photos.php:1300 src/Object/Post.php:401 #: mod/photos.php:1300 src/Object/Post.php:406
msgid "Dislike" msgid "Dislike"
msgstr "" 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)" msgid "I don't like this (toggle)"
msgstr "" msgstr ""
@ -1222,7 +1226,7 @@ msgid "Visible to <strong>everybody</strong>"
msgstr "" msgstr ""
#: src/Content/Conversation.php:330 src/Module/Item/Compose.php:200 #: 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:" msgid "Please enter a image/video/audio/webpage URL:"
msgstr "" msgstr ""
@ -1267,52 +1271,52 @@ msgid "attach file"
msgstr "" msgstr ""
#: src/Content/Conversation.php:365 src/Module/Item/Compose.php:190 #: 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" msgid "Bold"
msgstr "" msgstr ""
#: src/Content/Conversation.php:366 src/Module/Item/Compose.php:191 #: 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" msgid "Italic"
msgstr "" msgstr ""
#: src/Content/Conversation.php:367 src/Module/Item/Compose.php:192 #: 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" msgid "Underline"
msgstr "" msgstr ""
#: src/Content/Conversation.php:368 src/Module/Item/Compose.php:193 #: 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" msgid "Quote"
msgstr "" msgstr ""
#: src/Content/Conversation.php:369 src/Module/Item/Compose.php:194 #: 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" msgid "Add emojis"
msgstr "" msgstr ""
#: src/Content/Conversation.php:370 src/Module/Item/Compose.php:195 #: src/Content/Conversation.php:370 src/Module/Item/Compose.php:195
#: src/Object/Post.php:1089 #: src/Object/Post.php:1095
msgid "Content Warning" msgid "Content Warning"
msgstr "" msgstr ""
#: src/Content/Conversation.php:371 src/Module/Item/Compose.php:196 #: 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" msgid "Code"
msgstr "" msgstr ""
#: src/Content/Conversation.php:372 src/Module/Item/Compose.php:197 #: src/Content/Conversation.php:372 src/Module/Item/Compose.php:197
#: src/Object/Post.php:1093 #: src/Object/Post.php:1099
msgid "Image" msgid "Image"
msgstr "" msgstr ""
#: src/Content/Conversation.php:373 src/Module/Item/Compose.php:198 #: 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" msgid "Link"
msgstr "" msgstr ""
#: src/Content/Conversation.php:374 src/Module/Item/Compose.php:199 #: 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" msgid "Link or Media"
msgstr "" msgstr ""
@ -1467,21 +1471,21 @@ msgstr ""
msgid "Pinned item" msgid "Pinned item"
msgstr "" msgstr ""
#: src/Content/Conversation.php:1466 src/Object/Post.php:513 #: src/Content/Conversation.php:1466 src/Object/Post.php:518
#: src/Object/Post.php:514 #: src/Object/Post.php:519
#, php-format #, php-format
msgid "View %s's profile @ %s" msgid "View %s's profile @ %s"
msgstr "" msgstr ""
#: src/Content/Conversation.php:1479 src/Object/Post.php:501 #: src/Content/Conversation.php:1479 src/Object/Post.php:506
msgid "Categories:" msgid "Categories:"
msgstr "" msgstr ""
#: src/Content/Conversation.php:1480 src/Object/Post.php:502 #: src/Content/Conversation.php:1480 src/Object/Post.php:507
msgid "Filed under:" msgid "Filed under:"
msgstr "" msgstr ""
#: src/Content/Conversation.php:1488 src/Object/Post.php:527 #: src/Content/Conversation.php:1488 src/Object/Post.php:532
#, php-format #, php-format
msgid "%s from %s" msgid "%s from %s"
msgstr "" msgstr ""
@ -1696,7 +1700,7 @@ msgstr ""
msgid "Collapse" msgid "Collapse"
msgstr "" msgstr ""
#: src/Content/Item.php:434 src/Object/Post.php:482 #: src/Content/Item.php:434 src/Object/Post.php:487
msgid "Languages" msgid "Languages"
msgstr "" msgstr ""
@ -1754,7 +1758,7 @@ msgstr ""
#: src/Content/Nav.php:228 src/Module/BaseProfile.php:49 #: src/Content/Nav.php:228 src/Module/BaseProfile.php:49
#: src/Module/BaseSettings.php:100 src/Module/Contact.php:504 #: 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 #: src/Module/Welcome.php:57 view/theme/frio/theme.php:230
msgid "Profile" msgid "Profile"
msgstr "" msgstr ""
@ -2267,12 +2271,12 @@ msgid "More Trending Tags"
msgstr "" msgstr ""
#: src/Content/Widget/VCard.php:109 src/Model/Profile.php:378 #: 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:" msgid "XMPP:"
msgstr "" msgstr ""
#: src/Content/Widget/VCard.php:110 src/Model/Profile.php:379 #: 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:" msgid "Matrix:"
msgstr "" msgstr ""
@ -2280,7 +2284,7 @@ msgstr ""
#: src/Model/Event.php:109 src/Model/Event.php:473 src/Model/Event.php:965 #: 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/Model/Profile.php:373 src/Module/Contact/Profile.php:379
#: src/Module/Directory.php:147 src/Module/Notifications/Introductions.php:187 #: 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:" msgid "Location:"
msgstr "" msgstr ""
@ -2295,7 +2299,7 @@ msgstr ""
msgid "Unfollow" msgid "Unfollow"
msgstr "" 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" msgid "Yourself"
msgstr "" msgstr ""
@ -3051,7 +3055,7 @@ msgstr ""
msgid "Disallowed profile URL." msgid "Disallowed profile URL."
msgstr "" msgstr ""
#: src/Model/Contact.php:2994 src/Module/Friendica.php:83 #: src/Model/Contact.php:2994 src/Module/Friendica.php:102
msgid "Blocked domain" msgid "Blocked domain"
msgstr "" msgstr ""
@ -3299,8 +3303,8 @@ msgstr ""
msgid "Wall Photos" msgid "Wall Photos"
msgstr "" msgstr ""
#: src/Model/Profile.php:361 src/Module/Profile/Profile.php:285 #: src/Model/Profile.php:361 src/Module/Profile/Profile.php:283
#: src/Module/Profile/Profile.php:287 #: src/Module/Profile/Profile.php:285
msgid "Edit profile" msgid "Edit profile"
msgstr "" msgstr ""
@ -3309,7 +3313,7 @@ msgid "Change profile photo"
msgstr "" msgstr ""
#: src/Model/Profile.php:376 src/Module/Directory.php:152 #: src/Model/Profile.php:376 src/Module/Directory.php:152
#: src/Module/Profile/Profile.php:211 #: src/Module/Profile/Profile.php:209
msgid "Homepage:" msgid "Homepage:"
msgstr "" msgstr ""
@ -3404,6 +3408,7 @@ msgid "Title/Description:"
msgstr "" msgstr ""
#: src/Model/Profile.php:1025 src/Module/Admin/Summary.php:221 #: src/Model/Profile.php:1025 src/Module/Admin/Summary.php:221
#: src/Module/Moderation/Report/Create.php:280
#: src/Module/Moderation/Summary.php:77 #: src/Module/Moderation/Summary.php:77
msgid "Summary" msgid "Summary"
msgstr "" msgstr ""
@ -3858,6 +3863,8 @@ msgid "Manage Additional Features"
msgstr "" msgstr ""
#: src/Module/Admin/Federation.php:76 #: src/Module/Admin/Federation.php:76
#: src/Module/Moderation/Report/Create.php:191
#: src/Module/Moderation/Report/Create.php:316
msgid "Other" msgid "Other"
msgstr "" msgstr ""
@ -4224,7 +4231,7 @@ msgid "Policies"
msgstr "" msgstr ""
#: src/Module/Admin/Site.php:406 src/Module/Calendar/Event/Form.php:252 #: 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" msgid "Advanced"
msgstr "" msgstr ""
@ -5707,7 +5714,7 @@ msgstr ""
msgid "Share this event" msgid "Share this event"
msgstr "" 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" msgid "Basic"
msgstr "" msgstr ""
@ -5871,7 +5878,7 @@ msgid "Only show blocked contacts"
msgstr "" msgstr ""
#: src/Module/Contact.php:369 src/Module/Contact.php:441 #: src/Module/Contact.php:369 src/Module/Contact.php:441
#: src/Object/Post.php:360 #: src/Object/Post.php:365
msgid "Ignored" msgid "Ignored"
msgstr "" msgstr ""
@ -6082,7 +6089,7 @@ msgstr[1] ""
#: src/Module/Contact/Follow.php:70 src/Module/Contact/Redir.php:62 #: 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/Contact/Redir.php:222 src/Module/Conversation/Community.php:194
#: src/Module/Debug/ItemBody.php:38 src/Module/Diaspora/Receive.php:57 #: 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/Follow.php:41 src/Module/Item/Ignore.php:41
#: src/Module/Item/Pin.php:41 src/Module/Item/Pin.php:56 #: src/Module/Item/Pin.php:41 src/Module/Item/Pin.php:56
#: src/Module/Item/Star.php:42 src/Module/Update/Display.php:37 #: 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/Contact/Follow.php:171 src/Module/Contact/Profile.php:387
#: src/Module/Notifications/Introductions.php:191 #: src/Module/Notifications/Introductions.php:191
#: src/Module/Profile/Profile.php:236 #: src/Module/Profile/Profile.php:234
msgid "Tags:" msgid "Tags:"
msgstr "" msgstr ""
@ -6299,6 +6306,7 @@ msgid "Block/Unblock contact"
msgstr "" msgstr ""
#: src/Module/Contact/Profile.php:348 #: src/Module/Contact/Profile.php:348
#: src/Module/Moderation/Report/Create.php:293
msgid "Ignore contact" msgid "Ignore contact"
msgstr "" msgstr ""
@ -6554,7 +6562,7 @@ msgstr ""
msgid "Posts that mention or involve you" msgid "Posts that mention or involve you"
msgstr "" 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" msgid "Starred"
msgstr "" msgstr ""
@ -6907,55 +6915,55 @@ msgstr ""
msgid "Suggest a friend for %s" msgid "Suggest a friend for %s"
msgstr "" msgstr ""
#: src/Module/Friendica.php:64 #: src/Module/Friendica.php:82
msgid "Installed addons/apps:" msgid "Installed addons/apps:"
msgstr "" msgstr ""
#: src/Module/Friendica.php:69 #: src/Module/Friendica.php:87
msgid "No installed addons/apps" msgid "No installed addons/apps"
msgstr "" msgstr ""
#: src/Module/Friendica.php:74 #: src/Module/Friendica.php:92
#, php-format #, php-format
msgid "Read about the <a href=\"%1$s/tos\">Terms of Service</a> of this node." msgid "Read about the <a href=\"%1$s/tos\">Terms of Service</a> of this node."
msgstr "" msgstr ""
#: src/Module/Friendica.php:81 #: src/Module/Friendica.php:100
msgid "On this server the following remote servers are blocked." msgid "On this server the following remote servers are blocked."
msgstr "" 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:87
#: src/Module/Moderation/Blocklist/Server/Index.php:111 #: src/Module/Moderation/Blocklist/Server/Index.php:111
msgid "Reason for the block" msgid "Reason for the block"
msgstr "" msgstr ""
#: src/Module/Friendica.php:86 #: src/Module/Friendica.php:105
msgid "Download this list in CSV format" msgid "Download this list in CSV format"
msgstr "" msgstr ""
#: src/Module/Friendica.php:100 #: src/Module/Friendica.php:119
#, php-format #, php-format
msgid "" msgid ""
"This is Friendica, version %s that is running at the web location %s. The " "This is Friendica, version %s that is running at the web location %s. The "
"database version is %s, the post update version is %s." "database version is %s, the post update version is %s."
msgstr "" msgstr ""
#: src/Module/Friendica.php:105 #: src/Module/Friendica.php:124
msgid "" msgid ""
"Please visit <a href=\"https://friendi.ca\">Friendi.ca</a> to learn more " "Please visit <a href=\"https://friendi.ca\">Friendi.ca</a> to learn more "
"about the Friendica project." "about the Friendica project."
msgstr "" msgstr ""
#: src/Module/Friendica.php:106 #: src/Module/Friendica.php:125
msgid "Bug reports and issues: please visit" msgid "Bug reports and issues: please visit"
msgstr "" msgstr ""
#: src/Module/Friendica.php:106 #: src/Module/Friendica.php:125
msgid "the bugtracker at github" msgid "the bugtracker at github"
msgstr "" msgstr ""
#: src/Module/Friendica.php:107 #: src/Module/Friendica.php:126
msgid "" msgid ""
"Suggestions, praise, etc. - please email \"info\" at \"friendi - dot - ca" "Suggestions, praise, etc. - please email \"info\" at \"friendi - dot - ca"
msgstr "" msgstr ""
@ -7270,7 +7278,7 @@ msgid ""
"<a href=\"/settings/display\">Theme Customization settings</a>." "<a href=\"/settings/display\">Theme Customization settings</a>."
msgstr "" 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." msgid "The requested item doesn't exist or has been deleted."
msgstr "" msgstr ""
@ -7814,6 +7822,209 @@ msgstr ""
msgid "Item Guid" msgid "Item Guid"
msgstr "" 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 #: src/Module/Moderation/Summary.php:53
msgid "Normal Account" msgid "Normal Account"
msgstr "" msgstr ""
@ -8364,20 +8575,20 @@ msgid "No contacts."
msgstr "" msgstr ""
#: src/Module/Profile/Conversations.php:106 #: src/Module/Profile/Conversations.php:106
#: src/Module/Profile/Conversations.php:109 src/Module/Profile/Profile.php:353 #: src/Module/Profile/Conversations.php:109 src/Module/Profile/Profile.php:351
#: src/Module/Profile/Profile.php:356 src/Protocol/Feed.php:1090 #: src/Module/Profile/Profile.php:354 src/Protocol/Feed.php:1090
#: src/Protocol/OStatus.php:1009 #: src/Protocol/OStatus.php:1009
#, php-format #, php-format
msgid "%s's timeline" msgid "%s's timeline"
msgstr "" 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 #: src/Protocol/Feed.php:1094 src/Protocol/OStatus.php:1014
#, php-format #, php-format
msgid "%s's posts" msgid "%s's posts"
msgstr "" 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 #: src/Protocol/Feed.php:1097 src/Protocol/OStatus.php:1018
#, php-format #, php-format
msgid "%s's comments" msgid "%s's comments"
@ -8412,43 +8623,43 @@ msgstr ""
msgid "View Album" msgid "View Album"
msgstr "" 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." msgid "Profile not found."
msgstr "" msgstr ""
#: src/Module/Profile/Profile.php:160 #: src/Module/Profile/Profile.php:158
#, php-format #, php-format
msgid "" msgid ""
"You're currently viewing your profile as <b>%s</b> <a href=\"%s\" class=" "You're currently viewing your profile as <b>%s</b> <a href=\"%s\" class="
"\"btn btn-sm pull-right\">Cancel</a>" "\"btn btn-sm pull-right\">Cancel</a>"
msgstr "" 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:" msgid "Full Name:"
msgstr "" msgstr ""
#: src/Module/Profile/Profile.php:174 #: src/Module/Profile/Profile.php:172
msgid "Member since:" msgid "Member since:"
msgstr "" msgstr ""
#: src/Module/Profile/Profile.php:180 #: src/Module/Profile/Profile.php:178
msgid "j F, Y" msgid "j F, Y"
msgstr "" msgstr ""
#: src/Module/Profile/Profile.php:181 #: src/Module/Profile/Profile.php:179
msgid "j F" msgid "j F"
msgstr "" 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:" msgid "Birthday:"
msgstr "" 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 #: src/Util/Temporal.php:170
msgid "Age: " msgid "Age: "
msgstr "" 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 #: src/Util/Temporal.php:170
#, php-format #, php-format
msgid "%d year old" msgid "%d year old"
@ -8456,19 +8667,19 @@ msgid_plural "%d years old"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" 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:" msgid "Description:"
msgstr "" msgstr ""
#: src/Module/Profile/Profile.php:263 #: src/Module/Profile/Profile.php:261
msgid "Groups:" msgid "Groups:"
msgstr "" msgstr ""
#: src/Module/Profile/Profile.php:275 #: src/Module/Profile/Profile.php:273
msgid "View profile as:" msgid "View profile as:"
msgstr "" msgstr ""
#: src/Module/Profile/Profile.php:292 #: src/Module/Profile/Profile.php:290
msgid "View as" msgid "View as"
msgstr "" msgstr ""
@ -11297,222 +11508,226 @@ msgstr ""
msgid "Remove locally" msgid "Remove locally"
msgstr "" msgstr ""
#: src/Object/Post.php:270 #: src/Object/Post.php:271
#, php-format #, php-format
msgid "Block %s" msgid "Block %s"
msgstr "" msgstr ""
#: src/Object/Post.php:275 #: src/Object/Post.php:276
#, php-format #, php-format
msgid "Ignore %s" msgid "Ignore %s"
msgstr "" msgstr ""
#: src/Object/Post.php:280 #: src/Object/Post.php:281
#, php-format #, php-format
msgid "Collapse %s" msgid "Collapse %s"
msgstr "" msgstr ""
#: src/Object/Post.php:285 #: src/Object/Post.php:285
msgid "Report post"
msgstr ""
#: src/Object/Post.php:290
msgid "Save to folder" msgid "Save to folder"
msgstr "" msgstr ""
#: src/Object/Post.php:325 #: src/Object/Post.php:330
msgid "I will attend" msgid "I will attend"
msgstr "" msgstr ""
#: src/Object/Post.php:325 #: src/Object/Post.php:330
msgid "I will not attend" msgid "I will not attend"
msgstr "" msgstr ""
#: src/Object/Post.php:325 #: src/Object/Post.php:330
msgid "I might attend" msgid "I might attend"
msgstr "" msgstr ""
#: src/Object/Post.php:355 #: src/Object/Post.php:360
msgid "Ignore thread" msgid "Ignore thread"
msgstr "" msgstr ""
#: src/Object/Post.php:356 #: src/Object/Post.php:361
msgid "Unignore thread" msgid "Unignore thread"
msgstr "" msgstr ""
#: src/Object/Post.php:357 #: src/Object/Post.php:362
msgid "Toggle ignore status" msgid "Toggle ignore status"
msgstr "" msgstr ""
#: src/Object/Post.php:367 #: src/Object/Post.php:372
msgid "Add star" msgid "Add star"
msgstr "" msgstr ""
#: src/Object/Post.php:368 #: src/Object/Post.php:373
msgid "Remove star" msgid "Remove star"
msgstr "" msgstr ""
#: src/Object/Post.php:369 #: src/Object/Post.php:374
msgid "Toggle star status" msgid "Toggle star status"
msgstr "" msgstr ""
#: src/Object/Post.php:380 #: src/Object/Post.php:385
msgid "Pin" msgid "Pin"
msgstr "" msgstr ""
#: src/Object/Post.php:381 #: src/Object/Post.php:386
msgid "Unpin" msgid "Unpin"
msgstr "" msgstr ""
#: src/Object/Post.php:382 #: src/Object/Post.php:387
msgid "Toggle pin status" msgid "Toggle pin status"
msgstr "" msgstr ""
#: src/Object/Post.php:385 #: src/Object/Post.php:390
msgid "Pinned" msgid "Pinned"
msgstr "" msgstr ""
#: src/Object/Post.php:390 #: src/Object/Post.php:395
msgid "Add tag" msgid "Add tag"
msgstr "" msgstr ""
#: src/Object/Post.php:403 #: src/Object/Post.php:408
msgid "Quote share this" msgid "Quote share this"
msgstr "" msgstr ""
#: src/Object/Post.php:403 #: src/Object/Post.php:408
msgid "Quote Share" msgid "Quote Share"
msgstr "" msgstr ""
#: src/Object/Post.php:406 #: src/Object/Post.php:411
msgid "Reshare this" msgid "Reshare this"
msgstr "" msgstr ""
#: src/Object/Post.php:406 #: src/Object/Post.php:411
msgid "Reshare" msgid "Reshare"
msgstr "" msgstr ""
#: src/Object/Post.php:407 #: src/Object/Post.php:412
msgid "Cancel your Reshare" msgid "Cancel your Reshare"
msgstr "" msgstr ""
#: src/Object/Post.php:407 #: src/Object/Post.php:412
msgid "Unshare" msgid "Unshare"
msgstr "" msgstr ""
#: src/Object/Post.php:458 #: src/Object/Post.php:463
#, php-format #, php-format
msgid "%s (Received %s)" msgid "%s (Received %s)"
msgstr "" msgstr ""
#: src/Object/Post.php:464 #: src/Object/Post.php:469
msgid "Comment this item on your system" msgid "Comment this item on your system"
msgstr "" msgstr ""
#: src/Object/Post.php:464 #: src/Object/Post.php:469
msgid "Remote comment" msgid "Remote comment"
msgstr "" msgstr ""
#: src/Object/Post.php:486 #: src/Object/Post.php:491
msgid "Share via ..." msgid "Share via ..."
msgstr "" msgstr ""
#: src/Object/Post.php:486 #: src/Object/Post.php:491
msgid "Share via external services" msgid "Share via external services"
msgstr "" msgstr ""
#: src/Object/Post.php:515 #: src/Object/Post.php:520
msgid "to" msgid "to"
msgstr "" msgstr ""
#: src/Object/Post.php:516 #: src/Object/Post.php:521
msgid "via" msgid "via"
msgstr "" msgstr ""
#: src/Object/Post.php:517 #: src/Object/Post.php:522
msgid "Wall-to-Wall" msgid "Wall-to-Wall"
msgstr "" msgstr ""
#: src/Object/Post.php:518 #: src/Object/Post.php:523
msgid "via Wall-To-Wall:" msgid "via Wall-To-Wall:"
msgstr "" msgstr ""
#: src/Object/Post.php:563 #: src/Object/Post.php:569
#, php-format #, php-format
msgid "Reply to %s" msgid "Reply to %s"
msgstr "" msgstr ""
#: src/Object/Post.php:566 #: src/Object/Post.php:572
msgid "More" msgid "More"
msgstr "" msgstr ""
#: src/Object/Post.php:584 #: src/Object/Post.php:590
msgid "Notifier task is pending" msgid "Notifier task is pending"
msgstr "" msgstr ""
#: src/Object/Post.php:585 #: src/Object/Post.php:591
msgid "Delivery to remote servers is pending" msgid "Delivery to remote servers is pending"
msgstr "" msgstr ""
#: src/Object/Post.php:586 #: src/Object/Post.php:592
msgid "Delivery to remote servers is underway" msgid "Delivery to remote servers is underway"
msgstr "" msgstr ""
#: src/Object/Post.php:587 #: src/Object/Post.php:593
msgid "Delivery to remote servers is mostly done" msgid "Delivery to remote servers is mostly done"
msgstr "" msgstr ""
#: src/Object/Post.php:588 #: src/Object/Post.php:594
msgid "Delivery to remote servers is done" msgid "Delivery to remote servers is done"
msgstr "" msgstr ""
#: src/Object/Post.php:608 #: src/Object/Post.php:614
#, php-format #, php-format
msgid "%d comment" msgid "%d comment"
msgid_plural "%d comments" msgid_plural "%d comments"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: src/Object/Post.php:609 #: src/Object/Post.php:615
msgid "Show more" msgid "Show more"
msgstr "" msgstr ""
#: src/Object/Post.php:610 #: src/Object/Post.php:616
msgid "Show fewer" msgid "Show fewer"
msgstr "" msgstr ""
#: src/Object/Post.php:646 #: src/Object/Post.php:652
#, php-format #, php-format
msgid "Reshared by: %s" msgid "Reshared by: %s"
msgstr "" msgstr ""
#: src/Object/Post.php:651 #: src/Object/Post.php:657
#, php-format #, php-format
msgid "Viewed by: %s" msgid "Viewed by: %s"
msgstr "" msgstr ""
#: src/Object/Post.php:656 #: src/Object/Post.php:662
#, php-format #, php-format
msgid "Liked by: %s" msgid "Liked by: %s"
msgstr "" msgstr ""
#: src/Object/Post.php:661 #: src/Object/Post.php:667
#, php-format #, php-format
msgid "Disliked by: %s" msgid "Disliked by: %s"
msgstr "" msgstr ""
#: src/Object/Post.php:666 #: src/Object/Post.php:672
#, php-format #, php-format
msgid "Attended by: %s" msgid "Attended by: %s"
msgstr "" msgstr ""
#: src/Object/Post.php:671 #: src/Object/Post.php:677
#, php-format #, php-format
msgid "Maybe attended by: %s" msgid "Maybe attended by: %s"
msgstr "" msgstr ""
#: src/Object/Post.php:676 #: src/Object/Post.php:682
#, php-format #, php-format
msgid "Not attended by: %s" msgid "Not attended by: %s"
msgstr "" msgstr ""
#: src/Object/Post.php:681 #: src/Object/Post.php:687
#, php-format #, php-format
msgid "Reacted with %s by: %s" msgid "Reacted with %s by: %s"
msgstr "" msgstr ""

View file

@ -0,0 +1,25 @@
<div class="widget">
<p><strong>{{$l10n.contact_title}}</strong></p>
{{if $contact}}
{{include file="contact/entry.tpl"}}
{{/if}}
<p><strong>{{$l10n.category_title}}</strong></p>
{{if $category}}
<p>{{$category}}</p>
{{/if}}
{{if $rules}}
<p><strong>{{$l10n.rules_title}}</strong></p>
<ol>
{{foreach $rules as $rule_id => $rule_text}}
<li value="{{$rule_id}}">{{$rule_text}}</li>
{{/foreach}}
</ol>
{{/if}}
{{if $comment}}
<p><strong>{{$l10n.comment_title}}</strong></p>
<p>{{$comment nofilter}}</p>
{{/if}}
{{if $posts}}
<p><strong>{{$l10n.posts_title}} ({{$posts}})</strong></p>
{{/if}}
</div>

View file

@ -0,0 +1,16 @@
<div class="generic-page-wrapper">
<h1>{{$l10n.title}} - {{$l10n.page}}</h1>
<p>{{$l10n.description}}</p>
<form action="" method="post">
{{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}}
<p><button type="submit" class="btn btn-primary">{{$l10n.submit}}</button></p>
</form>
</div>

View file

@ -0,0 +1,9 @@
<div class="generic-page-wrapper">
<h1>{{$l10n.title}} - {{$l10n.page}}</h1>
<p>{{$l10n.description}}</p>
<form action="" method="post">
{{include file="field_input.tpl" field=$url}}
<p><button type="submit" class="btn btn-primary">{{$l10n.submit}}</button></p>
</form>
</div>

View file

@ -0,0 +1,24 @@
<div class="generic-page-wrapper">
<h1>{{$l10n.title}} - {{$l10n.page}}</h1>
<p>{{$l10n.description}}</p>
<form action="" method="post">
<table class="table-striped table-condensed">
{{foreach $threads as $thread}}
<tr>
<td>
<div id="tread-wrapper-{{$thread.id}}" class="tread-wrapper panel toplevel_item">
{{foreach $thread.items as $item}}
{{include file="{{$item.template}}"}}
{{/foreach}}
</div>
</td>
<td>
<input type="checkbox" name="uri-ids[]" value="{{$thread.items[0].uriid}}">
</td>
</tr>
{{/foreach}}
</table>
<p><button type="submit" class="btn btn-primary">{{$l10n.submit}}</button></p>
</form>
</div>

View file

@ -0,0 +1,16 @@
<div class="generic-page-wrapper">
<h1>{{$l10n.title}} - {{$l10n.page}}</h1>
<p>{{$l10n.description}}</p>
<form action="" method="post">
{{foreach $rules as $rule}}
<div class="field checkbox" id="div_id_{{$rule.0}}_{{$rule.1}}">
<input type="checkbox" name="{{$rule.0}}" id="id_{{$rule.0}}_{{$rule.1}}" value="{{$rule.1}}" {{if $rule.3}}checked="checked"{{/if}}>
<label for="id_{{$rule.0}}_{{$rule.1}}">
{{$rule.2}}
</label>
</div>
{{/foreach}}
<p><button type="submit" class="btn btn-primary">{{$l10n.submit}}</button></p>
</form>
</div>

View file

@ -0,0 +1,28 @@
<div class="generic-page-wrapper">
<h1>{{$l10n.title}} - {{$l10n.page}}</h1>
<p>{{$l10n.description}}</p>
<div>
{{$summary nofilter}}
</div>
<h2>{{$l10n.contact_action_title}}</h2>
<p>{{$l10n.contact_action_desc}}</p>
<form action="" method="post">
<input type="hidden" name="cid" value="{{$cid}}">
<input type="hidden" name="category" value="{{$category}}">
<input type="hidden" name="rule-ids" value="{{$ruleIds}}">
<input type="hidden" name="uri-ids" value="{{$uriIds}}">
{{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}}
<p><button type="submit" name="report_create" class="btn btn-primary">{{$l10n.submit}}</button></p>
</form>
</div>

View file

@ -161,13 +161,15 @@
</div> </div>
<div class="wall-item-actions-tools"> <div class="wall-item-actions-tools">
{{if $item.drop && $item.drop.pagedrop}} {{if $item.drop && $item.drop.pagedrop}}
<input type="checkbox" title="{{$item.drop.select}}" name="itemselected[]" class="item-select" value="{{$item.id}}" /> <input type="checkbox" title="{{$item.drop.select}}" name="itemselected[]" class="item-select" value="{{$item.id}}" />
{{/if}} {{/if}}
{{if $item.drop && $item.drop.dropping}} {{if $item.drop && $item.drop.dropping}}
<a role="button" href="item/drop/{{$item.id}}/{{$item.return}}" onclick="return confirmDelete();" title="{{$item.drop.delete}}"><i class="icon-trash icon-large"><span class="sr-only">{{$item.drop.delete}}</span></i></a> <a role="button" href="item/drop/{{$item.id}}/{{$item.return}}" onclick="return confirmDelete();" title="{{$item.drop.delete}}"><i class="icon-trash icon-large"><span class="sr-only">{{$item.drop.delete}}</span></i></a>
{{/if}} {{/if}}
{{if $item.report}}
<a href="{{$item.report.href}}" title="{{$item.report.label}}"><i class="icon-flag icon-large"><span class="sr-only">{{$item.report.label}}</span></i></a>
{{/if}}
{{if $item.edpost}} {{if $item.edpost}}
<a role="button" href="{{$item.edpost.0}}" title="{{$item.edpost.1}}"><i class="icon-edit icon-large"><span class="sr-only">{{$item.edpost.1}}</span></i></a> <a role="button" href="{{$item.edpost.0}}" title="{{$item.edpost.1}}"><i class="icon-edit icon-large"><span class="sr-only">{{$item.edpost.1}}</span></i></a>
{{/if}} {{/if}}

View file

@ -409,6 +409,11 @@ as the value of $top_child_total (this is done at the end of this file)
<a class="btn-link navicon collapse" href="javascript:collapseAuthor('item/collapse/{{$item.id}}/{{$item.return}}', 'item-{{$item.guid}}');" title="{{$item.collapse.collapse}}"><i class="fa fa-ban" aria-hidden="true"></i> {{$item.collapse.collapse}}</a> <a class="btn-link navicon collapse" href="javascript:collapseAuthor('item/collapse/{{$item.id}}/{{$item.return}}', 'item-{{$item.guid}}');" title="{{$item.collapse.collapse}}"><i class="fa fa-ban" aria-hidden="true"></i> {{$item.collapse.collapse}}</a>
</li> </li>
{{/if}} {{/if}}
{{if $item.report}}
<li role="menuitem">
<a class="btn-link navicon ignore" href="{{$item.report.href}}"><i class="fa fa-flag" aria-hidden="true"></i> {{$item.report.label}}</a>
</li>
{{/if}}
</ul> </ul>
</span> </span>
</span> </span>