Commit 67a57117 authored by Tino Goratsch's avatar Tino Goratsch

reworked the update check

- refactored the update check into its own component
- covered the update check with unit tests

refs #62
parent a780634b
......@@ -7,46 +7,36 @@
namespace ACP3\Modules\ACP3\System\Controller\Admin\Maintenance;
use ACP3\Core;
use ACP3\Core\Controller\Context;
use ACP3\Modules\ACP3\System;
use Composer\Semver\Comparator;
/**
* Class UpdateCheck
* @package ACP3\Modules\ACP3\System\Controller\Admin\Maintenance
*/
class UpdateCheck extends Core\Controller\AbstractAdminAction
{
/**
* @return array
* @var System\Helper\UpdateCheck
*/
public function execute()
private $updateCheck;
/**
* UpdateCheck constructor.
* @param Context\FrontendContext $context
* @param System\Helper\UpdateCheck $updateCheck
*/
public function __construct(Context\FrontendContext $context, System\Helper\UpdateCheck $updateCheck)
{
$update = [];
$file = @file_get_contents('https://acp3.github.io/update.txt');
if ($file !== false) {
list($latestVersion, $url) = explode('||', $file);
$update = [
'installed_version' => Core\Application\BootstrapInterface::VERSION,
'latest_version' => $latestVersion,
'is_latest' => $this->isLatestVersion($latestVersion),
'url' => $url
];
}
parent::__construct($context);
return [
'update' => $update
];
$this->updateCheck = $updateCheck;
}
/**
* @param string $latestVersion
* @return bool
* @return array
*/
protected function isLatestVersion($latestVersion)
public function execute()
{
return Comparator::greaterThanOrEqualTo(
Core\Application\BootstrapInterface::VERSION,
$latestVersion
);
return [
'update' => $this->updateCheck->checkForNewVersion()
];
}
}
<?php
/**
* Copyright (c) by the ACP3 Developers.
* See the LICENCE file at the top-level module directory for licencing details.
*/
namespace ACP3\Modules\ACP3\System\Helper;
use ACP3\Core\Application\BootstrapInterface;
use ACP3\Core\Date;
use ACP3\Core\Settings\SettingsInterface;
use ACP3\Modules\ACP3\System\Helper\UpdateCheck\UpdateFileParser;
use ACP3\Modules\ACP3\System\Installer\Schema;
use Composer\Semver\Comparator;
class UpdateCheck
{
const UPDATE_CHECK_FILE = 'https://acp3.github.io/update.txt';
const UPDATE_CHECK_DATE_OFFSET = 86400;
/**
* @var SettingsInterface
*/
private $settings;
/**
* @var Date
*/
private $date;
/**
* @var UpdateFileParser
*/
private $updateFileParser;
/**
* UpdateCheck constructor.
* @param Date $date
* @param SettingsInterface $settings
* @param UpdateFileParser $updateFileParser
*/
public function __construct(Date $date, SettingsInterface $settings, UpdateFileParser $updateFileParser)
{
$this->settings = $settings;
$this->date = $date;
$this->updateFileParser = $updateFileParser;
}
/**
* @return array
*/
public function checkForNewVersion()
{
$settings = $this->settings->getSettings(Schema::MODULE_NAME);
if ($this->canRequestUpdateURI($settings['update_last_check'])) {
$update = $this->doUpdateCheck();
} else {
$update = [
'installed_version' => BootstrapInterface::VERSION,
'latest_version' => $settings['update_new_version'],
'is_latest' => $this->isLatestVersion($settings['update_new_version']),
'url' => $settings['update_new_version_url']
];
}
return $update;
}
/**
* @param string $lastUpdateTimestamp
* @return bool
*/
private function canRequestUpdateURI($lastUpdateTimestamp)
{
return $this->date->timestamp() - $lastUpdateTimestamp >= static::UPDATE_CHECK_DATE_OFFSET;
}
/**
* @return array
*/
private function doUpdateCheck(): array
{
try {
$data = $this->updateFileParser->parseUpdateFile(static::UPDATE_CHECK_FILE);
$update = [
'installed_version' => BootstrapInterface::VERSION,
'latest_version' => $data['latest_version'],
'is_latest' => $this->isLatestVersion($data['latest_version']),
'url' => $data['url']
];
$this->saveUpdateSettings($update);
} catch (\RuntimeException $e) {
$update = [];
}
return $update;
}
/**
* @param string $latestVersion
* @return bool
*/
private function isLatestVersion($latestVersion)
{
return Comparator::greaterThanOrEqualTo(
BootstrapInterface::VERSION,
$latestVersion
);
}
/**
* @param array $update
* @return bool
*/
private function saveUpdateSettings(array $update)
{
$data = [
'update_last_check' => $this->date->timestamp(),
'update_new_version' => $update['latest_version'],
'update_new_version_url' => $update['url'],
];
return $this->settings->saveSettings($data, Schema::MODULE_NAME);
}
}
<?php
/**
* Copyright (c) by the ACP3 Developers.
* See the LICENCE file at the top-level module directory for licencing details.
*/
namespace ACP3\Modules\ACP3\System\Helper\UpdateCheck;
class UpdateFileParser
{
/**
* @param string $path
* @return array
* @throws \RuntimeException
*/
public function parseUpdateFile($path)
{
$file = @file_get_contents($path);
if ($file !== false) {
list($latestVersion, $url) = explode('||', $file);
return [
'latest_version' => $latestVersion,
'url' => $url,
];
}
throw new \RuntimeException("Error while fetching the path {$path}.");
}
}
......@@ -2,6 +2,7 @@
namespace ACP3\Modules\ACP3\System\Installer;
use ACP3\Core\Application\BootstrapInterface;
use ACP3\Core\Modules;
/**
......@@ -164,6 +165,11 @@ class Migration extends Modules\Installer\AbstractMigration
],
69 => [
"UPDATE `{pre}acl_resources` SET `page` = 'settings' WHERE `module_id` = '{moduleId}' AND `area` = 'admin' AND `controller` = 'index' AND `page` = 'configuration';",
],
70 => [
"INSERT INTO `{pre}settings` (`id`, `module_id`, `name`, `value`) VALUES ('', '{moduleId}', 'update_last_check', '0');",
"INSERT INTO `{pre}settings` (`id`, `module_id`, `name`, `value`) VALUES ('', '{moduleId}', 'update_new_version', '" . BootstrapInterface::VERSION. "');",
"INSERT INTO `{pre}settings` (`id`, `module_id`, `name`, `value`) VALUES ('', '{moduleId}', 'update_new_version_url', '');",
]
];
}
......
......@@ -3,6 +3,7 @@
namespace ACP3\Modules\ACP3\System\Installer;
use ACP3\Core\ACL\PrivilegeEnum;
use ACP3\Core\Application\BootstrapInterface;
use ACP3\Core\Modules;
/**
......@@ -51,7 +52,7 @@ class Schema implements Modules\Installer\SchemaInterface
*/
public function getSchemaVersion()
{
return 69;
return 70;
}
/**
......@@ -131,6 +132,9 @@ class Schema implements Modules\Installer\SchemaInterface
'site_subtitle' => '',
'site_subtitle_homepage_mode' => 0,
'site_subtitle_mode' => 1,
'update_last_check' => 0,
'update_new_version' => BootstrapInterface::VERSION,
'update_new_version_url' => '',
'wysiwyg' => 'core.wysiwyg.textarea'
];
}
......
......@@ -46,4 +46,6 @@ services:
system.controller.admin.maintenance.update_check:
class: ACP3\Modules\ACP3\System\Controller\Admin\Maintenance\UpdateCheck
parent: core.controller.admin
arguments:
- '@core.context.frontend'
- '@system.helpers.update_check'
......@@ -15,3 +15,13 @@ services:
arguments:
- '@core.config'
- '%core.environment%'
system.helpers.update_check:
class: ACP3\Modules\ACP3\System\Helper\UpdateCheck
arguments:
- '@core.date'
- '@core.config'
- '@system.helpers.update_check.update_file_parser'
system.helpers.update_check.update_file_parser:
class: ACP3\Modules\ACP3\System\Helper\UpdateCheck\UpdateFileParser
<?php
/**
* Copyright (c) by the ACP3 Developers.
* See the LICENCE file at the top-level module directory for licencing details.
*/
namespace ACP3\Modules\ACP3\System\Test\Helper;
use ACP3\Core\Application\BootstrapInterface;
use ACP3\Core\Date;
use ACP3\Core\Settings\SettingsInterface;
use ACP3\Modules\ACP3\System\Helper\UpdateCheck;
class UpdateCheckTest extends \PHPUnit_Framework_TestCase
{
/**
* @var UpdateCheck
*/
private $updateCheck;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $dateMock;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $settingsMock;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $updateFileParserMock;
protected function setUp()
{
$this->setUpMockObjects();
$this->updateCheck = new UpdateCheck(
$this->dateMock,
$this->settingsMock,
$this->updateFileParserMock
);
}
private function setUpMockObjects()
{
$this->dateMock = $this->getMockBuilder(Date::class)
->disableOriginalConstructor()
->getMock();
$this->settingsMock = $this->getMockBuilder(SettingsInterface::class)
->setMethods(['getSettings', 'saveSettings'])
->getMock();
$this->updateFileParserMock = $this->getMockBuilder(UpdateCheck\UpdateFileParser::class)
->setMethods(['parseUpdateFile'])
->getMock();
}
public function testDoNotRequestUpdateURI()
{
$this->dateMock->expects($this->once())
->method('timestamp')
->willReturn((new \DateTime())->modify('-1 hour')->format('U'));
$this->settingsMock->expects($this->once())
->method('getSettings')
->willReturn([
'update_last_check' => (new \DateTime())->modify('-1 hour')->format('U'),
'update_new_version' => BootstrapInterface::VERSION,
'update_new_version_url' => 'https://foo.bar/'
]);
$this->updateFileParserMock->expects($this->never())
->method('parseUpdateFile');
$update = [
'installed_version' => BootstrapInterface::VERSION,
'latest_version' => BootstrapInterface::VERSION,
'is_latest' => true,
'url' => 'https://foo.bar/'
];
$this->assertEquals($update, $this->updateCheck->checkForNewVersion());
}
public function testDoRequestUpdateURI()
{
$this->dateMock->expects($this->exactly(2))
->method('timestamp')
->willReturn((new \DateTime())->format('U'));
$this->settingsMock->expects($this->once())
->method('getSettings')
->willReturn([
'update_last_check' => (new \DateTime())->modify('-2 days')->format('U'),
'update_new_version' => BootstrapInterface::VERSION,
'update_new_version_url' => 'https://foo.bar/'
]);
$this->settingsMock->expects($this->once())
->method('saveSettings')
->willReturn(true);
$this->updateFileParserMock->expects($this->once())
->method('parseUpdateFile')
->willReturn([
'latest_version' => BootstrapInterface::VERSION,
'url' => 'https://foobar.baz/'
]);
$update = [
'installed_version' => BootstrapInterface::VERSION,
'latest_version' => BootstrapInterface::VERSION,
'is_latest' => true,
'url' => 'https://foobar.baz/'
];
$this->assertEquals($update, $this->updateCheck->checkForNewVersion());
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment