UPSTREAM BASELINE: nextcloud/spreed v22.0.12 (без изменений)
Type checking / changes (push) Has been cancelled
Type checking / test (push) Has been cancelled
Type checking / typescript-summary (push) Has been cancelled
Node tests / changes (push) Has been cancelled
Node tests / test (push) Has been cancelled
Node tests / test-summary (push) Has been cancelled

Источник: https://github.com/nextcloud/spreed/archive/refs/tags/v22.0.12.tar.gz
С этого коммита ветка официального Nextcloud Talk отрезана (решение владельца 2026-07-06).
Все дальнейшие изменения — только наши; версии релизов: 22.0.12-f7.N.
This commit is contained in:
2026-07-06 14:07:50 +00:00
commit 01acfa3b40
1716 changed files with 613013 additions and 0 deletions
@@ -0,0 +1,23 @@
<?xml version="1.0"?>
<!--
- SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<info>
<id>spreedcheats</id>
<name>Talk cheats app for Intergration tests</name>
<namespace>SpreedCheats</namespace>
<description/>
<licence>AGPL</licence>
<author>Joas Schilling</author>
<version>22.0.0</version>
<types>
<logging/>
</types>
<dependencies>
<nextcloud min-version="32" max-version="32" />
</dependencies>
</info>
@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
return [
'ocs' => [
['name' => 'Api#resetSpreed', 'url' => '/', 'verb' => 'DELETE'],
['name' => 'Api#ageChat', 'url' => '/age', 'verb' => 'POST'],
['name' => 'Api#forgedFederationLeave', 'url' => '/forged/federation/active', 'verb' => 'DELETE'],
['name' => 'Api#createEventInCalendar', 'url' => '/calendar', 'verb' => 'POST'],
['name' => 'Api#createDashboardEvents', 'url' => '/dashboardEvents', 'verb' => 'POST'],
['name' => 'Api#createEventAndInviteParticipant', 'url' => '/mutualEvents', 'verb' => 'POST'],
],
];
@@ -0,0 +1,18 @@
BEGIN:VCALENDAR
PRODID:-//IDN nextcloud.com//Calendar app 5.2.0-dev.1//EN
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
CREATED:20250310T171800Z
DTSTAMP:20250310T171819Z
LAST-MODIFIED:20250310T171819Z
SEQUENCE:2
UID:{{{UID}}}
DTSTART:{{{START}}}
DTEND:{{{END}}}
STATUS:CONFIRMED
SUMMARY:{{{NAME}}}-single
LOCATION:{{{LOCATION}}}
DESCRIPTION:{{{NAME}}}
END:VEVENT
END:VCALENDAR
@@ -0,0 +1,20 @@
BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
PRODID:-//IDN nextcloud.com//Calendar app 5.3.0-dev.1//EN
BEGIN:VEVENT
CREATED:20250428T161339Z
DTSTAMP:20250428T172521Z
LAST-MODIFIED:20250428T172521Z
SEQUENCE:3
UID:{{{UID}}}
DTSTART:{{{START}}}
DTEND:{{{END}}}
STATUS:CONFIRMED
SUMMARY:{{{NAME}}}-attachment
LOCATION:{{{LOCATION}}}
DESCRIPTION:{{{NAME}}}
ATTACH;FMTTYPE=image/jpeg;FILENAME=/Calendar/signal-2025-03-21-121810_004.j
peg;X-NC-FILE-ID=566;X-NC-HAS-PREVIEW=true:/index.php/f/566
END:VEVENT
END:VCALENDAR
@@ -0,0 +1,23 @@
BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//IDN nextcloud.com//Calendar app 5.3.0-dev.1//EN
BEGIN:VEVENT
CREATED:20250428T163259Z
DTSTAMP:20250428T163714Z
LAST-MODIFIED:20250428T163714Z
SEQUENCE:2
UID:{{{UID}}}
DTSTART:{{{START}}}
DTEND:{{{END}}}
STATUS:CONFIRMED
SUMMARY:{{{NAME}}}-attendees
LOCATION:{{{LOCATION}}}
DESCRIPTION:{{{NAME}}}
ATTENDEE;CN=bob;CUTYPE=INDIVIDUAL;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPAN
T;RSVP=TRUE;LANGUAGE=en_GB;SCHEDULE-STATUS=1.1:mailto:bob@domain.tld
ATTENDEE;CN=Alice Alice;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;ROLE=REQ-PARTIC
IPANT;LANGUAGE=en_GB;SCHEDULE-STATUS=2.0:mailto:alice@domain.tld
ORGANIZER;CN=admin:mailto:admin@example.net
END:VEVENT
END:VCALENDAR
@@ -0,0 +1,19 @@
BEGIN:VCALENDAR
PRODID:-//IDN nextcloud.com//Calendar app 5.3.0-dev.1//EN
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
CREATED:20250430T095901Z
DTSTAMP:20250430T095947Z
LAST-MODIFIED:20250430T095947Z
SEQUENCE:2
UID:{{{UID}}}
DTSTART:{{{START}}}
DTEND:{{{END}}}
STATUS:CONFIRMED
SUMMARY:{{{NAME}}}-recurring
LOCATION:{{{LOCATION}}}
DESCRIPTION:{{{NAME}}}
RRULE:FREQ=WEEKLY;COUNT=3;BYDAY=TH
END:VEVENT
END:VCALENDAR
@@ -0,0 +1,18 @@
BEGIN:VCALENDAR
PRODID:-//IDN nextcloud.com//Calendar app 5.2.0-dev.1//EN
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
CREATED:20250310T171800Z
DTSTAMP:20250310T171819Z
LAST-MODIFIED:20250310T171819Z
SEQUENCE:2
UID:{{{UID}}}
DTSTART:{{{START}}}
DTEND:{{{END}}}
STATUS:CANCELLED
SUMMARY:{{{NAME}}}-single
LOCATION:{{{LOCATION}}}
DESCRIPTION:{{{NAME}}}
END:VEVENT
END:VCALENDAR
@@ -0,0 +1,17 @@
BEGIN:VCALENDAR
PRODID:-//IDN nextcloud.com//Calendar app 5.2.0-dev.1//EN
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
CREATED:20250310T171800Z
DTSTAMP:20250310T171819Z
LAST-MODIFIED:20250310T171819Z
SEQUENCE:2
UID:{{{UID}}}
DTSTART:{{{START}}}
DTEND:{{{END}}}
SUMMARY:{{{NAME}}}-no-status
LOCATION:{{{LOCATION}}}
DESCRIPTION:{{{NAME}}}
END:VEVENT
END:VCALENDAR
@@ -0,0 +1,17 @@
BEGIN:VCALENDAR
PRODID:-//IDN nextcloud.com//Calendar app 5.2.0-dev.1//EN
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
CREATED:20250310T171800Z
DTSTAMP:20250310T171819Z
LAST-MODIFIED:20250310T171819Z
SEQUENCE:2
UID:{{{UID}}}
DTSTART:{{{START}}}
STATUS:CONFIRMED
SUMMARY:{{{NAME}}}-no-end
LOCATION:{{{LOCATION}}}
DESCRIPTION:{{{NAME}}}
END:VEVENT
END:VCALENDAR
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\SpreedCheats\AppInfo;
use OCA\SpreedCheats\PreferenceListener;
use OCA\SpreedCheats\SpeechToText\LoremIpsumSpeechToTextProvider;
use OCA\SpreedCheats\Translation\LoremIpsumTranslationProvider;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\Config\BeforePreferenceDeletedEvent;
class Application extends App implements IBootstrap {
public const APP_ID = 'spreedcheats';
public function __construct() {
parent::__construct(self::APP_ID);
}
public function register(IRegistrationContext $context): void {
$context->registerSpeechToTextProvider(LoremIpsumSpeechToTextProvider::class);
$context->registerTranslationProvider(LoremIpsumTranslationProvider::class);
$context->registerEventListener(BeforePreferenceDeletedEvent::class, PreferenceListener::class);
}
public function boot(IBootContext $context): void {
}
}
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\SpreedCheats\Calendar;
use Sabre\VObject\UUIDUtil;
class EventGenerator {
public static function generateEvents(string $name, string $location, int $start, int $end): array {
$files = scandir(__DIR__ . '/../../calendars/');
$events = [];
$startDate = (new \DateTime())->setTimestamp($start);
$endDate = (new \DateTime())->setTimestamp($end);
foreach ($files as $file) {
if ($file === '.' || $file === '..') {
continue;
}
$calData = file_get_contents(__DIR__ . '/../../calendars/' . $file);
if ($calData === false) {
continue;
}
$interval = new \DateInterval('P1D');
$startDate->add($interval);
$endDate->add($interval);
$uid = UUIDUtil::getUUID();
$events[] = str_replace(['{{{NAME}}}', '{{{START}}}', '{{{END}}}', '{{{UID}}}', '{{{LOCATION}}}'], [$name, $startDate->format('Ymd\THis'), $endDate->format('Ymd\THis'), $uid, $location], $calData);
}
return $events;
}
}
@@ -0,0 +1,353 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\SpreedCheats\Controller;
use OCA\SpreedCheats\Calendar\EventGenerator;
use OCA\Talk\Model\Attendee;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\Calendar\Exceptions\CalendarException;
use OCP\Calendar\ICreateFromString;
use OCP\Calendar\IManager;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\IRequest;
use OCP\Share\IShare;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\UUIDUtil;
class ApiController extends OCSController {
public function __construct(
string $appName,
IRequest $request,
protected IDBConnection $db,
private IManager $calendarManager,
private ?string $userId,
) {
parent::__construct($appName, $request);
}
public function resetSpreed(): DataResponse {
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_attachments')->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_attendees')->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_bots_conversation')->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_bots_server')->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_bridges')->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_commands')
->where($delete->expr()->neq('app', $delete->createNamedParameter('')))
->andWhere($delete->expr()->neq('command', $delete->createNamedParameter('help')))
->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_consent')->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_internalsignaling')->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_invitations')->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_phone_numbers')->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_polls')->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_poll_votes')->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_proxy_messages')->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_reminders')->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_retry_ocm')->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_rooms')->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_sessions')->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_threads')->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_thread_attendees')->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('share')
->where($delete->expr()->orX(
$delete->expr()->eq('share_type', $delete->createNamedParameter(IShare::TYPE_ROOM)),
$delete->expr()->eq('share_type', $delete->createNamedParameter(11 /*RoomShareProvider::SHARE_TYPE_USERROOM*/))
))
->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('preferences')
->where($delete->expr()->in('configkey', $delete->createNamedParameter(['changelog', 'note_to_self', 'samples_created'], IQueryBuilder::PARAM_STR_ARRAY)))
->andWhere($delete->expr()->eq('appid', $delete->createNamedParameter('spreed')))
->executeStatement();
try {
$delete = $this->db->getQueryBuilder();
$delete->delete('notifications')
->where($delete->expr()->eq('app', $delete->createNamedParameter('spreed')))
->executeStatement();
} catch (\Throwable $e) {
// Ignore
}
$delete = $this->db->getQueryBuilder();
$delete->delete('calendarobjects')->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('calendarobjects_props')->executeStatement();
return new DataResponse();
}
public function ageChat(string $token, int $hours): DataResponse {
$query = $this->db->getQueryBuilder();
$query->select('id', 'last_activity', 'object_id')
->from('talk_rooms')
->where($query->expr()->eq('token', $query->createNamedParameter($token)));
$result = $query->executeQuery();
$room = $result->fetch();
$result->closeCursor();
if (!$room) {
return new DataResponse(null, Http::STATUS_NOT_FOUND);
}
$roomId = (int)$room['id'];
$lastActivity = new \DateTime($room['last_activity']);
$lastActivity->sub(new \DateInterval('PT' . $hours . 'H'));
$update = $this->db->getQueryBuilder();
$update->update('talk_rooms')
->set('last_activity', $update->createNamedParameter($lastActivity, IQueryBuilder::PARAM_DATETIME_MUTABLE))
->where($update->expr()->eq('id', $update->createNamedParameter($roomId)));
$eventConversationObjectId = explode('#', $room['object_id']);
if (count($eventConversationObjectId) === 2) {
$eventConversationObjectId[0] -= $hours * 3600;
$eventConversationObjectId[1] -= $hours * 3600;
$update->set('object_id', $update->createNamedParameter(implode('#', $eventConversationObjectId)));
}
$update->executeStatement();
$update = $this->db->getQueryBuilder();
$update->update('comments')
->set('creation_timestamp', $update->createParameter('creation_timestamp'))
->set('expire_date', $update->createParameter('expire_date'))
->set('meta_data', $update->createParameter('meta_data'))
->where($update->expr()->eq('id', $update->createParameter('id')));
$query = $this->db->getQueryBuilder();
$query->select('id', 'creation_timestamp', 'expire_date', 'meta_data')
->from('comments')
->where($query->expr()->eq('object_type', $query->createNamedParameter('chat')))
->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($roomId)));
$result = $query->executeQuery();
while ($row = $result->fetch()) {
$creationTimestamp = new \DateTime($row['creation_timestamp']);
$creationTimestamp->sub(new \DateInterval('PT' . $hours . 'H'));
$expireDate = null;
if ($row['expire_date']) {
$expireDate = new \DateTime($row['expire_date']);
$expireDate->sub(new \DateInterval('PT' . $hours . 'H'));
}
$metaData = 'null';
if ($row['meta_data'] !== 'null') {
$metaData = json_decode($row['meta_data'], true);
if (isset($metaData['last_edited_time'])) {
$metaData['last_edited_time'] -= $hours * 3600;
}
$metaData = json_encode($metaData);
}
$update->setParameter('id', $row['id']);
$update->setParameter('creation_timestamp', $creationTimestamp, IQueryBuilder::PARAM_DATE);
$update->setParameter('expire_date', $expireDate, IQueryBuilder::PARAM_DATE);
$update->setParameter('meta_data', $metaData);
$update->executeStatement();
}
$result->closeCursor();
return new DataResponse();
}
public function forgedFederationLeave(string $token, string $actingUser, string $targetUser): DataResponse {
$query = $this->db->getQueryBuilder();
$query->select('id')
->from('talk_rooms')
->where($query->expr()->eq('token', $query->createNamedParameter($token)));
$result = $query->executeQuery();
$roomId = (int)$result->fetchOne();
$result->closeCursor();
$query = $this->db->getQueryBuilder();
$query->select('access_token', 'invited_cloud_id')
->from('talk_attendees')
->where($query->expr()->eq('actor_id', $query->createNamedParameter($actingUser)))
->andWhere($query->expr()->eq('actor_type', $query->createNamedParameter(Attendee::ACTOR_USERS)))
->andWhere($query->expr()->eq('room_id', $query->createNamedParameter($roomId)));
$result = $query->executeQuery();
$access = $result->fetch();
$result->closeCursor();
$query = $this->db->getQueryBuilder();
$query->select('s.session_id')
->from('talk_attendees', 'a')
->leftJoin('a', 'talk_sessions', 's', $query->expr()->eq('a.id', 's.attendee_id'))
->where($query->expr()->eq('actor_id', $query->createNamedParameter($targetUser)))
->andWhere($query->expr()->eq('actor_type', $query->createNamedParameter(Attendee::ACTOR_USERS)))
->andWhere($query->expr()->eq('room_id', $query->createNamedParameter($roomId)));
$result = $query->executeQuery();
$session = $result->fetch();
$result->closeCursor();
return new DataResponse([
'access' => $access,
'session' => $session,
]);
}
#[NoAdminRequired]
public function createDashboardEvents(string $name, string $location): DataResponse {
if ($this->userId === null) {
return new DataResponse(null, Http::STATUS_UNAUTHORIZED);
}
$calendar = null;
$calendars = $this->calendarManager->getCalendarsForPrincipal('principals/users/' . $this->userId);
foreach ($calendars as $c) {
if ($c instanceof ICreateFromString) {
$calendar = $c;
}
}
if ($calendar === null) {
return new DataResponse(null, Http::STATUS_NOT_FOUND);
}
$start = time();
$end = time() + 3600;
$events = EventGenerator::generateEvents($name, $location, $start, $end);
foreach ($events as $event) {
try {
/** @var ICreateFromString $calendar */
$calendar->createFromString(random_int(0, 10000) . '.ics', $event);
} catch (CalendarException) {
return new DataResponse(null, Http::STATUS_FORBIDDEN);
}
}
return new DataResponse();
}
#[NoAdminRequired]
public function createEventAndInviteParticipant(string $organizer, string $attendee): DataResponse {
if ($this->userId === null) {
return new DataResponse(null, Http::STATUS_UNAUTHORIZED);
}
$calendar = null;
// Create a calendar event with LOCATION and time via OCP
$calendars = $this->calendarManager->getCalendarsForPrincipal('principals/users/' . $this->userId);
foreach ($calendars as $c) {
if ($c instanceof ICreateFromString) {
$calendar = $c;
}
}
if ($calendar === null) {
return new DataResponse(null, Http::STATUS_NOT_FOUND);
}
$start = time();
$end = $start + 3600;
$startTime = (new \DateTime())->setTimestamp($start);
$endTime = (new \DateTime())->setTimestamp($end);
for ($i = 0; $i < 3; $i++) {
$interval = new \DateInterval('PT2H');
$startTime->add($interval);
$endTime->add($interval);
$vCalendar = new VCalendar();
$vevent = $vCalendar->createComponent('VEVENT');
$vevent->add('UID', UUIDUtil::getUUID());
$vevent->add('DTSTART');
$vevent->DTSTART->setDateTime($startTime);
$vevent->add('DTEND');
$vevent->DTEND->setDateTime($endTime);
$vevent->add('SUMMARY', 'Test');
$vevent->add('DESCRIPTION', 'Test');
$vevent->add('ORGANIZER', 'mailto:' . $organizer . '@example.tld', ['CN' => $organizer]);
$vevent->add('ATTENDEE', 'mailto:' . $attendee . '@example.tld', [
'CN' => $attendee,
'CUTYPE' => 'INDIVIDUAL',
'PARTSTAT' => 'NEEDS-ACTION',
'ROLE' => 'REQ-PARTICIPANT',
'RSVP' => 'TRUE'
]);
$vevent->add('STATUS', 'CONFIRMED');
$vCalendar->add($vevent);
if ($i === 1) {
$vevent->add('ATTACH', '/index.php/f/3226', [
'FMTTYPE' => 'application/pdf',
'FILENAME' => 'test.pdf',
]);
}
if ($i === 2) {
$vevent->add('ATTACH', '/index.php/f/3226', [
'FMTTYPE' => 'application/pdf',
'FILENAME' => 'test.pdf',
'X-NC-FILE-ID' => '3226',
'X-NC-HAS-PREVIEW' => 'false'
]);
}
$cal = $vCalendar->serialize();
try {
/** @var ICreateFromString $calendar */
$calendar->createFromString(random_int(0, 10000) . '.ics', $cal);
} catch (CalendarException) {
return new DataResponse(null, Http::STATUS_FORBIDDEN);
}
}
return new DataResponse();
}
}
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\SpreedCheats;
use OCP\Config\BeforePreferenceDeletedEvent;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
/**
* @template-implements IEventListener<Event>
*/
class PreferenceListener implements IEventListener {
public function handle(Event $event): void {
if (!$event instanceof BeforePreferenceDeletedEvent) {
return;
}
if ($event->getAppId() === 'spreed') {
$event->setValid(true);
}
}
}
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\SpreedCheats\SpeechToText;
use OCP\Files\File;
use OCP\SpeechToText\ISpeechToTextProvider;
class LoremIpsumSpeechToTextProvider implements ISpeechToTextProvider {
public function getName(): string {
return 'Lorem ipsum - Talk Integrationtests';
}
public function transcribeFile(File $file): string {
if (str_contains($file->getName(), 'leave')) {
throw new \RuntimeException('Transcription failed by name');
}
sleep(1); // make sure Postgres manages the order of the messages
return 'Lorem ipsum';
}
}
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\SpreedCheats\Translation;
use OCP\Translation\ITranslationProvider;
use OCP\Translation\LanguageTuple;
class LoremIpsumTranslationProvider implements ITranslationProvider {
public function getName(): string {
return 'Lorem ipsum - Talk Integrationtests';
}
public function getAvailableLanguages(): array {
return [
new LanguageTuple(
'en',
'English',
'lorem',
'Lorem ipsum',
)
];
}
public function translate(?string $fromLanguage, string $toLanguage, string $text): string {
return 'Lorem ipsum';
}
}