Merge pull request #14282 from owncloud/disable-non-shipped-apps-master
3rd-party apps are disabled on upgrade
This commit is contained in:
commit
7325414481
|
@ -36,9 +36,12 @@ if (OC::checkUpgrade(false)) {
|
||||||
$eventSource = \OC::$server->createEventSource();
|
$eventSource = \OC::$server->createEventSource();
|
||||||
$updater = new \OC\Updater(
|
$updater = new \OC\Updater(
|
||||||
\OC::$server->getHTTPHelper(),
|
\OC::$server->getHTTPHelper(),
|
||||||
\OC::$server->getAppConfig(),
|
\OC::$server->getConfig(),
|
||||||
\OC_Log::$object
|
\OC_Log::$object
|
||||||
);
|
);
|
||||||
|
$incompatibleApps = [];
|
||||||
|
$disabledThirdPartyApps = [];
|
||||||
|
|
||||||
$updater->listen('\OC\Updater', 'maintenanceStart', function () use ($eventSource, $l) {
|
$updater->listen('\OC\Updater', 'maintenanceStart', function () use ($eventSource, $l) {
|
||||||
$eventSource->send('success', (string)$l->t('Turned on maintenance mode'));
|
$eventSource->send('success', (string)$l->t('Turned on maintenance mode'));
|
||||||
});
|
});
|
||||||
|
@ -57,13 +60,11 @@ if (OC::checkUpgrade(false)) {
|
||||||
$updater->listen('\OC\Updater', 'appUpgrade', function ($app, $version) use ($eventSource, $l) {
|
$updater->listen('\OC\Updater', 'appUpgrade', function ($app, $version) use ($eventSource, $l) {
|
||||||
$eventSource->send('success', (string)$l->t('Updated "%s" to %s', array($app, $version)));
|
$eventSource->send('success', (string)$l->t('Updated "%s" to %s', array($app, $version)));
|
||||||
});
|
});
|
||||||
$updater->listen('\OC\Updater', 'disabledApps', function ($appList) use ($eventSource, $l) {
|
$updater->listen('\OC\Updater', 'incompatibleAppDisabled', function ($app) use (&$incompatibleApps) {
|
||||||
$list = array();
|
$incompatibleApps[]= $app;
|
||||||
foreach ($appList as $appId) {
|
});
|
||||||
$info = OC_App::getAppInfo($appId);
|
$updater->listen('\OC\Updater', 'thirdPartyAppDisabled', function ($app) use (&$disabledThirdPartyApps) {
|
||||||
$list[] = $info['name'] . ' (' . $info['id'] . ')';
|
$disabledThirdPartyApps[]= $app;
|
||||||
}
|
|
||||||
$eventSource->send('success', (string)$l->t('Disabled incompatible apps: %s', implode(', ', $list)));
|
|
||||||
});
|
});
|
||||||
$updater->listen('\OC\Updater', 'failure', function ($message) use ($eventSource) {
|
$updater->listen('\OC\Updater', 'failure', function ($message) use ($eventSource) {
|
||||||
$eventSource->send('failure', $message);
|
$eventSource->send('failure', $message);
|
||||||
|
@ -73,6 +74,15 @@ if (OC::checkUpgrade(false)) {
|
||||||
|
|
||||||
$updater->upgrade();
|
$updater->upgrade();
|
||||||
|
|
||||||
|
if (!empty($incompatibleApps)) {
|
||||||
|
$eventSource->send('notice',
|
||||||
|
(string)$l->t('Following incompatible apps have been disabled: %s', implode(', ', $incompatibleApps)));
|
||||||
|
}
|
||||||
|
if (!empty($disabledThirdPartyApps)) {
|
||||||
|
$eventSource->send('notice',
|
||||||
|
(string)$l->t('Following 3rd party apps have been disabled: %s', implode(', ', $disabledThirdPartyApps)));
|
||||||
|
}
|
||||||
|
|
||||||
$eventSource->send('done', '');
|
$eventSource->send('done', '');
|
||||||
$eventSource->close();
|
$eventSource->close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,8 @@ class Upgrade extends Command {
|
||||||
|
|
||||||
if(\OC::checkUpgrade(false)) {
|
if(\OC::checkUpgrade(false)) {
|
||||||
$self = $this;
|
$self = $this;
|
||||||
$updater = new Updater(\OC::$server->getHTTPHelper(), \OC::$server->getAppConfig());
|
$updater = new Updater(\OC::$server->getHTTPHelper(),
|
||||||
|
\OC::$server->getConfig());
|
||||||
|
|
||||||
$updater->setSimulateStepEnabled($simulateStepEnabled);
|
$updater->setSimulateStepEnabled($simulateStepEnabled);
|
||||||
$updater->setUpdateStepEnabled($updateStepEnabled);
|
$updater->setUpdateStepEnabled($updateStepEnabled);
|
||||||
|
@ -122,8 +123,17 @@ class Upgrade extends Command {
|
||||||
$updater->listen('\OC\Updater', 'dbSimulateUpgrade', function () use($output) {
|
$updater->listen('\OC\Updater', 'dbSimulateUpgrade', function () use($output) {
|
||||||
$output->writeln('<info>Checked database schema update</info>');
|
$output->writeln('<info>Checked database schema update</info>');
|
||||||
});
|
});
|
||||||
$updater->listen('\OC\Updater', 'disabledApps', function ($appList) use($output) {
|
$updater->listen('\OC\Updater', 'incompatibleAppDisabled', function ($app) use($output) {
|
||||||
$output->writeln('<info>Disabled incompatible apps: ' . implode(', ', $appList) . '</info>');
|
$output->writeln('<info>Disabled incompatible app: ' . $app . '</info>');
|
||||||
|
});
|
||||||
|
$updater->listen('\OC\Updater', 'thirdPartyAppDisabled', function ($app) use($output) {
|
||||||
|
$output->writeln('<info>Disabled 3rd-party app: ' . $app . '</info>');
|
||||||
|
});
|
||||||
|
$updater->listen('\OC\Updater', 'appUpgradeCheck', function () use ($output) {
|
||||||
|
$output->writeln('<info>Checked database schema update for apps</info>');
|
||||||
|
});
|
||||||
|
$updater->listen('\OC\Updater', 'appUpgrade', function ($app, $version) use ($output) {
|
||||||
|
$output->writeln("<info>Updated <$app> to $version</info>");
|
||||||
});
|
});
|
||||||
|
|
||||||
$updater->listen('\OC\Updater', 'failure', function ($message) use($output, $self) {
|
$updater->listen('\OC\Updater', 'failure', function ($message) use($output, $self) {
|
||||||
|
|
|
@ -38,6 +38,9 @@
|
||||||
updateEventSource.listen('success', function(message) {
|
updateEventSource.listen('success', function(message) {
|
||||||
$('<span>').append(message).append('<br />').appendTo($el);
|
$('<span>').append(message).append('<br />').appendTo($el);
|
||||||
});
|
});
|
||||||
|
updateEventSource.listen('notice', function(message) {
|
||||||
|
$('<span>').addClass('error').append(message).append('<br />').appendTo($el);
|
||||||
|
});
|
||||||
updateEventSource.listen('error', function(message) {
|
updateEventSource.listen('error', function(message) {
|
||||||
$('<span>').addClass('error').append(message).append('<br />').appendTo($el);
|
$('<span>').addClass('error').append(message).append('<br />').appendTo($el);
|
||||||
message = t('core', 'Please reload the page.');
|
message = t('core', 'Please reload the page.');
|
||||||
|
|
|
@ -958,39 +958,6 @@ class OC_App {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* check if the current enabled apps are compatible with the current
|
|
||||||
* ownCloud version. disable them if not.
|
|
||||||
* This is important if you upgrade ownCloud and have non ported 3rd
|
|
||||||
* party apps installed.
|
|
||||||
*
|
|
||||||
* @param array $apps optional app id list to check, uses all enabled apps
|
|
||||||
* when not specified
|
|
||||||
*
|
|
||||||
* @return array containing the list of ids of the disabled apps
|
|
||||||
*/
|
|
||||||
public static function checkAppsRequirements($apps = array()) {
|
|
||||||
$disabledApps = array();
|
|
||||||
if (empty($apps)) {
|
|
||||||
$apps = OC_App::getEnabledApps();
|
|
||||||
}
|
|
||||||
$version = OC_Util::getVersion();
|
|
||||||
foreach ($apps as $app) {
|
|
||||||
// check if the app is compatible with this version of ownCloud
|
|
||||||
$info = OC_App::getAppInfo($app);
|
|
||||||
if (!self::isAppCompatible($version, $info)) {
|
|
||||||
OC_Log::write('core',
|
|
||||||
'App "' . $info['name'] . '" (' . $app . ') can\'t be used because it is'
|
|
||||||
. ' not compatible with this version of ownCloud',
|
|
||||||
OC_Log::ERROR);
|
|
||||||
OC_App::disable($app);
|
|
||||||
OC_Hook::emit('update', 'success', 'Disabled ' . $info['name'] . ' app because it is not compatible');
|
|
||||||
$disabledApps[] = $app;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $disabledApps;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adjust the number of version parts of $version1 to match
|
* Adjust the number of version parts of $version1 to match
|
||||||
* the number of version parts of $version2.
|
* the number of version parts of $version2.
|
||||||
|
|
|
@ -77,7 +77,8 @@ class OC_TemplateLayout extends OC_Template {
|
||||||
// Update notification
|
// Update notification
|
||||||
if($this->config->getSystemValue('updatechecker', true) === true &&
|
if($this->config->getSystemValue('updatechecker', true) === true &&
|
||||||
OC_User::isAdminUser(OC_User::getUser())) {
|
OC_User::isAdminUser(OC_User::getUser())) {
|
||||||
$updater = new \OC\Updater(\OC::$server->getHTTPHelper(), \OC::$server->getAppConfig());
|
$updater = new \OC\Updater(\OC::$server->getHTTPHelper(),
|
||||||
|
\OC::$server->getConfig());
|
||||||
$data = $updater->check();
|
$data = $updater->check();
|
||||||
|
|
||||||
if(isset($data['version']) && $data['version'] != '' and $data['version'] !== Array()) {
|
if(isset($data['version']) && $data['version'] != '' and $data['version'] !== Array()) {
|
||||||
|
|
|
@ -30,6 +30,9 @@
|
||||||
namespace OC;
|
namespace OC;
|
||||||
|
|
||||||
use OC\Hooks\BasicEmitter;
|
use OC\Hooks\BasicEmitter;
|
||||||
|
use OC_App;
|
||||||
|
use OC_Util;
|
||||||
|
use OCP\IConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class that handles autoupdating of ownCloud
|
* Class that handles autoupdating of ownCloud
|
||||||
|
@ -42,29 +45,27 @@ use OC\Hooks\BasicEmitter;
|
||||||
*/
|
*/
|
||||||
class Updater extends BasicEmitter {
|
class Updater extends BasicEmitter {
|
||||||
|
|
||||||
/**
|
/** @var \OC\Log $log */
|
||||||
* @var \OC\Log $log
|
|
||||||
*/
|
|
||||||
private $log;
|
private $log;
|
||||||
|
|
||||||
/**
|
/** @var \OC\HTTPHelper $helper */
|
||||||
* @var \OC\HTTPHelper $helper;
|
|
||||||
*/
|
|
||||||
private $httpHelper;
|
private $httpHelper;
|
||||||
|
|
||||||
/**
|
/** @var IConfig */
|
||||||
* @var \OCP\IAppConfig;
|
|
||||||
*/
|
|
||||||
private $config;
|
private $config;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
private $simulateStepEnabled;
|
private $simulateStepEnabled;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
private $updateStepEnabled;
|
private $updateStepEnabled;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param HTTPHelper $httpHelper
|
||||||
|
* @param IConfig $config
|
||||||
* @param \OC\Log $log
|
* @param \OC\Log $log
|
||||||
*/
|
*/
|
||||||
public function __construct($httpHelper, $config, $log = null) {
|
public function __construct(HTTPHelper $httpHelper, IConfig $config, $log = null) {
|
||||||
$this->httpHelper = $httpHelper;
|
$this->httpHelper = $httpHelper;
|
||||||
$this->log = $log;
|
$this->log = $log;
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
|
@ -102,23 +103,23 @@ class Updater extends BasicEmitter {
|
||||||
public function check($updaterUrl = null) {
|
public function check($updaterUrl = null) {
|
||||||
|
|
||||||
// Look up the cache - it is invalidated all 30 minutes
|
// Look up the cache - it is invalidated all 30 minutes
|
||||||
if (($this->config->getValue('core', 'lastupdatedat') + 1800) > time()) {
|
if (((int)$this->config->getAppValue('core', 'lastupdatedat') + 1800) > time()) {
|
||||||
return json_decode($this->config->getValue('core', 'lastupdateResult'), true);
|
return json_decode($this->config->getAppValue('core', 'lastupdateResult'), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_null($updaterUrl)) {
|
if (is_null($updaterUrl)) {
|
||||||
$updaterUrl = 'https://apps.owncloud.com/updater.php';
|
$updaterUrl = 'https://apps.owncloud.com/updater.php';
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->config->setValue('core', 'lastupdatedat', time());
|
$this->config->setAppValue('core', 'lastupdatedat', time());
|
||||||
|
|
||||||
if ($this->config->getValue('core', 'installedat', '') == '') {
|
if ($this->config->getAppValue('core', 'installedat', '') == '') {
|
||||||
$this->config->setValue('core', 'installedat', microtime(true));
|
$this->config->setAppValue('core', 'installedat', microtime(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
$version = \OC_Util::getVersion();
|
$version = \OC_Util::getVersion();
|
||||||
$version['installed'] = $this->config->getValue('core', 'installedat');
|
$version['installed'] = $this->config->getAppValue('core', 'installedat');
|
||||||
$version['updated'] = $this->config->getValue('core', 'lastupdatedat');
|
$version['updated'] = $this->config->getAppValue('core', 'lastupdatedat');
|
||||||
$version['updatechannel'] = \OC_Util::getChannel();
|
$version['updatechannel'] = \OC_Util::getChannel();
|
||||||
$version['edition'] = \OC_Util::getEditionString();
|
$version['edition'] = \OC_Util::getEditionString();
|
||||||
$version['build'] = \OC_Util::getBuild();
|
$version['build'] = \OC_Util::getBuild();
|
||||||
|
@ -146,7 +147,7 @@ class Updater extends BasicEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache the result
|
// Cache the result
|
||||||
$this->config->setValue('core', 'lastupdateResult', json_encode($data));
|
$this->config->setAppValue('core', 'lastupdateResult', json_encode($data));
|
||||||
return $tmp;
|
return $tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,9 +158,9 @@ class Updater extends BasicEmitter {
|
||||||
* @return bool true if the operation succeeded, false otherwise
|
* @return bool true if the operation succeeded, false otherwise
|
||||||
*/
|
*/
|
||||||
public function upgrade() {
|
public function upgrade() {
|
||||||
\OC_Config::setValue('maintenance', true);
|
$this->config->setSystemValue('maintenance', true);
|
||||||
|
|
||||||
$installedVersion = \OC_Config::getValue('version', '0.0.0');
|
$installedVersion = $this->config->getSystemValue('version', '0.0.0');
|
||||||
$currentVersion = implode('.', \OC_Util::getVersion());
|
$currentVersion = implode('.', \OC_Util::getVersion());
|
||||||
if ($this->log) {
|
if ($this->log) {
|
||||||
$this->log->debug('starting upgrade from ' . $installedVersion . ' to ' . $currentVersion, array('app' => 'core'));
|
$this->log->debug('starting upgrade from ' . $installedVersion . ' to ' . $currentVersion, array('app' => 'core'));
|
||||||
|
@ -172,7 +173,7 @@ class Updater extends BasicEmitter {
|
||||||
$this->emit('\OC\Updater', 'failure', array($exception->getMessage()));
|
$this->emit('\OC\Updater', 'failure', array($exception->getMessage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
\OC_Config::setValue('maintenance', false);
|
$this->config->setSystemValue('maintenance', false);
|
||||||
$this->emit('\OC\Updater', 'maintenanceEnd');
|
$this->emit('\OC\Updater', 'maintenanceEnd');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,10 +221,10 @@ class Updater extends BasicEmitter {
|
||||||
// create empty file in data dir, so we can later find
|
// create empty file in data dir, so we can later find
|
||||||
// out that this is indeed an ownCloud data directory
|
// out that this is indeed an ownCloud data directory
|
||||||
// (in case it didn't exist before)
|
// (in case it didn't exist before)
|
||||||
file_put_contents(\OC_Config::getValue('datadirectory', \OC::$SERVERROOT . '/data') . '/.ocdata', '');
|
file_put_contents($this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/.ocdata', '');
|
||||||
|
|
||||||
// pre-upgrade repairs
|
// pre-upgrade repairs
|
||||||
$repair = new \OC\Repair(\OC\Repair::getBeforeUpgradeRepairSteps());
|
$repair = new Repair(Repair::getBeforeUpgradeRepairSteps());
|
||||||
$repair->run();
|
$repair->run();
|
||||||
|
|
||||||
// simulate DB upgrade
|
// simulate DB upgrade
|
||||||
|
@ -238,22 +239,18 @@ class Updater extends BasicEmitter {
|
||||||
if ($this->updateStepEnabled) {
|
if ($this->updateStepEnabled) {
|
||||||
$this->doCoreUpgrade();
|
$this->doCoreUpgrade();
|
||||||
|
|
||||||
$disabledApps = \OC_App::checkAppsRequirements();
|
$this->checkAppsRequirements();
|
||||||
if (!empty($disabledApps)) {
|
|
||||||
$this->emit('\OC\Updater', 'disabledApps', array($disabledApps));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->doAppUpgrade();
|
$this->doAppUpgrade();
|
||||||
|
|
||||||
// post-upgrade repairs
|
// post-upgrade repairs
|
||||||
$repair = new \OC\Repair(\OC\Repair::getRepairSteps());
|
$repair = new Repair(Repair::getRepairSteps());
|
||||||
$repair->run();
|
$repair->run();
|
||||||
|
|
||||||
//Invalidate update feed
|
//Invalidate update feed
|
||||||
$this->config->setValue('core', 'lastupdatedat', 0);
|
$this->config->setAppValue('core', 'lastupdatedat', 0);
|
||||||
|
|
||||||
// only set the final version if everything went well
|
// only set the final version if everything went well
|
||||||
\OC_Config::setValue('version', implode('.', \OC_Util::getVersion()));
|
$this->config->setSystemValue('version', implode('.', \OC_Util::getVersion()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,14 +275,11 @@ class Updater extends BasicEmitter {
|
||||||
$apps = \OC_App::getEnabledApps();
|
$apps = \OC_App::getEnabledApps();
|
||||||
|
|
||||||
foreach ($apps as $appId) {
|
foreach ($apps as $appId) {
|
||||||
if ($version) {
|
$info = \OC_App::getAppInfo($appId);
|
||||||
$info = \OC_App::getAppInfo($appId);
|
$compatible = \OC_App::isAppCompatible($version, $info);
|
||||||
$compatible = \OC_App::isAppCompatible($version, $info);
|
$isShipped = \OC_App::isShipped($appId);
|
||||||
} else {
|
|
||||||
$compatible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($compatible && \OC_App::shouldUpgrade($appId)) {
|
if ($compatible && $isShipped && \OC_App::shouldUpgrade($appId)) {
|
||||||
/**
|
/**
|
||||||
* FIXME: The preupdate check is performed before the database migration, otherwise database changes
|
* FIXME: The preupdate check is performed before the database migration, otherwise database changes
|
||||||
* are not possible anymore within it. - Consider this when touching the code.
|
* are not possible anymore within it. - Consider this when touching the code.
|
||||||
|
@ -356,5 +350,50 @@ class Updater extends BasicEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check if the current enabled apps are compatible with the current
|
||||||
|
* ownCloud version. disable them if not.
|
||||||
|
* This is important if you upgrade ownCloud and have non ported 3rd
|
||||||
|
* party apps installed.
|
||||||
|
*/
|
||||||
|
private function checkAppsRequirements() {
|
||||||
|
$isCoreUpgrade = $this->isCodeUpgrade();
|
||||||
|
$apps = OC_App::getEnabledApps();
|
||||||
|
$version = OC_Util::getVersion();
|
||||||
|
foreach ($apps as $app) {
|
||||||
|
// check if the app is compatible with this version of ownCloud
|
||||||
|
$info = OC_App::getAppInfo($app);
|
||||||
|
if(!OC_App::isAppCompatible($version, $info)) {
|
||||||
|
OC_App::disable($app);
|
||||||
|
$this->emit('\OC\Updater', 'incompatibleAppDisabled', array($app));
|
||||||
|
}
|
||||||
|
// no need to disable any app in case this is a non-core upgrade
|
||||||
|
if (!$isCoreUpgrade) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// shipped apps will remain enabled
|
||||||
|
if (OC_App::isShipped($app)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// authentication and session apps will remain enabled as well
|
||||||
|
if (OC_App::isType($app, ['session', 'authentication'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// disable any other 3rd party apps
|
||||||
|
\OC_App::disable($app);
|
||||||
|
$this->emit('\OC\Updater', 'thirdPartyAppDisabled', array($app));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isCodeUpgrade() {
|
||||||
|
$installedVersion = $this->config->getSystemValue('version', '0.0.0');
|
||||||
|
$currentVersion = implode('.', OC_Util::getVersion());
|
||||||
|
if (version_compare($currentVersion, $installedVersion, '>')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,7 @@ class UpdaterTest extends \Test\TestCase {
|
||||||
|
|
||||||
protected function getUpdaterMock($content){
|
protected function getUpdaterMock($content){
|
||||||
// Invalidate cache
|
// Invalidate cache
|
||||||
$mockedAppConfig = $this->getMockBuilder('\OC\AppConfig')
|
$mockedConfig = $this->getMockBuilder('\OCP\IConfig')
|
||||||
->disableOriginalConstructor()
|
->disableOriginalConstructor()
|
||||||
->getMock()
|
->getMock()
|
||||||
;
|
;
|
||||||
|
@ -101,7 +101,7 @@ class UpdaterTest extends \Test\TestCase {
|
||||||
|
|
||||||
$mockedHTTPHelper->expects($this->once())->method('getUrlContent')->will($this->returnValue($content));
|
$mockedHTTPHelper->expects($this->once())->method('getUrlContent')->will($this->returnValue($content));
|
||||||
|
|
||||||
return new Updater($mockedHTTPHelper, $mockedAppConfig);
|
return new Updater($mockedHTTPHelper, $mockedConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue