diff --git a/net-mgmt/icingaweb2/Makefile b/net-mgmt/icingaweb2/Makefile index 2302535299ca..8a93402fde45 100644 --- a/net-mgmt/icingaweb2/Makefile +++ b/net-mgmt/icingaweb2/Makefile @@ -1,77 +1,78 @@ PORTNAME= icingaweb2 DISTVERSIONPREFIX= v DISTVERSION= 2.8.2 +PORTREVISION= 1 CATEGORIES= net-mgmt www PKGNAMESUFFIX= ${PHP_PKGNAMESUFFIX} MAINTAINER= lme@FreeBSD.org COMMENT= Next generation web interface for Icinga 1 and 2 LICENSE= GPLv2 LICENSE_FILE= ${WRKSRC}/COPYING USES= php:build,web,flavors USE_PHP= bcmath bitset ctype curl dom gd gettext hash iconv json ldap \ mbstring mcrypt memcache memcached opcache openssl pcre pdo \ pdo_sqlite posix session simplexml soap sockets sqlite3 xml \ zlib USE_GITHUB= yes GH_ACCOUNT= icinga NO_BUILD= yes NO_ARCH= yes SUB_FILES= pkg-message SUB_LIST= ETCDIR=${ETCDIR} \ EXAMPLESDIR=${EXAMPLESDIR} \ WWWDIR=${WWWDIR} PLIST_SUB= WWWGRP=${WWWGRP} \ WWWOWN=${WWWOWN} OPTIONS_DEFINE= EXAMPLES LDAP MYSQL NLS PDF PGSQL SQLITE OPTIONS_DEFAULT= LDAP MYSQL PGSQL SQLITE PDF_DESC= Export graphs as PDF PDF_RUN_DEPENDS= ${PHPBASE}/lib/php/${PHP_EXT_DIR}/imagick.so:graphics/pecl-imagick@${PHP_FLAVOR} NLS_USES= gettext NLS_USE= PHP=intl PGSQL_USE= PHP=pdo_pgsql,pgsql MYSQL_USE= PHP=mysqli,pdo_mysql SQLITE_USE= PHP=pdo_sqlite,sqlite3 LDAP_USE= PHP=ldap post-patch: ${REINPLACE_CMD} 's%\(/etc/icingaweb2\)%${PREFIX}\1%g' \ ${WRKSRC}/library/Icinga/Application/ApplicationBootstrap.php ${REINPLACE_CMD} 's%readlink[^)]*)%"${PREFIX}/bin/php"%g' \ ${WRKSRC}/application/clicommands/WebCommand.php do-install: ${MKDIR} ${STAGEDIR}${PREFIX}/etc/bash_completion.d/ ${INSTALL_DATA} ${WRKSRC}/etc/bash_completion.d/icingacli \ ${STAGEDIR}${PREFIX}/etc/bash_completion.d (cd ${WRKSRC} && ${RM} -r .mailmap changelog.py icingaweb2.spec \ bin/license_writer.py packages test) (${FIND} ${WRKSRC} -name "*.bak" -delete) ${MKDIR} ${STAGEDIR}${WWWDIR} (cd ${WRKSRC} && \ ${COPYTREE_SHARE} . ${STAGEDIR}${WWWDIR}) ${CHMOD} 755 ${STAGEDIR}${WWWDIR}/bin/icingacli ${RLN} ${STAGEDIR}${WWWDIR}/bin/icingacli ${STAGEDIR}${PREFIX}/bin/icingacli ${MKDIR} ${STAGEDIR}${ETCDIR} ${MKDIR} ${STAGEDIR}${EXAMPLESDIR}/ .for webserver in apache nginx ${MKDIR} ${STAGEDIR}${EXAMPLESDIR}/${webserver} # ICINGAWEB_CONFIGDIR is set to /nonexistent to allow staging as user. # Once installed, ${ETCDIR} is not world-readable, so point icingacli to # a non existing directory. The config files are generated correctly anyway. (cd ${STAGEDIR}${WWWDIR} && \ ICINGAWEB_CONFIGDIR=/nonexistent \ ./bin/icingacli setup config webserver ${webserver} --path=/icingaweb2 \ --root=${WWWDIR}/public --config=${ETCDIR} \ --file=${STAGEDIR}${EXAMPLESDIR}/${webserver}/icingaweb2.conf) .endfor .include diff --git a/net-mgmt/icingaweb2/files/patch-4bc5350ebaae b/net-mgmt/icingaweb2/files/patch-4bc5350ebaae new file mode 100644 index 000000000000..b3f40ec52789 --- /dev/null +++ b/net-mgmt/icingaweb2/files/patch-4bc5350ebaae @@ -0,0 +1,1054 @@ +diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml +index 4d52983ba9..4ca606ac52 100644 +diff --git a/library/Icinga/Application/ApplicationBootstrap.php b/library/Icinga/Application/ApplicationBootstrap.php +index 04cc930d20..8c72d0e513 100644 +--- library/Icinga/Application/ApplicationBootstrap.php ++++ library/Icinga/Application/ApplicationBootstrap.php +@@ -605,7 +605,7 @@ protected function setupErrorHandling() + ini_set('display_startup_errors', 1); + ini_set('display_errors', 1); + set_error_handler(function ($errno, $errstr, $errfile, $errline) { +- if (error_reporting() === 0) { ++ if (! (error_reporting() & $errno)) { + // Error was suppressed with the @-operator + return false; // Continue with the normal error handler + } +diff --git a/library/Icinga/Test/BaseTestCase.php b/library/Icinga/Test/BaseTestCase.php +index 286ec30591..aea79210fb 100644 +--- library/Icinga/Test/BaseTestCase.php ++++ library/Icinga/Test/BaseTestCase.php +@@ -23,7 +23,6 @@ function mt() + use Exception; + use RuntimeException; + use Mockery; +- use PHPUnit_Framework_TestCase; + use Icinga\Application\Icinga; + use Icinga\Data\ConfigObject; + use Icinga\Data\ResourceFactory; +@@ -32,7 +31,7 @@ function mt() + /** + * Class BaseTestCase + */ +- abstract class BaseTestCase extends PHPUnit_Framework_TestCase implements DbTest ++ abstract class BaseTestCase extends Mockery\Adapter\Phpunit\MockeryTestCase implements DbTest + { + /** + * Path to application/ +@@ -138,7 +137,7 @@ public static function setupDirectories() + /** + * Setup MVC bootstrapping and ensure that the Icinga-Mock gets reinitialized + */ +- public function setUp() ++ public function setUp(): void + { + parent::setUp(); + $this->setupIcingaMock(); +@@ -334,6 +333,23 @@ public function setupDbProvider($resource) + $adapter->exec('DROP TABLE ' . $table . ';'); + } + } ++ ++ /** ++ * Add assertMatchesRegularExpression() method for phpunit >= 8.0 < 9.0 for compatibility with PHP 7.2. ++ * ++ * @TODO Remove once PHP 7.2 support is not needed for testing anymore. ++ */ ++ public static function assertMatchesRegularExpression( ++ string $pattern, ++ string $string, ++ string $message = '' ++ ): void { ++ if (method_exists(parent::class, 'assertMatchesRegularExpression')) { ++ parent::assertMatchesRegularExpression($pattern, $string, $message); ++ } else { ++ static::assertRegExp($pattern, $string, $message); ++ } ++ } + } + + BaseTestCase::setupTimezone(); +diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimestarthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimestarthistoryQuery.php +index 831df6c818..5d0517884e 100644 +--- modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimestarthistoryQuery.php ++++ modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimestarthistoryQuery.php +@@ -96,7 +96,7 @@ protected function joinBaseTables() + array() + ); + +- if (@func_get_arg(0) === false) { ++ if (func_num_args() === 0 || func_get_arg(0) === false) { + $this->select->where( + "hdh.actual_start_time > '1970-01-02 00:00:00'" + ); +diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimestarthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimestarthistoryQuery.php +index 6ed081ef70..932d854a01 100644 +--- modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimestarthistoryQuery.php ++++ modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimestarthistoryQuery.php +@@ -97,7 +97,7 @@ protected function joinBaseTables() + array() + ); + +- if (@func_get_arg(0) === false) { ++ if (func_num_args() === 0 || func_get_arg(0) === false) { + $this->select->where( + "sdh.actual_start_time > '1970-01-02 00:00:00'" + ); +diff --git a/modules/monitoring/test/php/application/views/helpers/PluginOutputTest.php b/modules/monitoring/test/php/application/views/helpers/PluginOutputTest.php +index a07614079e..94efee939a 100644 +--- modules/monitoring/test/php/application/views/helpers/PluginOutputTest.php ++++ modules/monitoring/test/php/application/views/helpers/PluginOutputTest.php +@@ -20,7 +20,7 @@ class PluginOutputTest extends BaseTestCase + + protected static $statusTags = array('OK', 'WARNING', 'CRITICAL', 'UNKNOWN', 'UP', 'DOWN'); + +- public function setUp() ++ public function setUp(): void + { + parent::setUp(); + +@@ -45,7 +45,7 @@ protected function checkOutput($output, $html, $regexp = false, $isHtml = false) + $html, + preg_quote(self::SUFFIX, '~') + ); +- $this->assertRegExp($expect, $actual, 'Output must match example regexp'); ++ $this->assertMatchesRegularExpression($expect, $actual, 'Output must match example regexp'); + } else { + $expect = $prefix . $html . self::SUFFIX; + $this->assertEquals($expect, $actual, 'Output must match example'); +diff --git a/modules/monitoring/test/php/library/Monitoring/Plugin/PerfdataTest.php b/modules/monitoring/test/php/library/Monitoring/Plugin/PerfdataTest.php +index dbccb5c800..ab6ffa9387 100644 +--- modules/monitoring/test/php/library/Monitoring/Plugin/PerfdataTest.php ++++ modules/monitoring/test/php/library/Monitoring/Plugin/PerfdataTest.php +@@ -8,19 +8,17 @@ + + class PerfdataTest extends BaseTestCase + { +- /** +- * @expectedException \InvalidArgumentException +- */ + public function testWhetherFromStringThrowsExceptionWhenGivenAnEmptyString() + { ++ $this->expectException(\InvalidArgumentException::class); ++ + Perfdata::fromString(''); + } + +- /** +- * @expectedException \InvalidArgumentException +- */ + public function testWhetherFromStringThrowsExceptionWhenGivenAnInvalidString() + { ++ $this->expectException(\InvalidArgumentException::class); ++ + Perfdata::fromString('test'); + } + +diff --git a/modules/monitoring/test/php/library/Monitoring/Web/Rest/RestRequestTest.php b/modules/monitoring/test/php/library/Monitoring/Web/Rest/RestRequestTest.php +index e422ec0766..6e77ffcdde 100644 +--- modules/monitoring/test/php/library/Monitoring/Web/Rest/RestRequestTest.php ++++ modules/monitoring/test/php/library/Monitoring/Web/Rest/RestRequestTest.php +@@ -16,11 +16,10 @@ protected function curlExec(array $options) + + class RestRequestTest extends BaseTestCase + { +- /** +- * @expectedException \Icinga\Exception\Json\JsonDecodeException +- */ + public function testInvalidServerResponseHandling() + { ++ $this->expectException(\Icinga\Exception\Json\JsonDecodeException::class); ++ + MockedRestRequest::get('http://localhost')->send(); + } + } +diff --git a/modules/monitoring/test/php/regression/Bug7043Test.php b/modules/monitoring/test/php/regression/Bug7043Test.php +index 07cc02428f..ba9291be60 100644 +--- modules/monitoring/test/php/regression/Bug7043Test.php ++++ modules/monitoring/test/php/regression/Bug7043Test.php +@@ -24,7 +24,7 @@ public static function setModuleConfig($moduleName, $configName, $config) + + class Bug7043Test extends BaseTestCase + { +- public function tearDown() ++ public function tearDown(): void + { + parent::tearDown(); + Mockery::close(); // Necessary because some tests run in a separate process +diff --git a/modules/setup/application/views/scripts/index/index.phtml b/modules/setup/application/views/scripts/index/index.phtml +index b5fb40798f..3a6e17965f 100644 +--- modules/setup/application/views/scripts/index/index.phtml ++++ modules/setup/application/views/scripts/index/index.phtml +@@ -9,10 +9,10 @@ $currentPos = array_search($wizard->getCurrentPage(), $pages, true); + list($configPagesLeft, $configPagesRight) = array_chunk($configPages, count($configPages) / 2, true); + + $visitedPages = array_keys($wizard->getPageData()); +-$maxProgress = @max(array_keys(array_filter( ++$maxProgress = max(array_merge([0], array_keys(array_filter( + $pages, + function ($page) use ($visitedPages) { return in_array($page->getName(), $visitedPages); } +-))); ++)))); + + ?> +
+diff --git a/test/php/application/views/helpers/DateFormatTestBroken.php b/test/php/application/views/helpers/DateFormatTestBroken.php +index 188c629888..6fb768b3cd 100644 +--- test/php/application/views/helpers/DateFormatTestBroken.php ++++ test/php/application/views/helpers/DateFormatTestBroken.php +@@ -12,7 +12,7 @@ + + class DateFormatTest extends BaseTestCase + { +- public function tearDown() ++ public function tearDown(): void + { + DateTimeFactory::setConfig(array('timezone' => date_default_timezone_get())); + } +diff --git a/test/php/bootstrap.php b/test/php/bootstrap.php +index 75912674c2..6f78711720 100644 +--- test/php/bootstrap.php ++++ test/php/bootstrap.php +@@ -36,10 +36,6 @@ + + require_once($icingaLibPath . '/Test/ClassLoader.php'); + +-if (! class_exists('PHPUnit_Framework_TestCase')) { +- require_once __DIR__ . '/phpunit-compat.php'; +-} +- + $loader = new Icinga\Test\ClassLoader(); + $loader->registerNamespace('Tests', $testLibraryPath); + $loader->registerNamespace('Icinga', $icingaLibPath); +diff --git a/test/php/library/Icinga/Application/ClassLoaderTest.php b/test/php/library/Icinga/Application/ClassLoaderTest.php +index 5422869125..7b88c6e74a 100644 +--- test/php/library/Icinga/Application/ClassLoaderTest.php ++++ test/php/library/Icinga/Application/ClassLoaderTest.php +@@ -26,7 +26,7 @@ public function testFlag() + + EOD; + +- public function setUp() ++ public function setUp(): void + { + parent::setUp(); + $tempDir = sys_get_temp_dir(); +@@ -35,7 +35,7 @@ public function setUp() + file_put_contents($this->baseDir. self::$classFile, self::$classContent); + } + +- public function tearDown() ++ public function tearDown(): void + { + parent::tearDown(); + system('rm -rf '. $this->baseDir); +diff --git a/test/php/library/Icinga/Application/ConfigTest.php b/test/php/library/Icinga/Application/ConfigTest.php +index e47173679c..5fb47639a6 100644 +--- test/php/library/Icinga/Application/ConfigTest.php ++++ test/php/library/Icinga/Application/ConfigTest.php +@@ -11,7 +11,7 @@ class ConfigTest extends BaseTestCase + /** + * Set up config dir + */ +- public function setUp() ++ public function setUp(): void + { + parent::setUp(); + $this->oldConfigDir = Config::$configDir; +@@ -21,7 +21,7 @@ public function setUp() + /** + * Reset config dir + */ +- public function tearDown() ++ public function tearDown(): void + { + parent::tearDown(); + Config::$configDir = $this->oldConfigDir; +@@ -185,11 +185,10 @@ public function testWhetherConfigKnowsWhichSectionsItHas() + ); + } + +- /** +- * @expectedException UnexpectedValueException +- */ + public function testWhetherAnExceptionIsThrownWhenTryingToAccessASectionPropertyOnANonSection() + { ++ $this->expectException(\UnexpectedValueException::class); ++ + $config = Config::fromArray(array('a' => 'b')); + $config->get('a', 'b'); + } +@@ -234,11 +233,10 @@ public function testWhetherItIsPossibleToInitializeAConfigFromAIniFile() + ); + } + +- /** +- * @expectedException Icinga\Exception\NotReadableError +- */ + public function testWhetherFromIniThrowsAnExceptionOnInsufficientPermission() + { ++ $this->expectException(\Icinga\Exception\NotReadableError::class); ++ + Config::fromIni('/etc/shadow'); + } + +diff --git a/test/php/library/Icinga/Application/Hook/AuditHookTest.php b/test/php/library/Icinga/Application/Hook/AuditHookTest.php +index 8f0a12507b..14ca43792c 100644 +--- test/php/library/Icinga/Application/Hook/AuditHookTest.php ++++ test/php/library/Icinga/Application/Hook/AuditHookTest.php +@@ -32,27 +32,24 @@ public function testFormatMessageResolvesParametersWithSingleBraces() + $this->assertEquals('foo', (new TestAuditHook())->formatMessage('{{te{.}st}}', ['te{' => ['}st' => 'foo']])); + } + +- /** +- * @expectedException \InvalidArgumentException +- */ + public function testFormatMessageComplainsAboutUnresolvedParameters() + { ++ $this->expectException(\InvalidArgumentException::class); ++ + (new TestAuditHook())->formatMessage('{{missing}}', []); + } + +- /** +- * @expectedException \InvalidArgumentException +- */ + public function testFormatMessageComplainsAboutNonScalarParameters() + { ++ $this->expectException(\InvalidArgumentException::class); ++ + (new TestAuditHook())->formatMessage('{{test}}', ['test' => ['foo' => 'bar']]); + } + +- /** +- * @expectedException \InvalidArgumentException +- */ + public function testFormatMessageComplainsAboutNonArrayParameters() + { ++ $this->expectException(\InvalidArgumentException::class); ++ + (new TestAuditHook())->formatMessage('{{test.foo}}', ['test' => 'foo']); + } + } +diff --git a/test/php/library/Icinga/Data/ConfigObjectTest.php b/test/php/library/Icinga/Data/ConfigObjectTest.php +index 9b87019bf6..f6b577b034 100644 +--- test/php/library/Icinga/Data/ConfigObjectTest.php ++++ test/php/library/Icinga/Data/ConfigObjectTest.php +@@ -115,11 +115,10 @@ public function testWhetherItIsPossibleToSetPropertiesAndSections() + ); + } + +- /** +- * @expectedException \Icinga\Exception\ProgrammingError +- */ + public function testWhetherItIsNotPossibleToAppendProperties() + { ++ $this->expectException(\Icinga\Exception\ProgrammingError::class); ++ + $config = new ConfigObject(); + $config[] = 'test'; + } +diff --git a/test/php/library/Icinga/Data/DataArray/ArrayDatasourceTest.php b/test/php/library/Icinga/Data/DataArray/ArrayDatasourceTest.php +index 5ad2b1b091..7e715cac2e 100644 +--- test/php/library/Icinga/Data/DataArray/ArrayDatasourceTest.php ++++ test/php/library/Icinga/Data/DataArray/ArrayDatasourceTest.php +@@ -10,7 +10,7 @@ class ArrayDatasourceTest extends BaseTestCase + { + private $sampleData; + +- public function setUp() ++ public function setUp(): void + { + parent::setUp(); + $this->sampleData = array( +diff --git a/test/php/library/Icinga/Data/Filter/FilterTest.php b/test/php/library/Icinga/Data/Filter/FilterTest.php +index 97133a2163..9bbff01a32 100644 +--- test/php/library/Icinga/Data/Filter/FilterTest.php ++++ test/php/library/Icinga/Data/Filter/FilterTest.php +@@ -61,7 +61,7 @@ class FilterTest extends BaseTestCase + + private $sampleData; + +- public function setUp() ++ public function setUp(): void + { + parent::setUp(); + $this->sampleData = array( +diff --git a/test/php/library/Icinga/File/Ini/IniParserTest.php b/test/php/library/Icinga/File/Ini/IniParserTest.php +index 5a1d7df906..b945cc44e1 100644 +--- test/php/library/Icinga/File/Ini/IniParserTest.php ++++ test/php/library/Icinga/File/Ini/IniParserTest.php +@@ -12,13 +12,13 @@ class IniParserTest extends BaseTestCase + { + protected $tempFile; + +- public function setUp() ++ public function setUp(): void + { + parent::setUp(); + $this->tempFile = tempnam(sys_get_temp_dir(), 'icinga-ini-parser-test'); + } + +- public function tearDown() ++ public function tearDown(): void + { + parent::tearDown(); + unlink($this->tempFile); +diff --git a/test/php/library/Icinga/File/Ini/IniWriterTest.php b/test/php/library/Icinga/File/Ini/IniWriterTest.php +index c3fb6df1fc..41e1f13e67 100644 +--- test/php/library/Icinga/File/Ini/IniWriterTest.php ++++ test/php/library/Icinga/File/Ini/IniWriterTest.php +@@ -12,7 +12,7 @@ class IniWriterTest extends BaseTestCase + protected $tempFile; + protected $tempFile2; + +- public function setUp() ++ public function setUp(): void + { + parent::setUp(); + +@@ -20,7 +20,7 @@ public function setUp() + $this->tempFile2 = tempnam(sys_get_temp_dir(), 'icinga-ini-writer-test-2'); + } + +- public function tearDown() ++ public function tearDown(): void + { + parent::tearDown(); + +@@ -275,7 +275,7 @@ public function testWhetherLinebreaksAreProcessed() + ); + + $rendered = $writer->render(); +- $this->assertRegExp( ++ $this->assertMatchesRegularExpression( + '~linebreak\\\\nin line~', + $rendered, + 'newlines in values are not escaped' +@@ -322,11 +322,10 @@ public function testSectionNameEscaping() + ); + } + +- /** +- * @expectedException \Icinga\Exception\ConfigurationError +- */ + public function testWhetherBracketsAreIllegalInSectionNames() + { ++ $this->expectException(\Icinga\Exception\ConfigurationError::class); ++ + $config = Config::fromArray(['section [brackets]' => []]); + (new IniWriter($config, $this->tempFile))->write(); + } +@@ -419,7 +418,7 @@ public function testWhetherNullValuesGetPersisted() + $config->setSection('garbage', $section); + + $iniWriter = new IniWriter($config, '/dev/null'); +- $this->assertNotContains( ++ $this->assertStringNotContainsString( + 'foobar', + $iniWriter->render(), + 'IniWriter persists section keys with null values' +@@ -434,7 +433,7 @@ public function testWhetherEmptyValuesGetPersisted() + $config->setSection('garbage', $section); + + $iniWriter = new IniWriter($config, '/dev/null'); +- $this->assertContains( ++ $this->assertStringContainsString( + 'foobar', + $iniWriter->render(), + 'IniWriter doesn\'t persist section keys with empty values' +@@ -451,7 +450,7 @@ public function testExplicitRemove() + $section = $config->getSection('garbage'); + $section->foobar = null; + $iniWriter = new IniWriter($config, $filename); +- $this->assertNotContains( ++ $this->assertStringNotContainsString( + 'foobar', + $iniWriter->render(), + 'IniWriter doesn\'t remove section keys with null values' +diff --git a/test/php/library/Icinga/File/Storage/LocalFileStorageTest.php b/test/php/library/Icinga/File/Storage/LocalFileStorageTest.php +index 5f104a50c4..7ba0efd2f1 100644 +--- test/php/library/Icinga/File/Storage/LocalFileStorageTest.php ++++ test/php/library/Icinga/File/Storage/LocalFileStorageTest.php +@@ -54,11 +54,10 @@ public function testGetIterator() + static::assertSame(array('foobar'), array_values(iterator_to_array($lfs->getIterator()))); + } + +- /** +- * @expectedException \Icinga\Exception\NotReadableError +- */ + public function testGetIteratorThrowsNotReadableError() + { ++ $this->expectException(\Icinga\Exception\NotReadableError::class); ++ + $lfs = new LocalFileStorage('/notreadabledirectory'); + $lfs->getIterator(); + } +@@ -79,21 +78,19 @@ public function testCreate() + static::assertSame('Hello world!', $lfs->read('foo/bar')); + } + +- /** +- * @expectedException \Icinga\Exception\AlreadyExistsException +- */ + public function testCreateThrowsAlreadyExistsException() + { ++ $this->expectException(\Icinga\Exception\AlreadyExistsException::class); ++ + $lfs = new TemporaryLocalFileStorage(); + $lfs->create('foobar', 'Hello world!'); + $lfs->create('foobar', 'Hello world!'); + } + +- /** +- * @expectedException \Icinga\Exception\NotWritableError +- */ + public function testCreateThrowsNotWritableError() + { ++ $this->expectException(\Icinga\Exception\NotWritableError::class); ++ + $lfs = new LocalFileStorage('/notwritabledirectory'); + $lfs->create('foobar', 'Hello world!'); + } +@@ -105,20 +102,18 @@ public function testRead() + static::assertSame('Hello world!', $lfs->read('foobar')); + } + +- /** +- * @expectedException \Icinga\Exception\NotFoundError +- */ + public function testReadThrowsNotFoundError() + { ++ $this->expectException(\Icinga\Exception\NotFoundError::class); ++ + $lfs = new TemporaryLocalFileStorage(); + $lfs->read('foobar'); + } + +- /** +- * @expectedException \Icinga\Exception\NotReadableError +- */ + public function testReadThrowsNotReadableError() + { ++ $this->expectException(\Icinga\Exception\NotReadableError::class); ++ + $lfs = new TemporaryLocalFileStorage(); + $lfs->create('foobar', 'Hello world!'); + chmod($lfs->resolvePath('foobar'), 0); +@@ -133,20 +128,18 @@ public function testUpdate() + static::assertSame('Hello universe!', $lfs->read('foobar')); + } + +- /** +- * @expectedException \Icinga\Exception\NotFoundError +- */ + public function testUpdateThrowsNotFoundError() + { ++ $this->expectException(\Icinga\Exception\NotFoundError::class); ++ + $lfs = new TemporaryLocalFileStorage(); + $lfs->update('foobar', 'Hello universe!'); + } + +- /** +- * @expectedException \Icinga\Exception\NotWritableError +- */ + public function testUpdateThrowsNotWritableError() + { ++ $this->expectException(\Icinga\Exception\NotWritableError::class); ++ + $lfs = new TemporaryLocalFileStorage(); + $lfs->create('foobar', 'Hello world!'); + chmod($lfs->resolvePath('foobar'), 0); +@@ -161,20 +154,18 @@ public function testDelete() + static::assertFalse($lfs->has('foobar')); + } + +- /** +- * @expectedException \Icinga\Exception\NotFoundError +- */ + public function testDeleteThrowsNotFoundError() + { ++ $this->expectException(\Icinga\Exception\NotFoundError::class); ++ + $lfs = new TemporaryLocalFileStorage(); + $lfs->delete('foobar'); + } + +- /** +- * @expectedException \Icinga\Exception\NotWritableError +- */ + public function testDeleteThrowsNotWritableError() + { ++ $this->expectException(\Icinga\Exception\NotWritableError::class); ++ + $lfs = new TemporaryLocalFileStorage(); + $lfs->create('foobar', 'Hello world!'); + +@@ -204,20 +195,18 @@ public function testResolvePathAssertExistence() + $lfs->resolvePath('./notRelevant/../foobar', true); + } + +- /** +- * @expectedException \Icinga\Exception\NotFoundError +- */ + public function testResolvePathThrowsNotFoundError() + { ++ $this->expectException(\Icinga\Exception\NotFoundError::class); ++ + $lfs = new TemporaryLocalFileStorage(); + $lfs->resolvePath('foobar', true); + } + +- /** +- * @expectedException \InvalidArgumentException +- */ + public function testResolvePathThrowsInvalidArgumentException() + { ++ $this->expectException(\InvalidArgumentException::class); ++ + $lfs = new LocalFileStorage('/notreadabledirectory'); + $lfs->resolvePath('../foobar'); + } +diff --git a/test/php/library/Icinga/Logger/Writer/StreamWriterTest.php b/test/php/library/Icinga/Logger/Writer/StreamWriterTest.php +index 38ff4dc0d0..dfd356248a 100644 +--- test/php/library/Icinga/Logger/Writer/StreamWriterTest.php ++++ test/php/library/Icinga/Logger/Writer/StreamWriterTest.php +@@ -10,14 +10,14 @@ + + class StreamWriterTest extends BaseTestCase + { +- public function setUp() ++ public function setUp(): void + { + parent::setUp(); + + $this->target = tempnam(sys_get_temp_dir(), 'log'); + } + +- public function tearDown() ++ public function tearDown(): void + { + parent::tearDown(); + +@@ -38,6 +38,6 @@ public function testWhetherStreamWriterWritesMessages() + $writer = new FileWriter(new ConfigObject(array('file' => $this->target))); + $writer->log(Logger::ERROR, 'This is a test error'); + $log = file_get_contents($this->target); +- $this->assertContains('This is a test error', $log, 'StreamWriter does not write log messages'); ++ $this->assertStringContainsString('This is a test error', $log, 'StreamWriter does not write log messages'); + } + } +diff --git a/test/php/library/Icinga/Test/BaseTestCaseTest.php b/test/php/library/Icinga/Test/BaseTestCaseTest.php +index 8611dbe620..5c06ad9662 100644 +--- test/php/library/Icinga/Test/BaseTestCaseTest.php ++++ test/php/library/Icinga/Test/BaseTestCaseTest.php +@@ -10,7 +10,7 @@ class BaseTestCaseTest extends BaseTestCase + { + protected $emptySqlDumpFile; + +- public function tearDown() ++ public function tearDown(): void + { + parent::tearDown(); + +@@ -148,19 +148,17 @@ public function testWhetherSetupDbProviderCleansUpOciAdapter($resource) + $this->assertCount(0, $tables); + } + +- /** +- * @expectedException RuntimeException +- */ + public function testWhetherLoadSqlThrowsErrorWhenFileMissing() + { ++ $this->expectException(\RuntimeException::class); ++ + $this->loadSql(Mockery::mock('Icinga\Data\Db\DbConnection'), 'not_existing'); + } + +- /** +- * @expectedException RuntimeException +- */ + public function testWhetherLoadSqlThrowsErrorWhenFileEmpty() + { ++ $this->expectException(\RuntimeException::class); ++ + $this->emptySqlDumpFile = tempnam(sys_get_temp_dir(), 'icinga2-web-db-test-empty'); + $this->loadSql(Mockery::mock('Icinga\Data\Db\DbConnection'), $this->emptySqlDumpFile); + } +diff --git a/test/php/library/Icinga/User/Store/DbStoreTest.php b/test/php/library/Icinga/User/Store/DbStoreTest.php +index 54855cf60a..1f56f93cbe 100644 +--- test/php/library/Icinga/User/Store/DbStoreTest.php ++++ test/php/library/Icinga/User/Store/DbStoreTest.php +@@ -83,11 +83,10 @@ public function testWhetherPreferenceInsertionWorks() + $this->assertEmpty($dbMock->deletions, 'DbStore::save deletes *new* preferences'); + } + +- /** +- * @expectedException \Icinga\Exception\NotWritableError +- */ + public function testWhetherPreferenceInsertionThrowsNotWritableError() + { ++ $this->expectException(\Icinga\Exception\NotWritableError::class); ++ + $store = $this->getStore(new FaultyDatabaseMock()); + $store->save( + Mockery::mock( +@@ -114,11 +113,10 @@ public function testWhetherPreferenceUpdatesWork() + $this->assertEmpty($dbMock->deletions, 'DbStore::save inserts *existing* preferneces'); + } + +- /** +- * @expectedException \Icinga\Exception\NotWritableError +- */ + public function testWhetherPreferenceUpdatesThrowNotWritableError() + { ++ $this->expectException(\Icinga\Exception\NotWritableError::class); ++ + $store = $this->getStore(new FaultyDatabaseMock()); + $store->setPreferences(array('testsection' => array('key' => 'value'))); + $store->save( +@@ -146,11 +144,10 @@ public function testWhetherPreferenceDeletionWorks() + $this->assertEmpty($dbMock->updates, 'DbStore::save updates *removed* preferences'); + } + +- /** +- * @expectedException \Icinga\Exception\NotWritableError +- */ + public function testWhetherPreferenceDeletionThrowsNotWritableError() + { ++ $this->expectException(\Icinga\Exception\NotWritableError::class); ++ + $store = $this->getStore(new FaultyDatabaseMock()); + $store->setPreferences(array('testsection' => array('key' => 'value'))); + $store->save( +diff --git a/test/php/library/Icinga/UserTest.php b/test/php/library/Icinga/UserTest.php +index 7798aee501..a5f7ebdb1b 100644 +--- test/php/library/Icinga/UserTest.php ++++ test/php/library/Icinga/UserTest.php +@@ -52,11 +52,10 @@ public function testWhetherValidEmailsCanBeSet() + ); + } + +- /** +- * @expectedException \InvalidArgumentException +- */ + public function testWhetherInvalidEmailsCannotBeSet() + { ++ $this->expectException(\InvalidArgumentException::class); ++ + $user = new User('unittest'); + $user->setEmail('mySampleEmail at someDomain dot org'); + } +diff --git a/test/php/library/Icinga/Util/FileTest.php b/test/php/library/Icinga/Util/FileTest.php +index 68074a5d34..d05be2bec6 100644 +--- test/php/library/Icinga/Util/FileTest.php ++++ test/php/library/Icinga/Util/FileTest.php +@@ -8,20 +8,18 @@ + + class FileTest extends BaseTestCase + { +- /** +- * @expectedException \Icinga\Exception\NotWritableError +- */ + public function testWhetherWritingToNonWritableFilesThrowsAnException() + { ++ $this->expectException(\Icinga\Exception\NotWritableError::class); ++ + $file = new File('/dev/null'); + $file->fwrite('test'); + } + +- /** +- * @expectedException \Icinga\Exception\NotWritableError +- */ + public function testWhetherTruncatingNonWritableFilesThrowsAnException() + { ++ $this->expectException(\Icinga\Exception\NotWritableError::class); ++ + $file = new File('/dev/null'); + $file->ftruncate(0); + } +diff --git a/test/php/library/Icinga/Util/TranslatorTest.php b/test/php/library/Icinga/Util/TranslatorTest.php +index d61ea4a9da..1ebb7b4141 100644 +--- test/php/library/Icinga/Util/TranslatorTest.php ++++ test/php/library/Icinga/Util/TranslatorTest.php +@@ -17,7 +17,7 @@ public static function getAvailableLocaleCodes() + + class TranslatorTest extends BaseTestCase + { +- public function setUp() ++ public function setUp(): void + { + parent::setUp(); + Translator::registerDomain('icingatest', BaseTestCase::$testDir . '/res/locale'); +@@ -48,11 +48,10 @@ public function testWhetherSetupLocaleSetsUpTheGivenLocale() + ); + } + +- /** +- * @expectedException Icinga\Exception\IcingaException +- */ + public function testWhetherSetupLocaleThrowsAnExceptionWhenGivenAnInvalidLocale() + { ++ $this->expectException(\Icinga\Exception\IcingaException::class); ++ + Translator::setupLocale('foobar'); + } + +diff --git a/test/php/library/Icinga/Web/FormTest.php b/test/php/library/Icinga/Web/FormTest.php +index 3d6ac7d1e9..b43efe452d 100644 +--- test/php/library/Icinga/Web/FormTest.php ++++ test/php/library/Icinga/Web/FormTest.php +@@ -26,7 +26,7 @@ public function onSuccess() + + class FormTest extends BaseTestCase + { +- public function tearDown() ++ public function tearDown(): void + { + Mockery::close(); // Necessary as some tests are running isolated + } +@@ -251,11 +251,10 @@ public function testWhetherGetNameReturnsTheEscapedClassNameByDefault() + ); + } + +- /** +- * @expectedException \Icinga\Exception\ProgrammingError +- */ + public function testWhetherTheOnSuccessOptionMustBeCallable() + { ++ $this->expectException(\Icinga\Exception\ProgrammingError::class); ++ + new Form(array('onSuccess' => '_invalid_')); + } + +diff --git a/test/php/library/Icinga/Web/HookTest.php b/test/php/library/Icinga/Web/HookTest.php +index fdee11636a..111b6be379 100644 +--- test/php/library/Icinga/Web/HookTest.php ++++ test/php/library/Icinga/Web/HookTest.php +@@ -41,14 +41,14 @@ class HookTest extends BaseTestCase + protected $failingHook = '\\Tests\\Icinga\\Web\\FailingHook'; + protected $testBaseClass = '\\Icinga\\Web\\Hook\\TestHook'; + +- public function setUp() ++ public function setUp(): void + { + $this->markTestSkipped(); + parent::setUp(); + Hook::clean(); + } + +- public function tearDown() ++ public function tearDown(): void + { + parent::tearDown(); + Hook::clean(); +diff --git a/test/php/library/Icinga/Web/Paginator/ScrollingStyle/SlidingWithBorderTest.php b/test/php/library/Icinga/Web/Paginator/ScrollingStyle/SlidingWithBorderTest.php +index 93fc2b171e..96d2ec65ce 100644 +--- test/php/library/Icinga/Web/Paginator/ScrollingStyle/SlidingWithBorderTest.php ++++ test/php/library/Icinga/Web/Paginator/ScrollingStyle/SlidingWithBorderTest.php +@@ -17,7 +17,7 @@ public function testGetPages2() + $paginator = new Zend_Paginator($this->getPaginatorAdapter()); + + $pages = $scrollingStyle->getPages($paginator); +- $this->assertInternalType('array', $pages); ++ $this->assertIsArray($pages); + $this->assertCount(10, $pages); + $this->assertEquals('...', $pages[8]); + } +@@ -29,7 +29,7 @@ public function testGetPages3() + $paginator->setCurrentPageNumber(9); + + $pages = $scrollingStyle->getPages($paginator); +- $this->assertInternalType('array', $pages); ++ $this->assertIsArray($pages); + $this->assertCount(10, $pages); + $this->assertEquals('...', $pages[3]); + $this->assertEquals('...', $pages[12]); +diff --git a/test/php/library/Icinga/Web/Session/SessionNamespaceTest.php b/test/php/library/Icinga/Web/Session/SessionNamespaceTest.php +index 99f08317de..7f90b2cd60 100644 +--- test/php/library/Icinga/Web/Session/SessionNamespaceTest.php ++++ test/php/library/Icinga/Web/Session/SessionNamespaceTest.php +@@ -62,11 +62,10 @@ public function testPropertyAccess() + $this->assertNull($ns->get('key2')); + } + +- /** +- * @expectedException Icinga\Exception\IcingaException +- */ + public function testFailingPropertyAccess() + { ++ $this->expectException(\Icinga\Exception\IcingaException::class); ++ + $ns = new SessionNamespace(); + $ns->missing; + } +diff --git a/test/php/library/Icinga/Web/UrlTest.php b/test/php/library/Icinga/Web/UrlTest.php +index 31c790c998..cbe8b6bf87 100644 +--- test/php/library/Icinga/Web/UrlTest.php ++++ test/php/library/Icinga/Web/UrlTest.php +@@ -130,11 +130,10 @@ public function testWhetherFromRequestAcceptsAdditionalParameters() + ); + } + +- /** +- * @expectedException Icinga\Exception\ProgrammingError +- */ + public function testWhetherFromPathProperlyHandlesInvalidUrls() + { ++ $this->expectException(\Icinga\Exception\ProgrammingError::class); ++ + Url::fromPath(null); + } + +diff --git a/test/php/library/Icinga/Web/Widget/DashboardTest.php b/test/php/library/Icinga/Web/Widget/DashboardTest.php +index 260e5b494b..3749bc8ef9 100644 +--- test/php/library/Icinga/Web/Widget/DashboardTest.php ++++ test/php/library/Icinga/Web/Widget/DashboardTest.php +@@ -39,7 +39,7 @@ public function getTabs() + + class DashboardTest extends BaseTestCase + { +- public function tearDown() ++ public function tearDown(): void + { + parent::tearDown(); + Mockery::close(); // Necessary because some tests run in a separate process +@@ -109,11 +109,12 @@ public function testWhetherGetPaneReturnsAPaneByName() + } + + /** +- * @expectedException \Icinga\Exception\ProgrammingError + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherGetPaneThrowsAnExceptionOnNotExistentPaneName() + { ++ $this->expectException(\Icinga\Exception\ProgrammingError::class); ++ + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + +@@ -267,11 +268,10 @@ public function testWhetherSetDashletUrlUpdatesTheDashletUrl() + ); + } + +- /** +- * @expectedException \Icinga\Exception\ConfigurationError +- */ + public function testWhetherDetermineActivePaneThrowsAnExceptionIfCouldNotDetermine() + { ++ $this->expectException(\Icinga\Exception\ConfigurationError::class); ++ + $dashboard = new Dashboard(); + $dashboard->determineActivePane(); + } +@@ -279,11 +279,12 @@ public function testWhetherDetermineActivePaneThrowsAnExceptionIfCouldNotDetermi + /** + * @runInSeparateProcess + * @preserveGlobalState disabled +- * @expectedException \Icinga\Exception\ProgrammingError + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherDetermineActivePaneThrowsAnExceptionIfCouldNotDetermineInvalidPane() + { ++ $this->expectException(\Icinga\Exception\ProgrammingError::class); ++ + $dashboard = new DashboardWithPredefinableActiveName(); + $dashboard->createPane('test1'); + +diff --git a/test/php/library/Icinga/Web/Widget/SearchDashboardTest.php b/test/php/library/Icinga/Web/Widget/SearchDashboardTest.php +index 7370338e5c..916ab79143 100644 +--- test/php/library/Icinga/Web/Widget/SearchDashboardTest.php ++++ test/php/library/Icinga/Web/Widget/SearchDashboardTest.php +@@ -11,7 +11,7 @@ + + class SearchDashboardTest extends BaseTestCase + { +- public function setUp() ++ public function setUp(): void + { + $moduleMock = Mockery::mock('Icinga\Application\Modules\Module'); + $searchUrl = (object) array( +@@ -32,11 +32,10 @@ public function setUp() + $bootstrapMock->shouldReceive('getModuleManager')->andReturn($moduleManagerMock); + } + +- /** +- * @expectedException Zend_Controller_Action_Exception +- */ + public function testWhetherRenderThrowsAnExceptionWhenHasNoDashlets() + { ++ $this->expectException(\Zend_Controller_Action_Exception::class); ++ + $user = new User('test'); + $user->setPermissions(array('*' => '*')); + $dashboard = new SearchDashboard(); +diff --git a/test/php/phpunit-compat.php b/test/php/phpunit-compat.php +deleted file mode 100644 +index 88287906d6..0000000000 +--- test/php/phpunit-compat.php ++++ /dev/null +@@ -1,15 +0,0 @@ +-assertContains('Bug4102Test', $class); +- } +- +- /** +- * Test namespace to match definition +- */ +- public function testNamespace() +- { +- $namespace = __NAMESPACE__; +- $this->assertEquals('Tests\Icinga\Regression', $namespace); +- } +- +- /** +- * Test phpunit inheritance +- */ +- public function testInheritance() +- { +- $this->assertInstanceOf('\PHPUnit_Framework_TestCase', $this); +- } +-} diff --git a/net-mgmt/icingaweb2/files/patch-dc7a8c8d8b6e b/net-mgmt/icingaweb2/files/patch-dc7a8c8d8b6e new file mode 100644 index 000000000000..5106a8c9a90a --- /dev/null +++ b/net-mgmt/icingaweb2/files/patch-dc7a8c8d8b6e @@ -0,0 +1,193 @@ +diff --git a/library/vendor/JShrink/Minifier.php b/library/vendor/JShrink/Minifier.php +index 171a7b6c22..fad43d2127 100644 +--- library/vendor/JShrink/Minifier.php ++++ library/vendor/JShrink/Minifier.php +@@ -38,6 +38,13 @@ class Minifier + */ + protected $input; + ++ /** ++ * Length of input javascript. ++ * ++ * @var int ++ */ ++ protected $len = 0; ++ + /** + * The location of the character (in the input string) that is next to be + * processed. +@@ -77,7 +84,7 @@ class Minifier + /** + * These characters are used to define strings. + */ +- protected $stringDelimiters = ['\'', '"', '`']; ++ protected $stringDelimiters = ['\'' => true, '"' => true, '`' => true]; + + /** + * Contains the default options for minification. This array is merged with +@@ -86,7 +93,7 @@ class Minifier + * + * @var array + */ +- protected static $defaultOptions = array('flaggedComments' => true); ++ protected static $defaultOptions = ['flaggedComments' => true]; + + /** + * Contains lock ids which are used to replace certain code patterns and +@@ -94,7 +101,7 @@ class Minifier + * + * @var array + */ +- protected $locks = array(); ++ protected $locks = []; + + /** + * Takes a string containing javascript and removes unneeded characters in +@@ -105,7 +112,7 @@ class Minifier + * @throws \Exception + * @return bool|string + */ +- public static function minify($js, $options = array()) ++ public static function minify($js, $options = []) + { + try { + ob_start(); +@@ -157,21 +164,34 @@ protected function minifyDirectToOutput($js, $options) + protected function initialize($js, $options) + { + $this->options = array_merge(static::$defaultOptions, $options); +- $js = str_replace("\r\n", "\n", $js); +- $js = str_replace('/**/', '', $js); +- $this->input = str_replace("\r", "\n", $js); ++ $this->input = str_replace(["\r\n", '/**/', "\r"], ["\n", "", "\n"], $js); + + // We add a newline to the end of the script to make it easier to deal + // with comments at the bottom of the script- this prevents the unclosed + // comment error that can otherwise occur. + $this->input .= PHP_EOL; + ++ // save input length to skip calculation every time ++ $this->len = strlen($this->input); ++ + // Populate "a" with a new line, "b" with the first character, before + // entering the loop + $this->a = "\n"; + $this->b = $this->getReal(); + } + ++ /** ++ * Characters that can't stand alone preserve the newline. ++ * ++ * @var array ++ */ ++ protected $noNewLineCharacters = [ ++ '(' => true, ++ '-' => true, ++ '+' => true, ++ '[' => true, ++ '@' => true]; ++ + /** + * The primary action occurs here. This function loops through the input string, + * outputting anything that's relevant and discarding anything that is not. +@@ -183,7 +203,7 @@ protected function loop() + // new lines + case "\n": + // if the next line is something that can't stand alone preserve the newline +- if ($this->b !== false && strpos('(-+[@', $this->b) !== false) { ++ if ($this->b !== false && isset($this->noNewLineCharacters[$this->b])) { + echo $this->a; + $this->saveString(); + break; +@@ -226,7 +246,7 @@ protected function loop() + break; + } + +- // no break ++ // no break + default: + // check for some regex that breaks stuff + if ($this->a === '/' && ($this->b === '\'' || $this->b === '"')) { +@@ -243,7 +263,7 @@ protected function loop() + // do reg check of doom + $this->b = $this->getReal(); + +- if (($this->b == '/' && strpos('(,=:[!&|?*+-%', $this->a) !== false)) { ++ if (($this->b == '/' && strpos('(,=:[!&|?', $this->a) !== false)) { + $this->saveRegex(); + } + } +@@ -257,6 +277,7 @@ protected function loop() + protected function clean() + { + unset($this->input); ++ $this->len = 0; + $this->index = 0; + $this->a = $this->b = ''; + unset($this->c); +@@ -276,7 +297,7 @@ protected function getChar() + unset($this->c); + } else { + // Otherwise we start pulling from the input. +- $char = substr($this->input, $this->index, 1); ++ $char = $this->index < $this->len ? $this->input[$this->index] : false; + + // If the next character doesn't exist return false. + if (isset($char) && $char === false) { +@@ -289,7 +310,7 @@ protected function getChar() + + // Normalize all whitespace except for the newline character into a + // standard space. +- if ($char !== "\n" && ord($char) < 32) { ++ if ($char !== "\n" && $char < "\x20") { + return ' '; + } + +@@ -340,7 +361,7 @@ protected function getReal() + */ + protected function processOneLineComments($startIndex) + { +- $thirdCommentString = substr($this->input, $this->index, 1); ++ $thirdCommentString = $this->index < $this->len ? $this->input[$this->index] : false; + + // kill rest of line + $this->getNext("\n"); +@@ -429,7 +450,7 @@ protected function getNext($string) + $this->index = $pos; + + // Return the first character of that string. +- return substr($this->input, $this->index, 1); ++ return $this->index < $this->len ? $this->input[$this->index] : false; + } + + /** +@@ -447,7 +468,7 @@ protected function saveString() + $this->a = $this->b; + + // If this isn't a string we don't need to do anything. +- if (!in_array($this->a, $this->stringDelimiters)) { ++ if (!isset($this->stringDelimiters[$this->a])) { + return; + } + +@@ -557,7 +578,7 @@ protected function lock($js) + /* lock things like "asd" + ++x; */ + $lock = '"LOCK---' . crc32(time()) . '"'; + +- $matches = array(); ++ $matches = []; + preg_match('/([+-])(\s+)([+-])/S', $js, $matches); + if (empty($matches)) { + return $js; +diff --git a/library/vendor/JShrink/SOURCE b/library/vendor/JShrink/SOURCE +index 794856288e..00ccc58ce0 100644 +--- library/vendor/JShrink/SOURCE ++++ library/vendor/JShrink/SOURCE +@@ -1,6 +1,6 @@ + #!/bin/bash + set -eux +-VERSION=1.3.2 ++VERSION=1.4.0 + curl -LsS https://github.com/tedious/JShrink/archive/v"$VERSION".tar.gz -o /tmp/JShrink.tar.gz + tar xzf /tmp/JShrink.tar.gz --strip-components 1 JShrink-"$VERSION"/LICENSE + tar xzf /tmp/JShrink.tar.gz --strip-components 3 JShrink-"$VERSION"/src/JShrink/Minifier.php