migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018, Morris Jobke <hey@morrisjobke.de>
*
* @author Morris Jobke <hey@morrisjobke.de>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Support\AppInfo;
use OCA\Support\Notification\Notifier;
use OCA\Support\Settings\Admin;
use OCA\Support\Settings\Section;
use OCA\Support\Subscription\SubscriptionAdapter;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\IConfig;
use OCP\Notification\IManager;
use OCP\Settings\IManager as ISettingsManager;
use OCP\Support\Subscription\Exception\AlreadyRegisteredException;
use OCP\Support\Subscription\IRegistry;
use Psr\Log\LoggerInterface;
class Application extends App implements IBootstrap {
public const APP_ID = 'support';
public function __construct() {
parent::__construct(self::APP_ID);
}
public function register(IRegistrationContext $context): void {
}
public function boot(IBootContext $context): void {
$container = $context->getAppContainer();
/* @var $registry IRegistry */
$registry = $container->get(IRegistry::class);
try {
$registry->registerService(SubscriptionAdapter::class);
if ($container->get(IConfig::class)->getAppValue('support', 'hide-app', 'no') !== 'yes') {
$settingsManager = $container->get(ISettingsManager::class);
$settingsManager->registerSetting('admin', Admin::class);
$settingsManager->registerSection('admin', Section::class);
}
} catch (AlreadyRegisteredException $e) {
$logger = $container->get(LoggerInterface::class);
$logger->critical('Multiple subscription adapters are registered.', [
'exception' => $e,
]);
}
$context->injectFn(\Closure::fromCallable([$this, 'registerNotifier']));
}
public function registerNotifier(IManager $notificationsManager) {
$notificationsManager->registerNotifierService(Notifier::class);
}
}
@@ -0,0 +1,50 @@
<?php
/**
* @copyright Copyright (c) 2018 Morris Jobke <hey@morrisjobke.de>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Support\BackgroundJobs;
use OCA\Support\Service\SubscriptionService;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
use OCP\IConfig;
class CheckSubscription extends TimedJob {
private IConfig $config;
private SubscriptionService $subscriptionService;
public function __construct(IConfig $config, SubscriptionService $subscriptionService, ITimeFactory $factory) {
parent::__construct($factory);
// Run every 5 minutes
$this->setInterval(60 * 5);
$this->config = $config;
$this->subscriptionService = $subscriptionService;
}
public function run($argument) {
$lastCheck = $this->config->getAppValue('support', 'last_check', 0);
// renew subscription info every 23h
if (time() - $lastCheck > 23 * 60 * 60) {
$this->subscriptionService->renewSubscriptionInfo(false);
$this->subscriptionService->checkSubscription();
}
}
}
@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Support\Command;
use OCA\Support\DetailManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class SystemReport extends Command {
private DetailManager $detailManager;
public function __construct(DetailManager $detailManager) {
parent::__construct();
$this->detailManager = $detailManager;
}
protected function configure(): void {
$this
->setName('support:report')
->setDescription('Generate a system report')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$output->writeln($this->detailManager->getRenderedDetails());
return 0;
}
}
@@ -0,0 +1,175 @@
<?php
/**
* @copyright Copyright (c) 2018 Morris Jobke <hey@morrisjobke.de>
*
* @author Morris Jobke <hey@morrisjobke.de>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Support\Controller;
use OC\AppFramework\Http;
use OCA\Support\DetailManager;
use OCA\Support\Sections\ServerSection;
use OCA\Support\Service\SubscriptionService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\Constants;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IL10N;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserSession;
use OCP\Security\Events\GenerateSecurePasswordEvent;
use OCP\Security\ISecureRandom;
use OCP\Share\IManager;
use Psr\Log\LoggerInterface;
class ApiController extends Controller {
private IURLGenerator $urlGenerator;
private SubscriptionService $subscriptionService;
private ServerSection $serverSection;
private LoggerInterface $logger;
private IL10N $l10n;
private IManager $shareManager;
private Folder $userFolder;
private ISecureRandom $random;
private string $userId;
private IEventDispatcher $eventDispatcher;
public function __construct(
$appName,
IRequest $request,
IURLGenerator $urlGenerator,
SubscriptionService $subscriptionService,
IRootFolder $rootFolder,
IUserSession $userSession,
LoggerInterface $logger,
IL10N $l10n,
IManager $shareManager,
IEventDispatcher $eventDispatcher,
ISecureRandom $random
) {
parent::__construct($appName, $request);
$this->urlGenerator = $urlGenerator;
$this->subscriptionService = $subscriptionService;
$this->logger = $logger;
$this->l10n = $l10n;
$this->shareManager = $shareManager;
$this->random = $random;
$this->userId = $userSession->getUser()->getUID();
$this->eventDispatcher = $eventDispatcher;
$this->userFolder = $rootFolder->getUserFolder($this->userId);
}
/**
* @AuthorizedAdminSetting(settings=OCA\Support\Settings\Admin)
*/
public function setSubscriptionKey(string $subscriptionKey) {
$this->subscriptionService->setSubscriptionKey(trim($subscriptionKey));
return new RedirectResponse($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'support'])));
}
/**
* @AuthorizedAdminSetting(settings=OCA\Support\Settings\Admin)
*/
public function generateSystemReport() {
try {
$directory = $this->userFolder->get('System information');
} catch (NotFoundException $e) {
try {
$directory = $this->userFolder->newFolder('System information');
} catch (\Exception $ex) {
$this->logger->warning('Could not create folder "System information" to store generated report.', [
'app' => 'support',
'exception' => $e,
]);
$response = new DataResponse(['message' => $this->l10n->t('Could not create folder "System information" to store generated report.')]);
$response->setStatus(Http::STATUS_INTERNAL_SERVER_ERROR);
return $response;
}
}
$date = (new \DateTime())->format('Y-m-d');
$filename = $date . '.md';
$filename = $directory->getNonExistingName($filename);
try {
$file = $directory->newFile($filename);
$detailManager = \OC::$server->get(DetailManager::class);
$details = $detailManager->getRenderedDetails();
$file->putContent($details);
} catch (\Exception $e) {
$this->logger->warning('Could not create file "' . $filename . '" to store generated report.', [
'app' => 'support',
'exception' => $e,
]);
$response = new DataResponse(['message' => $this->l10n->t('Could not create file "%s" to store generated report.', [ $filename ])]);
$response->setStatus(Http::STATUS_INTERNAL_SERVER_ERROR);
return $response;
}
try {
$passwordEvent = new GenerateSecurePasswordEvent();
$this->eventDispatcher->dispatchTyped($passwordEvent);
$password = $passwordEvent->getPassword() ?? $this->random->generate(20);
$share = $this->shareManager->newShare();
$share->setNode($file);
$share->setPermissions(Constants::PERMISSION_READ);
$share->setShareType(\OC\Share\Constants::SHARE_TYPE_LINK);
$share->setSharedBy($this->userId);
$share->setPassword($password);
if ($this->shareManager->shareApiLinkDefaultExpireDateEnforced()) {
$expiry = new \DateTime();
$expiry->add(new \DateInterval('P' . $this->shareManager->shareApiLinkDefaultExpireDays() . 'D'));
} else {
$expiry = new \DateTime();
$expiry->add(new \DateInterval('P2W'));
}
$share->setExpirationDate($expiry);
$share = $this->shareManager->createShare($share);
} catch (\Exception $e) {
$this->logger->warning('Could not share file "' . $filename . '".', [
'app' => 'support',
'exception' => $e,
]);
$response = new DataResponse(['message' => $this->l10n->t('Could not share file "%s". Nevertheless, you can find it in the folder "System information".', [$filename])]);
$response->setStatus(Http::STATUS_INTERNAL_SERVER_ERROR);
return $response;
}
return new DataResponse(
[
'link' => $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $share->getToken()]),
'password' => $password,
],
Http::STATUS_CREATED
);
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
/**
* @copyright Copyright (c) 2017 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Support;
class Detail implements IDetail {
private string $section;
private string $title;
private string $information;
private int $type;
public function __construct(string $section, string $title, string $information, int $type) {
$this->section = $section;
$this->title = $title;
$this->information = $information;
$this->type = $type;
}
public function getTitle(): string {
return $this->title;
}
public function getType(): string {
return $this->type;
}
public function getInformation(): string {
return $this->information;
}
public function getSection(): int {
return $this->section;
}
}
@@ -0,0 +1,104 @@
<?php
/**
* @copyright Copyright (c) 2017 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Support;
use OCA\Support\Sections\ServerSection;
class DetailManager {
private array $sections = [];
public function __construct(ServerSection $serverSection) {
// Register core details that are used in every report
$this->addSection($serverSection);
}
public function createSection(string $identifier, string $title, int $order = 0): void {
$section = new Section($identifier, $title, $order);
$this->addSection($section);
}
public function addSection(ISection $section): void {
if (array_key_exists($section->getIdentifier(), $this->sections)) {
/** @var ISection $existing */
$existing = $this->sections[$section->getIdentifier()];
foreach ($section->getDetails() as $detail) {
$existing->addDetail($detail);
}
return;
}
$this->sections[$section->getIdentifier()] = $section;
}
public function removeSection(string $section): void {
unset($this->sections[$section]);
}
public function createDetail(string $sectionIdentifier, string $title, string $information, int $type = IDetail::TYPE_MULTI_LINE_PREFORMAT): void {
$detail = new Detail($sectionIdentifier, $title, $information, $type);
/** @var ISection $sectionObject */
$sectionObject = $this->sections[$sectionIdentifier];
$sectionObject->addDetail($detail);
}
/**
* @return ISection[]
*/
public function getSections(): array {
return $this->sections;
}
public function getRenderedDetails(): string {
$result = '';
/** @var ISection $section */
foreach ($this->sections as $section) {
$result .= $this->renderSectionHeader($section);
/** @var IDetail $detail */
foreach ($section->getDetails() as $detail) {
$result .= $this->renderDetail($detail);
}
}
return $result;
}
private function renderSectionHeader(ISection $section): string {
return '## ' . $section->getTitle() . "\n\n";
}
private function renderDetail(IDetail $detail): string {
switch ($detail->getType()) {
case IDetail::TYPE_SINGLE_LINE:
return '**' . $detail->getTitle() . ':** ' . $detail->getInformation() . "\n\n";
case IDetail::TYPE_MULTI_LINE:
return '**' . $detail->getTitle() . ":** \n\n" . $detail->getInformation() . "\n\n";
case IDetail::TYPE_MULTI_LINE_PREFORMAT:
return '**' . $detail->getTitle() . ":** \n\n``` \n" . $detail->getInformation() . "\n```\n\n";
case IDetail::TYPE_COLLAPSIBLE:
return '<details><summary>' . $detail->getTitle() . "</summary>\n\n" . $detail->getInformation() . "\n</details>\n\n";
case IDetail::TYPE_COLLAPSIBLE_PREFORMAT:
return '<details><summary>' . $detail->getTitle() . "</summary>\n\n```\n" . $detail->getInformation() . "\n```\n</details>\n\n";
default:
return '**' . $detail->getTitle() . ':** ' . $detail->getInformation() . "\n\n";
}
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
/**
* @copyright Copyright (c) 2017 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Support;
interface IDetail {
public const TYPE_SINGLE_LINE = 0;
public const TYPE_MULTI_LINE = 1;
public const TYPE_MULTI_LINE_PREFORMAT = 2;
public const TYPE_COLLAPSIBLE = 3;
public const TYPE_COLLAPSIBLE_PREFORMAT = 4;
public function getTitle(): string;
public function getType(): string;
public function getInformation(): string;
public function getSection(): int;
}
@@ -0,0 +1,40 @@
<?php
/**
* @copyright Copyright (c) 2017 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Support;
/**
* Interface ISection
*
* @package OCA\IssueTemplate
*/
interface ISection {
public function getIdentifier(): string;
public function getTitle(): string;
public function addDetail(IDetail $details): void;
/**
* @return IDetail[]
*/
public function getDetails(): array;
}
@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018, Morris Jobke <hey@morrisjobke.de>
*
* @author Morris Jobke <hey@morrisjobke.de>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Support\Notification;
use OCA\Support\AppInfo\Application;
use OCP\IConfig;
use OCP\IURLGenerator;
use OCP\L10N\IFactory;
use OCP\Notification\IManager;
use OCP\Notification\INotification;
use OCP\Notification\INotifier;
class Notifier implements INotifier {
protected IURLGenerator $url;
protected IConfig $config;
protected IManager $notificationManager;
protected IFactory $l10nFactory;
public function __construct(IURLGenerator $url, IConfig $config, IManager $notificationManager, IFactory $l10nFactory) {
$this->url = $url;
$this->notificationManager = $notificationManager;
$this->config = $config;
$this->l10nFactory = $l10nFactory;
}
public function getID(): string {
return 'support';
}
public function getName(): string {
return $this->l10nFactory->get(Application::APP_ID)->t('Subscription notifications');
}
/**
* @param INotification $notification
* @param string $languageCode The code of the language that should be used to prepare the notification
* @return INotification
* @throws \InvalidArgumentException When the notification was not prepared by a notifier
* @since 9.0.0
*/
public function prepare(INotification $notification, string $languageCode): INotification {
if ($notification->getApp() !== 'support') {
throw new \InvalidArgumentException('Unknown app id');
}
$l = $this->l10nFactory->get('support', $languageCode);
switch ($notification->getSubject()) {
case 'subscription_info':
$notification->setParsedSubject($l->t('Nextcloud Subscription'))
->setParsedMessage($l->t('Your server has no Nextcloud Subscription or your Subscription has expired.'));
$notification->setIcon($this->url->getAbsoluteURL($this->url->imagePath('support', 'notification.svg')));
return $notification;
case 'subscription_over_limit':
$notification->setParsedSubject($l->t('Nextcloud Subscription'))
->setParsedMessage($l->t('Your Nextcloud server subscription does not cover your number of users.'));
$notification->setIcon($this->url->getAbsoluteURL($this->url->imagePath('support', 'notification.svg')));
return $notification;
case 'subscription_expired':
$notification->setParsedSubject($l->t('Nextcloud Subscription'))
->setParsedMessage($l->t('Your Nextcloud Subscription has expired!'));
$notification->setIcon($this->url->getAbsoluteURL($this->url->imagePath('support', 'notification.svg')));
return $notification;
default:
// Unknown subject => Unknown notification => throw
throw new \InvalidArgumentException();
}
}
}
@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Morris Jobke <hey@morrisjobke.de>
*
* @author Morris Jobke <hey@morrisjobke.de>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Support\Repair;
use OCP\IConfig;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
use OCP\Support\Subscription\IRegistry;
class SwitchUpdaterServer implements IRepairStep {
private IConfig $config;
private IRegistry $subscriptionRegistry;
public function __construct(IConfig $config, IRegistry $subscriptionRegistry) {
$this->config = $config;
$this->subscriptionRegistry = $subscriptionRegistry;
}
public function getName(): string {
return 'Switches from default updater server to the customer one if a valid subscription is available';
}
public function run(IOutput $output): void {
if ($this->config->getAppValue('support', 'SwitchUpdaterServerHasRun') === 'yes') {
$output->info('Repair step already executed');
return;
}
$currentUpdaterServer = $this->config->getSystemValue('updater.server.url', 'https://updates.nextcloud.com/updater_server/');
$subscriptionKey = $this->config->getAppValue('support', 'subscription_key', '');
/**
* only overwrite the updater server if:
* - it is the default one
* - there is a valid subscription
* - there is a subscription key set
* - the subscription key is halfway sane
*/
if ($currentUpdaterServer === 'https://updates.nextcloud.com/updater_server/' &&
$this->subscriptionRegistry->delegateHasValidSubscription() &&
$subscriptionKey !== '' &&
preg_match('!^[a-zA-Z0-9-]{10,250}$!', $subscriptionKey)
) {
$this->config->setSystemValue('updater.server.url', 'https://updates.nextcloud.com/customers/' . $subscriptionKey . '/');
}
// if everything is done, no need to redo the repair during next upgrade
$this->config->setAppValue('support', 'SwitchUpdaterServerHasRun', 'yes');
}
}
+59
View File
@@ -0,0 +1,59 @@
<?php
/**
* @copyright Copyright (c) 2017 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Support;
class Section implements ISection {
private string $identifier;
private string $title;
/** @var IDetail[] */
private array $details = [];
public function __construct(string $identifier, string $title, int $order = 0) {
$this->identifier = $identifier;
$this->title = $title;
}
public function getIdentifier(): string {
return $this->identifier;
}
public function getTitle(): string {
return $this->title;
}
public function addDetail(IDetail $details): void {
$this->details[] = $details;
}
/** @inheritdoc */
public function getDetails(): array {
return $this->details;
}
public function createDetail(string $title, string $information, int $type = IDetail::TYPE_SINGLE_LINE): IDetail {
$detail = new Detail($this->getIdentifier(), $title, $information, $type);
$this->addDetail($detail);
return $detail;
}
}
@@ -0,0 +1,523 @@
<?php
/**
* @copyright Copyright (c) 2017 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Support\Sections;
use OC\DB\Exceptions\DbalException;
use OC\IntegrityCheck\Checker;
use OC\SystemConfig;
use OCA\Files_External\Lib\StorageConfig;
use OCA\Files_External\Service\GlobalStoragesService;
use OCA\Support\IDetail;
use OCA\Support\Section;
use OCA\User_LDAP\Configuration;
use OCA\User_LDAP\Helper;
use OCP\App\IAppManager;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IUserManager;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Output\BufferedOutput;
class ServerSection extends Section {
private IConfig $config;
private Checker $checker;
private IAppManager $appManager;
private SystemConfig $systemConfig;
private IDBConnection $connection;
private IClientService $clientService;
private IUserManager $userManager;
private LoggerInterface $logger;
public function __construct(
IConfig $config,
Checker $checker,
IAppManager $appManager,
IDBConnection $connection,
IClientService $clientService,
IUserManager $userManager,
LoggerInterface $logger,
SystemConfig $systemConfig,
) {
parent::__construct('server-detail', 'Server configuration detail');
$this->config = $config;
$this->checker = $checker;
$this->appManager = $appManager;
$this->systemConfig = $systemConfig;
$this->connection = $connection;
$this->clientService = $clientService;
$this->userManager = $userManager;
$this->logger = $logger;
$this->createDetail('Operating system', $this->getOsVersion());
$this->createDetail('Webserver', $this->getWebserver());
$this->createDetail('Database', $this->getDatabaseInfo());
$this->createDetail('PHP version', $this->getPhpVersion());
$this->createDetail('Nextcloud version', $this->getNextcloudVersion());
$this->createDetail('Updated from an older Nextcloud/ownCloud or fresh install', '');
$this->createDetail('Where did you install Nextcloud from', $this->getInstallMethod());
$this->createDetail('Signing status', $this->getIntegrityResults(), IDetail::TYPE_COLLAPSIBLE);
$this->createDetail('List of activated apps', $this->renderAppList(), IDetail::TYPE_COLLAPSIBLE_PREFORMAT);
$this->createDetail('Configuration (config/config.php)', print_r(json_encode($this->getConfig(), JSON_PRETTY_PRINT), true), IDetail::TYPE_COLLAPSIBLE_PREFORMAT);
$this->createDetail('Cron Configuration', print_r($this->getCronConfig(), true));
$externalStorageEnabled = $this->appManager->isEnabledForUser('files_external');
$this->createDetail('External storages', $externalStorageEnabled ? 'yes' : 'files_external is disabled');
if ($externalStorageEnabled) {
$this->createDetail('External storage configuration', $this->getExternalStorageInfo(), IDetail::TYPE_COLLAPSIBLE_PREFORMAT);
}
$this->createDetail('Encryption', $this->getEncryptionInfo());
$this->createDetail('User-backends', $this->getUserBackendInfo());
if ($this->isLDAPEnabled()) {
$this->createDetail('LDAP configuration', $this->getLDAPInfo(), IDetail::TYPE_COLLAPSIBLE_PREFORMAT);
}
if ($this->isTalkEnabled()) {
$this->createDetail('Talk configuration', $this->getTalkInfo());
}
$this->createDetail('Browser', $this->getBrowser());
}
private function getWebserver() {
return ($_SERVER['SERVER_SOFTWARE'] ?? 'Unknown') . ' (' . PHP_SAPI . ')';
}
private function getNextcloudVersion() {
return \OC_Util::getHumanVersion() . ' - ' . $this->config->getSystemValue('version');
}
private function getOsVersion() {
return function_exists('php_uname') ? php_uname('s') . ' ' . php_uname('r') . ' ' . php_uname('v') . ' ' . php_uname('m') : PHP_OS;
}
private function getPhpVersion() {
return PHP_VERSION . "\n\nModules loaded: " . implode(', ', get_loaded_extensions());
}
protected function getDatabaseInfo() {
return $this->config->getSystemValue('dbtype') . ' ' . $this->getDatabaseVersion();
}
/**
* original source from nextcloud/survey_client
* @link https://github.com/nextcloud/survey_client/blob/master/lib/Categories/Database.php#L80-L107
*
* @copyright Copyright (c) 2016, ownCloud, Inc.
* @author Joas Schilling <coding@schilljs.com>
* @license AGPL-3.0
*/
private function getDatabaseVersion() {
switch ($this->config->getSystemValue('dbtype')) {
case 'sqlite':
case 'sqlite3':
$sql = 'SELECT sqlite_version() AS version';
break;
case 'oci':
$sql = 'SELECT VERSION FROM PRODUCT_COMPONENT_VERSION';
break;
case 'mysql':
case 'pgsql':
default:
$sql = 'SELECT VERSION() AS version';
break;
}
try {
$result = $this->connection->executeQuery($sql);
$version = $result->fetchColumn();
$result->closeCursor();
if ($version) {
return $this->cleanVersion($version);
}
} catch (DBALException $e) {
$this->logger->debug('Unable to determine database version', [
'exception' => $e
]);
}
return 'N/A';
}
/**
* Try to strip away additional information
*
* @copyright Copyright (c) 2016, ownCloud, Inc.
* @author Joas Schilling <coding@schilljs.com>
* @license AGPL-3.0
*
* @param string $version E.g. `5.6.27-0ubuntu0.14.04.1`
* @return string `5.6.27`
*/
protected function cleanVersion(string $version): string {
$matches = [];
preg_match('/^(\d+)(\.\d+)(\.\d+)/', $version, $matches);
if (isset($matches[0])) {
return $matches[0];
}
return $version;
}
/**
* @return array{backgroundjobs_mode: string, lastcron: string}
*/
private function getCronConfig(): array {
return [
'backgroundjobs_mode' => $this->config->getAppValue('core', 'backgroundjobs_mode', 'ajax'),
'lastcron' => $this->config->getAppValue('core', 'lastcron', 'never'),
];
}
private function getIntegrityResults(): string {
if (!$this->checker->isCodeCheckEnforced()) {
return 'Integrity checker has been disabled. Integrity cannot be verified.';
}
return print_r(json_encode($this->checker->getResults(), JSON_PRETTY_PRINT), true);
}
private function getInstallMethod(): string {
$base = \OC::$SERVERROOT;
if (file_exists($base . '/.git')) {
return 'git';
}
return 'unknown';
}
private function renderAppList() {
$apps = $this->getAppList();
$result = "Enabled:\n";
foreach ($apps['enabled'] as $name => $version) {
$result .= ' - ' . $name . ': ' . $version . "\n";
}
$result .= "Disabled:\n";
foreach ($apps['disabled'] as $name => $version) {
if ($version) {
$result .= ' - ' . $name . ': ' . $version . "\n";
} else {
$result .= ' - ' . $name . "\n";
}
}
return $result;
}
/**
* @return string[][]
*/
private function getAppList() {
$apps = \OC_App::getAllApps();
$enabledApps = $disabledApps = [];
$versions = \OC_App::getAppVersions();
//sort enabled apps above disabled apps
foreach ($apps as $app) {
if ($this->appManager->isInstalled($app)) {
$enabledApps[] = $app;
} else {
$disabledApps[] = $app;
}
}
$apps = ['enabled' => [], 'disabled' => []];
sort($enabledApps);
foreach ($enabledApps as $app) {
$apps['enabled'][$app] = $versions[$app] ?? true;
}
sort($disabledApps);
foreach ($disabledApps as $app) {
$apps['disabled'][$app] = $versions[$app] ?? false;
}
return $apps;
}
protected function getEncryptionInfo() {
return $this->config->getAppValue('core', 'encryption_enabled', 'no');
}
protected function getExternalStorageInfo() {
$globalService = \OC::$server->query(GlobalStoragesService::class);
$mounts = $globalService->getStorageForAllUsers();
// copy of OCA\Files_External\Command\ListCommand::listMounts
if ($mounts === null || count($mounts) === 0) {
return 'No mounts configured';
}
$headers = ['Mount ID', 'Mount Point', 'Storage', 'Authentication Type', 'Configuration', 'Options'];
$headers[] = 'Applicable Users';
$headers[] = 'Applicable Groups';
$headers[] = 'Type';
$hideKeys = ['password', 'refresh_token', 'token', 'client_secret', 'public_key', 'private_key', 'key', 'secret'];
/** @var StorageConfig $mount */
foreach ($mounts as $mount) {
$config = $mount->getBackendOptions();
foreach ($config as $key => $value) {
if (in_array($key, $hideKeys)) {
$mount->setBackendOption($key, '***');
}
}
}
$defaultMountOptions = [
'encrypt' => true,
'previews' => true,
'filesystem_check_changes' => 1,
'enable_sharing' => false,
'encoding_compatibility' => false,
'readonly' => false,
];
$rows = array_map(function (StorageConfig $config) use ($defaultMountOptions) {
$storageConfig = $config->getBackendOptions();
$keys = array_keys($storageConfig);
$values = array_values($storageConfig);
$configStrings = array_map(function ($key, $value) {
return $key . ': ' . json_encode($value);
}, $keys, $values);
$configString = implode(', ', $configStrings);
$mountOptions = $config->getMountOptions();
// hide defaults
foreach ($mountOptions as $key => $value) {
if (isset($defaultMountOptions[$key]) && ($value === $defaultMountOptions[$key])) {
unset($mountOptions[$key]);
}
}
$keys = array_keys($mountOptions);
$values = array_values($mountOptions);
$optionsStrings = array_map(function ($key, $value) {
return $key . ': ' . json_encode($value);
}, $keys, $values);
$optionsString = implode(', ', $optionsStrings);
$values = [
$config->getId(),
$config->getMountPoint(),
$config->getBackend()->getText(),
$config->getAuthMechanism()->getText(),
$configString,
$optionsString
];
$applicableUsers = implode(', ', $config->getApplicableUsers());
$applicableGroups = implode(', ', $config->getApplicableGroups());
if ($applicableUsers === '' && $applicableGroups === '') {
$applicableUsers = 'All';
}
$values[] = $applicableUsers;
$values[] = $applicableGroups;
$values[] = $config->getType() === StorageConfig::MOUNT_TYPE_ADMIN ? 'Admin' : 'Personal';
return $values;
}, $mounts);
$output = new BufferedOutput();
$table = new Table($output);
$table->setHeaders($headers);
$table->setRows($rows);
$table->render();
return $output->fetch();
}
private function getConfig() {
$keys = $this->systemConfig->getKeys();
$configs = [];
foreach ($keys as $key) {
$value = $this->systemConfig->getFilteredValue($key, serialize(null));
if ($value !== 'N;') {
$configs[$key] = $value;
}
}
return $configs;
}
private function getBrowser(): string {
return $_SERVER['HTTP_USER_AGENT'] ?? 'unknown';
}
private function getUserBackendInfo() {
$backends = $this->userManager->getBackends();
$output = PHP_EOL;
foreach ($backends as $backend) {
$output .= ' * ' . get_class($backend) . PHP_EOL;
}
return $output;
}
private function isLDAPEnabled() {
$backends = $this->userManager->getBackends();
foreach ($backends as $backend) {
if ($backend instanceof \OCA\User_LDAP\User_Proxy) {
return true;
}
}
return false;
}
private function isTalkEnabled() {
return $this->appManager->isEnabledForUser('spreed');
}
private function getTalkInfo() {
$output = PHP_EOL;
$config = $this->config->getAppValue('spreed', 'stun_servers');
$servers = json_decode($config, true);
$output .= PHP_EOL;
$output .= 'STUN servers' . PHP_EOL;
if (empty($servers)) {
$output .= ' * no custom server configured' . PHP_EOL;
} else {
foreach ($servers as $server) {
$output .= ' * ' . $server . PHP_EOL;
}
}
$config = $this->config->getAppValue('spreed', 'turn_servers');
$servers = json_decode($config, true);
$output .= PHP_EOL;
$output .= 'TURN servers' . PHP_EOL;
if (empty($servers)) {
$output .= ' * no custom server configured' . PHP_EOL;
} else {
foreach ($servers as $server) {
$output .= ' * ' . ($server['schemes'] ?? 'turn') . ':' . $server['server'] . ' - ' . $server['protocols'] . PHP_EOL;
}
}
$config = $this->config->getAppValue('spreed', 'signaling_mode', 'default');
$output .= PHP_EOL;
$output .= 'Signaling servers (mode: ' . $config . '):' . PHP_EOL;
if ($this->config->getAppValue('spreed', 'sip_bridge_shared_secret') !== '') {
$output .= ' * SIP dialin is enabled' . PHP_EOL;
} else {
$output .= ' * SIP dialin is disabled' . PHP_EOL;
}
if ($this->config->getAppValue('spreed', 'sip_dialout', 'no') !== 'no') {
$output .= ' * SIP dialout is enabled' . PHP_EOL;
} else {
$output .= ' * SIP dialout is disabled' . PHP_EOL;
}
$config = $this->config->getAppValue('spreed', 'signaling_servers');
$servers = json_decode($config, true);
if (empty($servers['servers'])) {
$output .= ' * no custom server configured' . PHP_EOL;
} else {
foreach ($servers['servers'] as $server) {
$output .= ' * ' . $server['server'] . ' - ' . $this->getHPBVersion($server['server']) . PHP_EOL;
}
}
$output .= PHP_EOL;
$output .= 'Recording servers:' . PHP_EOL;
if ($this->config->getAppValue('spreed', 'call_recording', 'yes') !== 'yes') {
$output .= ' * Recording is disabled' . PHP_EOL;
} else {
$output .= ' * Recording is enabled' . PHP_EOL;
}
$output .= ' * Recording consent is set to "' . $this->config->getAppValue('spreed', 'recording_consent', 'default') . '"' . PHP_EOL;
$config = $this->config->getAppValue('spreed', 'recording_servers');
$servers = json_decode($config, true);
if (empty($servers['servers'])) {
$output .= ' * no recording server configured' . PHP_EOL;
} else {
foreach ($servers['servers'] as $server) {
$output .= ' * ' . $server['server'] . ' - ' . $this->getHPBVersion($server['server']) . PHP_EOL;
}
}
return $output;
}
private function getHPBVersion(string $url): string {
$url = rtrim($url, '/');
if (strpos($url, 'wss://') === 0) {
$url = 'https://' . substr($url, 6);
}
if (strpos($url, 'ws://') === 0) {
$url = 'http://' . substr($url, 5);
}
$client = $this->clientService->newClient();
try {
$response = $client->get($url . '/api/v1/welcome', [
'verify' => false,
'nextcloud' => [
'allow_local_address' => true,
],
]);
$body = $response->getBody();
$data = json_decode($body, true);
if (!is_array($data) || !isset($data['version'])) {
return 'error';
}
return $data['version'];
} catch (\Exception $e) {
return 'error: ' . $e->getMessage();
}
}
private function getLDAPInfo() {
/** @var Helper $helper */
$helper = \OC::$server->query(Helper::class);
$output = new BufferedOutput();
// copy of OCA\User_LDAP\Command\ShowConfig::renderConfigs
$configIDs = $helper->getServerConfigurationPrefixes();
foreach ($configIDs as $id) {
$configHolder = new Configuration($id);
$configuration = $configHolder->getConfiguration();
ksort($configuration);
$table = new Table($output);
$table->setHeaders(['Configuration', $id]);
$rows = [];
foreach ($configuration as $key => $value) {
if ($key === 'ldapAgentPassword') {
$value = '***';
}
if (is_array($value)) {
$value = implode(';', $value);
}
$rows[] = [$key, $value];
}
$table->setRows($rows);
$table->render();
}
return $output->fetch();
}
}
@@ -0,0 +1,684 @@
<?php
/**
* @copyright Copyright (c) 2018 Morris Jobke <hey@morrisjobke.de>
*
* @author Morris Jobke <hey@morrisjobke.de>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Support\Service;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use OC\User\Backend;
use OCP\Http\Client\IClientService;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\L10N\IFactory;
use OCP\Mail\IMailer;
use OCP\Notification\IManager;
use Psr\Log\LoggerInterface;
class SubscriptionService {
public const ERROR_FAILED_RETRY = 1;
public const ERROR_FAILED_INVALID = 2;
public const ERROR_NO_INTERNET_CONNECTION = 3;
public const ERROR_INVALID_SUBSCRIPTION_KEY = 4;
public const THRESHOLD_MEDIUM = 500;
public const THRESHOLD_LARGE = 1000;
private IConfig $config;
private IClientService $clientService;
private LoggerInterface $log;
private IUserManager $userManager;
private int $userCount = -1;
private int $activeUserCount = -1;
private IManager $notifications;
private IURLGenerator $urlGenerator;
private IGroupManager $groupManager;
private IMailer $mailer;
private IFactory $l10nFactory;
private $subscriptionInfoCache = null;
public function __construct(
IConfig $config,
IClientService $clientService,
LoggerInterface $log,
IUserManager $userManager,
IManager $notifications,
IURLGenerator $urlGenerator,
IGroupManager $groupManager,
IMailer $mailer,
IFactory $l10nFactory,
private ICacheFactory $cacheFactory,
) {
$this->config = $config;
$this->clientService = $clientService;
$this->log = $log;
$this->userManager = $userManager;
$this->notifications = $notifications;
$this->urlGenerator = $urlGenerator;
$this->groupManager = $groupManager;
$this->mailer = $mailer;
$this->l10nFactory = $l10nFactory;
}
public function setSubscriptionKey(string $subscriptionKey) {
if (!preg_match('!^[a-zA-Z0-9-]{10,250}$!', $subscriptionKey)) {
$this->config->setAppValue('support', 'last_error', self::ERROR_INVALID_SUBSCRIPTION_KEY);
return;
}
$this->config->setAppValue('support', 'potential_subscription_key', $subscriptionKey);
$this->config->deleteAppValue('support', 'last_error');
$this->renewSubscriptionInfo(true);
}
public function getUserCount(): int {
if ($this->userCount > 0) {
return $this->userCount;
}
$userCount = 0;
$backends = $this->userManager->getBackends();
foreach ($backends as $backend) {
if ($backend->implementsActions(Backend::COUNT_USERS)) {
try {
$backendUsers = $backend->countUsers();
} catch (\Exception $e) {
$backendUsers = false;
$this->log->error($e->getMessage(), ['exception' => $e]);
}
if ($backendUsers !== false) {
$userCount += $backendUsers;
} else {
// TODO what if the user count can't be determined?
$this->log->warning('Can not determine user count for ' . get_class($backend), ['app' => 'support']);
}
}
}
$disabledUsers = $this->config->getUsersForUserValue('core', 'enabled', 'false');
$disabledUsersCount = count($disabledUsers);
$this->userCount = $userCount - $disabledUsersCount;
if ($this->userCount < 0) {
$this->userCount = 0;
// TODO this should never happen
$this->log->warning("Total user count was negative (users: $userCount, disabled: $disabledUsersCount)", ['app' => 'support']);
}
return $this->userCount;
}
public function getActiveUserCount(): int {
if ($this->activeUserCount > 0) {
return $this->activeUserCount;
}
$this->activeUserCount = $this->userManager->countSeenUsers();
return $this->activeUserCount;
}
public function renewSubscriptionInfo(bool $fast) {
$hasInternetConnection = $this->config->getSystemValue('has_internet_connection', true);
if (!$hasInternetConnection) {
$this->config->setAppValue('support', 'last_error', self::ERROR_NO_INTERNET_CONNECTION);
return;
}
$subscriptionKey = $this->config->getAppValue('support', 'potential_subscription_key', '');
if (!preg_match('!^[a-zA-Z0-9-]{10,250}$!', $subscriptionKey)) {
// fallback to normal subscription key
$subscriptionKey = $this->config->getAppValue('support', 'subscription_key', '');
if (!preg_match('!^[a-zA-Z0-9-]{10,250}$!', $subscriptionKey)) {
return;
}
}
$backendURL = $this->config->getSystemValue('support.backend', 'https://cloud.nextcloud.com/');
$backendURL = rtrim($backendURL, '/') . '/apps/zammad_organisation_management/api/query/subscription/' . $subscriptionKey;
try {
$userCount = $this->getUserCount();
$activeUserCount = $this->userManager->countSeenUsers();
$httpClient = $this->clientService->newClient();
$response = $httpClient->post(
$backendURL,
[
'body' => [
'instanceId' => $this->config->getSystemValue('instanceid', ''),
'userCount' => $userCount,
'activeUserCount' => $activeUserCount,
'version' => implode('.', \OCP\Util::getVersion()),
],
'timeout' => $fast ? 10 : 30,
'connect_timeout' => $fast ? 3 : 30,
]
);
$body = json_decode($response->getBody(), true);
if ($response->getStatusCode() === 200 && is_array($body)) {
$this->log->info('Subscription info successfully fetched');
$this->config->setAppValue('support', 'subscription_key', $subscriptionKey);
$this->config->setAppValue('support', 'last_check', time());
$this->config->setAppValue('support', 'last_response', json_encode($body));
$this->config->deleteAppValue('support', 'last_error');
$currentUpdaterServer = $this->config->getSystemValue('updater.server.url', 'https://updates.nextcloud.com/updater_server/');
$newUpdaterServer = 'https://updates.nextcloud.com/customers/' . $subscriptionKey . '/';
/**
* only overwrite the updater server if:
* - it is the default one or another /.customers/ one
* - there is a valid subscription
* - there is a subscription key set
* - the subscription key is halfway sane
*/
if (
(
$currentUpdaterServer === 'https://updates.nextcloud.com/updater_server/' ||
substr($currentUpdaterServer, 0, 40) === 'https://updates.nextcloud.com/customers/'
) &&
$subscriptionKey !== '' &&
preg_match('!^[a-zA-Z0-9-]{10,250}$!', $subscriptionKey)
) {
$this->config->setSystemValue('updater.server.url', $newUpdaterServer);
}
// remove all pending notifications
$notification = $this->notifications->createNotification();
$notification->setApp('support')
->setSubject('subscription_info');
$this->notifications->markProcessed($notification);
// hide push fair use warning
$cacheNotifications = $this->cacheFactory->createDistributed('notifications');
$cacheNotifications->remove('push_fair_use');
return;
}
$this->log->info('Renewal of subscription info returned invalid data. URL: ' . $backendURL . ' Status: ' . $response->getStatusCode() . ' Body: ' . $response->getBody());
$error = self::ERROR_FAILED_RETRY;
} catch (ConnectException $e) {
$this->log->info('Renew of subscription info failed due to connect exception - retrying later. URL: ' . $backendURL, ['app' => 'support', 'exception' => $e]);
$error = self::ERROR_FAILED_RETRY;
} catch (RequestException $e) {
$response = $e->getResponse();
if ($response !== null && $response->getStatusCode() === 403) {
$this->log->info('Subscription key invalid');
$this->config->deleteAppValue('support', 'potential_subscription_key');
$error = self::ERROR_FAILED_INVALID;
} else {
$this->log->info('Renew of subscription info failed. URL: ' . $backendURL, ['app' => 'support', 'exception' => $e]);
$error = self::ERROR_FAILED_RETRY;
}
} catch (\Exception $e) {
$this->log->info('Renew of subscription info failed. URL: ' . $backendURL, ['app' => 'support', 'exception' => $e]);
$error = self::ERROR_FAILED_RETRY;
}
$this->config->setAppValue('support', 'last_error', $error);
}
public function getSubscriptionInfo(): array {
if ($this->subscriptionInfoCache !== null) {
return $this->subscriptionInfoCache;
}
$userCount = $this->getUserCount();
$activeUserCount = $this->getActiveUserCount();
$instanceSize = 'small';
if ($userCount > SubscriptionService::THRESHOLD_MEDIUM) {
if ($userCount > SubscriptionService::THRESHOLD_LARGE) {
$instanceSize = 'large';
} else {
$instanceSize = 'medium';
}
}
$subscriptionInfo = $this->getMinimalSubscriptionInfo();
$now = new \DateTime();
$subscriptionEndDate = new \DateTime($subscriptionInfo['endDate'] ?? 'now');
if ($now > $subscriptionEndDate) {
$years = 0;
$months = 0;
$days = 0;
} else {
$diff = $now->diff($subscriptionEndDate);
$years = (int)$diff->format('%y');
$months = $years * 12 + (int)$diff->format('%m');
$days = $months * 30 + (int)$diff->format('%d');
}
$hasSubscription = $subscriptionInfo !== null;
$isInvalidSubscription = ($years + $months + $days) <= 0;
$allowedUsersCount = $subscriptionInfo['amountOfUsers'] ?? 0;
$onlyCountActiveUsers = $subscriptionInfo['onlyCountActiveUsers'] ?? false;
if ($allowedUsersCount === -1) {
$isOverLimit = false;
} elseif ($onlyCountActiveUsers) {
$isOverLimit = $allowedUsersCount < $activeUserCount;
} else {
$isOverLimit = $allowedUsersCount < $userCount;
}
$this->subscriptionInfoCache = [
$instanceSize,
$hasSubscription,
$isInvalidSubscription,
$isOverLimit,
$subscriptionInfo
];
return $this->subscriptionInfoCache;
}
public function getMinimalSubscriptionInfo(): ?array {
$lastResponse = $this->config->getAppValue('support', 'last_response', '');
return json_decode($lastResponse, true);
}
public function checkSubscription() {
$hasInternetConnection = $this->config->getSystemValue('has_internet_connection', true);
if (!$hasInternetConnection) {
return;
}
[
$instanceSize,
$hasSubscription,
$isInvalidSubscription,
$isOverLimit,
$subscriptionInfo
] = $this->getSubscriptionInfo();
if ($hasSubscription && $isInvalidSubscription) {
$this->handleExpired(
$subscriptionInfo['accountManagerInfo']['name'] ?? '',
$subscriptionInfo['accountManagerInfo']['email'] ?? '',
$subscriptionInfo['accountManagerInfo']['phone'] ?? '');
} elseif ($hasSubscription && $isOverLimit) {
$this->handleOverLimit(
$subscriptionInfo['accountManagerInfo']['name'] ?? '',
$subscriptionInfo['accountManagerInfo']['email'] ?? '',
$subscriptionInfo['accountManagerInfo']['phone'] ?? '');
} elseif (!$hasSubscription && $instanceSize === 'large') {
$this->handleNoSubscription($instanceSize);
}
}
private function handleNoSubscription(string $instanceSize) {
$currentTime = time();
$installTime = (int)$this->config->getAppValue('core', 'installedat', $currentTime);
// skip if installed within the last 30 days
if (($installTime + 30 * 24 * 3600) > $currentTime) {
return;
}
$lastNotificationTime = (int)$this->config->getAppValue('support', 'last_notification', 0);
// skip if last notification was within the last 30 days
if (($lastNotificationTime + 30 * 24 * 3600) > $currentTime) {
return;
}
$updateLastNotificationTime = false;
$adminGroup = $this->groupManager->get('admin');
$adminUsers = $adminGroup->getUsers();
foreach ($adminUsers as $adminUser) {
$notification = $this->notifications->createNotification();
$notification->setApp('support')
->setObject('subscription', $instanceSize)
->setSubject('subscription_info')
->setUser($adminUser->getUID());
$count = $this->notifications->getCount($notification);
// skip if the user already has a notification
if ($count > 0) {
continue;
}
$notification->setDateTime(new \DateTime());
$notification->setLink($this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'support']));
$this->notifications->notify($notification);
$updateLastNotificationTime = true;
}
foreach ($adminUsers as $adminUser) {
$emailAddress = $adminUser->getEMailAddress();
if ($emailAddress === null || $emailAddress === '') {
continue;
}
$this->sendNoSubscriptionEmail($adminUser);
$updateLastNotificationTime = true;
}
if ($updateLastNotificationTime) {
$this->config->setAppValue('support', 'last_notification', $currentTime);
}
}
private function handleOverLimit(string $accountManager, string $accountManagerEmail, string $accountManagerPhone) {
$currentTime = time();
$lastNotificationTime = (int)$this->config->getAppValue('support', 'last_over_limit_notification', 0);
// skip if last notification was within the last 5 days
if (($lastNotificationTime + 5 * 24 * 3600) > $currentTime) {
return;
}
$updateLastNotificationTime = false;
$adminGroup = $this->groupManager->get('admin');
$adminUsers = $adminGroup->getUsers();
foreach ($adminUsers as $adminUser) {
$notification = $this->notifications->createNotification();
$notification->setApp('support')
->setObject('subscription', 'over_limit')
->setSubject('subscription_over_limit')
->setUser($adminUser->getUID());
$count = $this->notifications->getCount($notification);
// skip if the user already has a notification
if ($count > 0) {
continue;
}
$notification->setDateTime(new \DateTime());
$notification->setLink($this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'support']));
$this->notifications->notify($notification);
$updateLastNotificationTime = true;
}
foreach ($adminUsers as $adminUser) {
$emailAddress = $adminUser->getEMailAddress();
if ($emailAddress === null || $emailAddress === '') {
continue;
}
$this->sendOverLimitEmail(
$adminUser,
$accountManager,
$accountManagerEmail,
$accountManagerPhone
);
$updateLastNotificationTime = true;
}
if ($updateLastNotificationTime) {
$this->config->setAppValue('support', 'last_over_limit_notification', $currentTime);
}
}
private function handleExpired(string $accountManager, string $accountManagerEmail, string $accountManagerPhone) {
$currentTime = time();
$lastNotificationTime = (int)$this->config->getAppValue('support', 'last_expired_notification', 0);
// skip if last notification was within the last 5 days
if (($lastNotificationTime + 5 * 24 * 3600) > $currentTime) {
return;
}
$updateLastNotificationTime = false;
$adminGroup = $this->groupManager->get('admin');
$adminUsers = $adminGroup->getUsers();
foreach ($adminUsers as $adminUser) {
$notification = $this->notifications->createNotification();
$notification->setApp('support')
->setObject('subscription', 'expired')
->setSubject('subscription_expired')
->setUser($adminUser->getUID());
$count = $this->notifications->getCount($notification);
// skip if the user already has a notification
if ($count > 0) {
continue;
}
$notification->setDateTime(new \DateTime());
$notification->setLink($this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'support']));
$this->notifications->notify($notification);
$updateLastNotificationTime = true;
}
foreach ($adminUsers as $adminUser) {
$emailAddress = $adminUser->getEMailAddress();
if ($emailAddress === null || $emailAddress === '') {
continue;
}
$this->sendExpiredEmail(
$adminUser,
$accountManager,
$accountManagerEmail,
$accountManagerPhone
);
$updateLastNotificationTime = true;
}
if ($updateLastNotificationTime) {
$this->config->setAppValue('support', 'last_expired_notification', $currentTime);
}
}
private function sendNoSubscriptionEmail(IUser $user) {
// TODO what about enforced language?
$language = $this->config->getUserValue($user->getUID(), 'core', 'lang', 'en');
$l = $this->l10nFactory->get('support', $language);
$link = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'support']));
$message = $this->mailer->createMessage();
$emailTemplate = $this->mailer->createEMailTemplate('support.SubscriptionNotification', [
'displayName' => $user->getDisplayName(),
]);
$emailTemplate->setSubject($l->t('Your server has no Nextcloud Subscription'));
$emailTemplate->addHeader();
$emailTemplate->addHeading($l->t('Your Nextcloud server is not backed by a Nextcloud Enterprise Subscription.'));
$text = $l->t('A Nextcloud Enterprise Subscription means the original developers behind your self-hosted cloud server are 100%% dedicated to your success: the security, scalability, performance and functionality of your service!');
$listItem1 = $l->t('If your server setup breaks and employees can\'t work anymore, you don\'t have to rely on searching online forums for a solution. You have direct access to our experienced engineers!');
$listItem2 = $l->t('You have a contract with the vendor providing early security information, mitigations, patches and updates.');
$listItem3 = $l->t('If you need to stay longer on your current version without disruptions, you don\'t have to run software without security updates.');
$listItem4 = $l->t('You have the best expertise at hand to deal with performance and scalability issues.');
$listItem5 = $l->t('You have access to the right documentation and expertise to quickly answer compliance questions or deliver on GDPR, HIPAA and other regulation requirements.');
$text2 = $l->t('We can also provide Outlook integration, Online Office, scalable integrated audio-video and chat communication and other features only available in a limited form for free or develop further integrations and capabilities to your needs.');
$text3 = $l->t('A subscription helps you get the most out of Nextcloud!');
$emailTemplate->addBodyText(
htmlspecialchars($text),
$text
);
$emailTemplate->addBodyListItem(htmlspecialchars($listItem1), '', '', $listItem1);
$emailTemplate->addBodyListItem(htmlspecialchars($listItem2), '', '', $listItem2);
$emailTemplate->addBodyListItem(htmlspecialchars($listItem3), '', '', $listItem3);
$emailTemplate->addBodyListItem(htmlspecialchars($listItem4), '', '', $listItem4);
$emailTemplate->addBodyListItem(htmlspecialchars($listItem5), '', '', $listItem5);
$emailTemplate->addBodyText(
htmlspecialchars($text2) . '<br><br>' .
htmlspecialchars($text3),
$text2 . "\n\n" .
$text3
);
$emailTemplate->addBodyButton(
$l->t('Learn more now'),
$link
);
$generalLink = $this->urlGenerator->getAbsoluteURL('/');
$noteText = $l->t('This mail was sent to all administrators by the support app on your Nextcloud instance at %1$s because you have over %2$s registered users.', [$generalLink, self::THRESHOLD_LARGE]);
$emailTemplate->addBodyText($noteText);
$emailTemplate->addFooter();
$message->useTemplate($emailTemplate);
$attachment = $this->mailer->createAttachmentFromPath(__DIR__ . '/../../resources/Why the Nextcloud Subscription.pdf');
$message->attach($attachment);
$message->setTo([$user->getEMailAddress()]);
$this->mailer->send($message);
}
private function sendOverLimitEmail(IUser $user, string $accountManager, string $accountManagerEmail, string $accountManagerPhone) {
// TODO what about enforced language?
$language = $this->config->getUserValue($user->getUID(), 'core', 'lang', 'en');
$l = $this->l10nFactory->get('support', $language);
$link = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'support']));
$message = $this->mailer->createMessage();
$emailTemplate = $this->mailer->createEMailTemplate('support.SubscriptionNotification', [
'displayName' => $user->getDisplayName(),
]);
$emailTemplate->setSubject($l->t('Your Nextcloud server Subscription is over limit'));
$emailTemplate->addHeader();
$emailTemplate->addHeading($l->t('Your Nextcloud server Subscription is over limit'));
$text = $l->t('Dear admin,');
$text2 = $l->t('Your Nextcloud Subscription doesn\'t cover the number of users who are currently active on this server. Please contact your Nextcloud account manager to get your subscription updated!');
$text3 = $l->t('%1$s is your account manager and can be reached by email via %2$s or by phone via %3$s.', [$accountManager, $accountManagerEmail, $accountManagerPhone]);
$text4 = $l->t('Thank you,');
$text5 = $l->t('Your Nextcloud team');
$emailTemplate->addBodyText(
htmlspecialchars($text) . '<br><br>' .
htmlspecialchars($text2) . '<br><br>' .
htmlspecialchars($text3) . '<br><br>' .
htmlspecialchars($text4) . '<br><br>' .
htmlspecialchars($text5),
$text . "\n\n" .
$text2 . "\n\n" .
$text3 . "\n\n" .
$text4 . "\n\n" .
$text5
);
$emailTemplate->addBodyButton(
$l->t('Learn more now'),
$link
);
$generalLink = $this->urlGenerator->getAbsoluteURL('/');
$noteText = $l->t('This mail was sent to all administrators by the support app on your Nextcloud instance at %s because you have more users than your subscription covers.', [$generalLink]);
$emailTemplate->addBodyText($noteText);
$message->setTo([$user->getEMailAddress()]);
$emailTemplate->addFooter();
$message->useTemplate($emailTemplate);
$this->mailer->send($message);
}
private function sendExpiredEmail(IUser $user, string $accountManager, string $accountManagerEmail, string $accountManagerPhone) {
// TODO what about enforced language?
$language = $this->config->getUserValue($user->getUID(), 'core', 'lang', 'en');
$l = $this->l10nFactory->get('support', $language);
$link = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'support']));
$message = $this->mailer->createMessage();
$emailTemplate = $this->mailer->createEMailTemplate('support.SubscriptionNotification', [
'displayName' => $user->getDisplayName(),
]);
$emailTemplate->setSubject($l->t('Your Nextcloud server Subscription is expired'));
$emailTemplate->addHeader();
$emailTemplate->addHeading($l->t('Your Nextcloud server Subscription is expired!'));
$text = $l->t('Dear admin,');
$text2 = $l->t('Your Nextcloud Subscription has expired! Please contact your Nextcloud account manager to get your subscription updated!');
$text3 = $l->t('%1$s is your account manager and can be reached by email via %2$s or by phone via %3$s.', [$accountManager, $accountManagerEmail, $accountManagerPhone]);
$text4 = $l->t('Thank you,');
$text5 = $l->t('Your Nextcloud team');
$emailTemplate->addBodyText(
htmlspecialchars($text) . '<br><br>' .
htmlspecialchars($text2) . '<br><br>' .
htmlspecialchars($text3) . '<br><br>' .
htmlspecialchars($text4) . '<br><br>' .
htmlspecialchars($text5),
$text . "\n\n" .
$text2 . "\n\n" .
$text3 . "\n\n" .
$text4 . "\n\n" .
$text5
);
$emailTemplate->addBodyButton(
$l->t('Learn more now'),
$link
);
$generalLink = $this->urlGenerator->getAbsoluteURL('/');
$noteText = $l->t('This mail was sent to all administrators by the support app on your Nextcloud instance at %s because your subscription expired.', [$generalLink]);
$emailTemplate->addBodyText($noteText);
$message->setTo([$user->getEMailAddress()]);
$emailTemplate->addFooter();
$message->useTemplate($emailTemplate);
$this->mailer->send($message);
}
}
@@ -0,0 +1,205 @@
<?php
/**
* @copyright Copyright (c) 2018 Morris Jobke <hey@morrisjobke.de>
*
* @author Morris Jobke <hey@morrisjobke.de>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Support\Settings;
use OCA\Support\Service\SubscriptionService;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\Settings\IDelegatedSettings;
class Admin implements IDelegatedSettings {
private IConfig $config;
private IUserManager $userManager;
private IURLGenerator $urlGenerator;
private SubscriptionService $subscriptionService;
public function __construct(IConfig $config,
IUserManager $userManager,
IURLGenerator $urlGenerator,
SubscriptionService $subscriptionService) {
$this->userManager = $userManager;
$this->config = $config;
$this->urlGenerator = $urlGenerator;
$this->subscriptionService = $subscriptionService;
}
/**
* @return TemplateResponse
*/
public function getForm() {
$userCount = $this->subscriptionService->getUserCount();
$activeUserCount = $this->userManager->countSeenUsers();
$instanceSize = 'small';
if ($userCount > SubscriptionService::THRESHOLD_MEDIUM) {
if ($userCount > SubscriptionService::THRESHOLD_LARGE) {
$instanceSize = 'large';
} else {
$instanceSize = 'medium';
}
}
$subscriptionKey = $this->config->getAppValue('support', 'subscription_key', null);
$potentialSubscriptionKey = $this->config->getAppValue('support', 'potential_subscription_key', null);
$lastResponse = $this->config->getAppValue('support', 'last_response', '');
$lastError = (int)$this->config->getAppValue('support', 'last_error', 0);
// delete the invalid error, because there is no renewal happening
if ($lastError === SubscriptionService::ERROR_FAILED_INVALID) {
if ($subscriptionKey !== null && $subscriptionKey !== '') {
$this->config->setAppValue('support', 'potential_subscription_key', $subscriptionKey);
} else {
$this->config->deleteAppValue('support', 'potential_subscription_key');
}
$this->config->deleteAppValue('support', 'last_error');
} elseif ($lastError === SubscriptionService::ERROR_INVALID_SUBSCRIPTION_KEY) {
$this->config->deleteAppValue('support', 'last_error');
}
$subscriptionInfo = json_decode($lastResponse, true);
$now = new \DateTime();
$subscriptionEndDate = new \DateTime($subscriptionInfo['endDate'] ?? 'now');
if ($now > $subscriptionEndDate) {
$years = 0;
$months = 0;
$days = 0;
$weeks = 0;
} else {
$diff = $now->diff($subscriptionEndDate);
$years = (int)$diff->format('%y');
$months = (int)$diff->format('%m');
$days = (int)$diff->format('%d');
$weeks = floor($days / 7);
/* run up to the next month for 4 weeks and more */
if ($weeks > 3) {
$months += 1;
$weeks = 0;
$days = 0;
}
}
$specificSubscriptions = [];
$collaboraEndDate = new \DateTime($subscriptionInfo['collabora']['endDate'] ?? 'yesterday');
if ($now < $collaboraEndDate) {
$specificSubscriptions[] = 'Collabora';
}
$talkEndDate = new \DateTime($subscriptionInfo['talk']['endDate'] ?? 'yesterday');
if ($now < $talkEndDate) {
$specificSubscriptions[] = 'Talk';
}
$groupwareEndDate = new \DateTime($subscriptionInfo['groupware']['endDate'] ?? 'yesterday');
if ($now < $groupwareEndDate) {
$specificSubscriptions[] = 'Groupware';
}
$allowedUsersCount = $subscriptionInfo['amountOfUsers'] ?? 0;
$onlyCountActiveUsers = $subscriptionInfo['onlyCountActiveUsers'] ?? false;
if ($allowedUsersCount === -1) {
$isOverLimit = false;
} elseif ($onlyCountActiveUsers) {
$isOverLimit = $allowedUsersCount < $activeUserCount;
} else {
$isOverLimit = $allowedUsersCount < $userCount;
}
if (isset($subscriptionInfo['partnerContact']) && count($subscriptionInfo['partnerContact']) > 0) {
$contactInfo = $subscriptionInfo['partnerContact'];
} else {
$contactInfo = $subscriptionInfo['accountManagerInfo'] ?? '';
}
$params = [
'instanceSize' => $instanceSize,
'userCount' => $userCount,
'activeUserCount' => $activeUserCount,
'subscriptionKey' => $subscriptionKey,
'potentialSubscriptionKey' => $potentialSubscriptionKey,
'lastError' => $lastError,
'contactPerson' => $contactInfo,
'subscriptionType' => $subscriptionInfo['level'] ?? '',
'subscriptionUsers' => $allowedUsersCount,
'onlyCountActiveUsers' => $onlyCountActiveUsers,
'specificSubscriptions' => $specificSubscriptions,
'extendedSupport' => $subscriptionInfo['extendedSupport'] ?? false,
'expiryYears' => $years,
'expiryMonths' => $months,
'expiryWeeks' => $weeks,
'expiryDays' => $days,
'validSubscription' => ($years + $months + $days) > 0,
'overLimit' => $isOverLimit,
'showSubscriptionDetails' => is_array($subscriptionInfo),
'showSubscriptionKeyInput' => !is_array($subscriptionInfo),
'showCommunitySupportSection' => $instanceSize === 'small' && !is_array($subscriptionInfo),
'showEnterpriseSupportSection' => $instanceSize !== 'small' && !is_array($subscriptionInfo),
'subscriptionKeyUrl' => $this->urlGenerator->linkToRoute('support.api.setSubscriptionKey'),
'offlineActivationData' => [
'subscriptionKey' => $potentialSubscriptionKey,
'instanceId' => $this->config->getSystemValueString('instanceid', ''),
'userCount' => $userCount,
'activeUserCount' => $activeUserCount,
'version' => implode('.', \OCP\Util::getVersion())
],
];
return new TemplateResponse('support', 'admin', $params);
}
/**
* @return string the section ID, e.g. 'sharing'
*/
public function getSection() {
return 'support';
}
/**
* @return int whether the form should be rather on the top or bottom of
* the admin section. The forms are arranged in ascending order of the
* priority values. It is required to return a value between 0 and 100.
*
* keep the server setting at the top, right after "server settings"
*/
public function getPriority() {
return 0;
}
public function getName(): ?string {
return null; // Only one setting in this section
}
public function getAuthorizedAppConfig(): array {
return [
'support' => ['.*'],
];
}
}
@@ -0,0 +1,69 @@
<?php
/**
* @copyright Copyright (c) 2018 Morris Jobke <hey@morrisjobke.de>
*
* @author Morris Jobke <hey@morrisjobke.de>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Support\Settings;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\Settings\IIconSection;
class Section implements IIconSection {
private IL10N $l;
private IURLGenerator $url;
public function __construct(
IL10N $l,
IURLGenerator $url
) {
$this->l = $l;
$this->url = $url;
}
/**
* {@inheritdoc}
*/
public function getID() {
return 'support';
}
/**
* {@inheritdoc}
*/
public function getName() {
return $this->l->t('Support');
}
/**
* {@inheritdoc}
*/
public function getPriority() {
return 1;
}
/**
* {@inheritdoc}
*/
public function getIcon() {
return $this->url->imagePath('support', 'section.svg');
}
}
@@ -0,0 +1,240 @@
<?php
declare(strict_types=1);
/**
* @author Morris Jobke <hey@morrisjobke.de>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Support\Subscription;
use OCA\Support\Service\SubscriptionService;
use OCP\IConfig;
use OCP\Support\Subscription\ISubscription;
use OCP\Support\Subscription\ISupportedApps;
class SubscriptionAdapter implements ISubscription, ISupportedApps {
public function __construct(
private SubscriptionService $subscriptionService,
private IConfig $config,
) {
}
/**
* Indicates if a valid subscription is available
*/
public function hasValidSubscription(): bool {
[
$instanceSize,
$hasSubscription,
$isInvalidSubscription,
$isOverLimit,
$subscriptionInfo
] = $this->subscriptionService->getSubscriptionInfo();
return !$isInvalidSubscription;
}
private function subscriptionNotExpired(string $endDate): bool {
$subscriptionEndDate = new \DateTime($endDate);
$now = new \DateTime();
if ($now >= $subscriptionEndDate) {
return false;
}
return true;
}
/**
* Fetches the list of app IDs that are supported by the subscription
*
* @since 17.0.0
*/
public function getSupportedApps(): array {
[
$instanceSize,
$hasSubscription,
$isInvalidSubscription,
$isOverLimit,
$subscriptionInfo
] = $this->subscriptionService->getSubscriptionInfo();
$hasValidGroupwareSubscription = $this->subscriptionNotExpired($subscriptionInfo['groupware']['endDate'] ?? 'now');
$hasValidTalkSubscription = $this->subscriptionNotExpired($subscriptionInfo['talk']['endDate'] ?? 'now');
$hasValidCollaboraSubscription = $this->subscriptionNotExpired($subscriptionInfo['collabora']['endDate'] ?? 'now');
$hasValidOnlyOfficeSubscription = $this->subscriptionNotExpired($subscriptionInfo['onlyoffice']['endDate'] ?? 'now');
$filesSubscription = [
'accessibility',
'activity',
'admin_audit',
'bruteforcesettings',
'circles',
'comments',
'data_request',
'dav',
'encryption',
'external',
'federatedfilesharing',
'federation',
'files',
'files_accesscontrol',
'files_antivirus',
'files_automatedtagging',
'files_external',
'files_fulltextsearch',
'files_fulltextsearch_tesseract',
'files_pdfviewer',
'files_retention',
'files_sharing',
'files_trashbin',
'files_versions',
'files_videoplayer',
'firstrunwizard',
'fulltextsearch',
'fulltextsearch_elasticsearch',
'groupfolders',
'guests',
'logreader',
'lookup_server_connector',
'nextcloud_announcements',
'notifications',
'oauth2',
'password_policy',
'photos',
'privacy',
'provisioning_api',
'recommendations',
'serverinfo',
'sharebymail',
'sharepoint',
'socialsharing_diaspora',
'socialsharing_email',
'socialsharing_facebook',
'socialsharing_twitter',
'support',
'suspicious_login',
'systemtags',
'terms_of_service',
'text',
'theming',
'twofactor_backupcodes',
'twofactor_totp',
'twofactor_u2f',
'updatenotification',
'user_ldap',
'user_oidc',
'user_saml',
'viewer',
'workflowengine',
'workflow_script',
];
$nextcloudVersion = \OCP\Util::getVersion()[0];
if ($nextcloudVersion >= 24) {
$filesSubscription[] = 'files_lock';
}
if ($nextcloudVersion >= 22) {
$filesSubscription[] = 'approval';
$filesSubscription[] = 'contacts';
$filesSubscription[] = 'files_zip';
}
if ($nextcloudVersion >= 20) {
$filesSubscription[] = 'dashboard';
$filesSubscription[] = 'flow_notifications';
$filesSubscription[] = 'user_status';
$filesSubscription[] = 'weather_status';
}
if ($nextcloudVersion >= 19) {
$filesSubscription[] = 'contactsinteraction';
}
$supportedApps = [];
if ($hasSubscription) {
$supportedApps = array_merge($supportedApps, $filesSubscription);
}
if ($hasValidGroupwareSubscription) {
$supportedApps[] = 'calendar';
$supportedApps[] = 'contacts';
$supportedApps[] = 'deck';
$supportedApps[] = 'mail';
}
if ($hasValidTalkSubscription) {
$supportedApps[] = 'spreed';
}
if ($hasValidCollaboraSubscription) {
$supportedApps[] = 'richdocuments';
}
if ($hasValidOnlyOfficeSubscription) {
$supportedApps[] = 'onlyoffice';
}
if (isset($subscriptionInfo['supportedApps'])) {
foreach ($subscriptionInfo['supportedApps'] as $app) {
if ($app !== '' && !in_array($app, $supportedApps)) {
$supportedApps[] = $app;
}
}
}
return $supportedApps;
}
/**
* Indicates if the subscription has extended support
*
* @since 17.0.0
*/
public function hasExtendedSupport(): bool {
$subscriptionInfo = $this->subscriptionService->getMinimalSubscriptionInfo();
return $subscriptionInfo['extendedSupport'] ?? false;
}
/**
* Indicates if a hard user limit is reached and no new users should be created
*
* @since 21.0.0
*/
public function isHardUserLimitReached(): bool {
[
,,
$isInvalidSubscription,
$isOverLimit,
$subscriptionInfo
] = $this->subscriptionService->getSubscriptionInfo();
$configUserLimit = (int) $this->config->getAppValue('support', 'user-limit', '0');
if (
!$isInvalidSubscription
&& $configUserLimit > 0
&& $configUserLimit <= $this->subscriptionService->getUserCount()
) {
return true;
}
if (!isset($subscriptionInfo['hasHardUserLimit']) || $subscriptionInfo['hasHardUserLimit'] === false) {
return false;
}
return $isOverLimit;
}
}