diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 4112bd2f8a..6ec5ea3fa4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -525,7 +525,9 @@ class FrameworkExtension extends Extension $guard = new Definition(Workflow\EventListener\GuardListener::class); $guard->setPrivate(true); $configuration = array(); - foreach ($workflow['transitions'] as $transitionName => $config) { + foreach ($workflow['transitions'] as $config) { + $transitionName = $config['name']; + if (!isset($config['guard'])) { continue; } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.php index ba6356f4ac..7cf4f4a6e2 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.php @@ -48,6 +48,15 @@ class CheckDefinitionValidityPass implements CompilerPassInterface throw new RuntimeException(sprintf('Please add the class to service "%s" even if it is constructed by a factory since we might need to add method calls based on compile-time checks.', $id)); } if (class_exists($id) || interface_exists($id, false)) { + if (0 === strpos($id, '\\') && 1 < substr_count($id, '\\')) { + throw new RuntimeException(sprintf( + 'The definition for "%s" has no class attribute, and appears to reference a class or interface. ' + .'Please specify the class attribute explicitly or remove the leading backslash by renaming ' + .'the service to "%s" to get rid of this error.', + $id, substr($id, 1) + )); + } + throw new RuntimeException(sprintf( 'The definition for "%s" has no class attribute, and appears to reference a ' .'class or interface in the global namespace. Leaving out the "class" attribute ' diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index b88d0a8a77..ccc5c6cb75 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -1238,6 +1238,30 @@ class ContainerBuilderTest extends TestCase $container->compile(); } + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException + * @expectedExceptionMessage The definition for "\DateTime" has no class attribute, and appears to reference a class or interface in the global namespace. + */ + public function testNoClassFromGlobalNamespaceClassIdWithLeadingSlash() + { + $container = new ContainerBuilder(); + + $container->register('\\'.\DateTime::class); + $container->compile(); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException + * @expectedExceptionMessage The definition for "\Symfony\Component\DependencyInjection\Tests\FooClass" has no class attribute, and appears to reference a class or interface. Please specify the class attribute explicitly or remove the leading backslash by renaming the service to "Symfony\Component\DependencyInjection\Tests\FooClass" to get rid of this error. + */ + public function testNoClassFromNamespaceClassIdWithLeadingSlash() + { + $container = new ContainerBuilder(); + + $container->register('\\'.FooClass::class); + $container->compile(); + } + /** * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException * @expectedExceptionMessage The definition for "123_abc" has no class. diff --git a/src/Symfony/Component/HttpFoundation/Cookie.php b/src/Symfony/Component/HttpFoundation/Cookie.php index 0cac235a6d..4962e1de37 100644 --- a/src/Symfony/Component/HttpFoundation/Cookie.php +++ b/src/Symfony/Component/HttpFoundation/Cookie.php @@ -266,7 +266,7 @@ class Cookie */ public function isCleared() { - return $this->expire < time(); + return 0 !== $this->expire && $this->expire < time(); } /** diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 355d1d608a..e16640c96a 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -1221,7 +1221,10 @@ class Request if ($method = $this->headers->get('X-HTTP-METHOD-OVERRIDE')) { $this->method = strtoupper($method); } elseif (self::$httpMethodParameterOverride) { - $this->method = strtoupper($this->request->get('_method', $this->query->get('_method', 'POST'))); + $method = $this->request->get('_method', $this->query->get('_method', 'POST')); + if (\is_string($method)) { + $this->method = strtoupper($method); + } } } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/CookieTest.php b/src/Symfony/Component/HttpFoundation/Tests/CookieTest.php index 5abb581408..3c7563adda 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/CookieTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/CookieTest.php @@ -154,6 +154,18 @@ class CookieTest extends TestCase $cookie = new Cookie('foo', 'bar', time() - 20); $this->assertTrue($cookie->isCleared(), '->isCleared() returns true if the cookie has expired'); + + $cookie = new Cookie('foo', 'bar'); + + $this->assertFalse($cookie->isCleared()); + + $cookie = new Cookie('foo', 'bar', 0); + + $this->assertFalse($cookie->isCleared()); + + $cookie = new Cookie('foo', 'bar', -1); + + $this->assertFalse($cookie->isCleared()); } public function testToString() diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index bc17169da3..4a86e4c4ed 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -848,6 +848,11 @@ class RequestTest extends TestCase $request->setMethod('POST'); $request->headers->set('X-HTTP-METHOD-OVERRIDE', 'delete'); $this->assertEquals('DELETE', $request->getMethod(), '->getMethod() returns the method from X-HTTP-Method-Override if defined and POST'); + + $request = new Request(); + $request->setMethod('POST'); + $request->query->set('_method', array('delete', 'patch')); + $this->assertSame('POST', $request->getMethod(), '->getMethod() returns the request method if invalid type is defined in query'); } /** diff --git a/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php b/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php index 7133ef1d3f..ccc8506d37 100644 --- a/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php +++ b/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php @@ -115,8 +115,7 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer $server['HTTP_X_FORWARDED_FOR'] = ($currentXForwardedFor ? $currentXForwardedFor.', ' : '').$request->getClientIp(); } - $trustedProxies = Request::getTrustedProxies(); - $server['REMOTE_ADDR'] = $trustedProxies ? reset($trustedProxies) : '127.0.0.1'; + $server['REMOTE_ADDR'] = $this->resolveTrustedProxy(); unset($server['HTTP_IF_MODIFIED_SINCE']); unset($server['HTTP_IF_NONE_MATCH']); @@ -133,6 +132,17 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer return $subRequest; } + private function resolveTrustedProxy() + { + if (!$trustedProxies = Request::getTrustedProxies()) { + return '127.0.0.1'; + } + + $firstTrustedProxy = reset($trustedProxies); + + return false !== ($i = strpos($firstTrustedProxy, '/')) ? substr($firstTrustedProxy, 0, $i) : $firstTrustedProxy; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php b/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php index bdd834fe4c..0ffca854f4 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php @@ -195,6 +195,25 @@ class InlineFragmentRendererTest extends TestCase Request::setTrustedProxies(array(), -1); } + public function testIpAddressOfRangedTrustedProxyIsSetAsRemote() + { + $expectedSubRequest = Request::create('/'); + $expectedSubRequest->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); + $expectedSubRequest->server->set('REMOTE_ADDR', '1.1.1.1'); + $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); + $expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); + + Request::setTrustedProxies(array('1.1.1.1/24'), -1); + + $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest)); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); + $strategy->render('/', $request); + + Request::setTrustedProxies(array(), -1); + } + /** * Creates a Kernel expecting a request equals to $request * Allows delta in comparison in case REQUEST_TIME changed by 1 second. diff --git a/src/Symfony/Component/Intl/Resources/bin/icu.ini b/src/Symfony/Component/Intl/Resources/bin/icu.ini deleted file mode 100644 index 9845e0bb1a..0000000000 --- a/src/Symfony/Component/Intl/Resources/bin/icu.ini +++ /dev/null @@ -1,20 +0,0 @@ -; ICU data source URLs -; We use always the latest release of a major version. -4.0 = http://source.icu-project.org/repos/icu/icu/tags/release-4-0-1/source -4.2 = http://source.icu-project.org/repos/icu/icu/tags/release-4-2-1/source -4.4 = http://source.icu-project.org/repos/icu/icu/tags/release-4-4-2/source -4.6 = http://source.icu-project.org/repos/icu/icu/tags/release-4-6-1/source -4.8 = http://source.icu-project.org/repos/icu/icu/tags/release-4-8-1-1/source -49 = http://source.icu-project.org/repos/icu/icu/tags/release-49-1-2/source -50 = http://source.icu-project.org/repos/icu/icu/tags/release-50-1-2/source -51 = http://source.icu-project.org/repos/icu/icu/tags/release-51-2/source -52 = http://source.icu-project.org/repos/icu/icu/tags/release-52-1/source -53 = http://source.icu-project.org/repos/icu/icu/tags/release-53-1/source -54 = http://source.icu-project.org/repos/icu/icu/tags/release-54-1/source -55 = http://source.icu-project.org/repos/icu/icu/tags/release-55-1/source -57 = http://source.icu-project.org/repos/icu/icu/tags/release-57-1/source -58 = http://source.icu-project.org/repos/icu/tags/release-58-2/icu4c/source -59 = http://source.icu-project.org/repos/icu/tags/release-59-1/icu4c/source -60 = http://source.icu-project.org/repos/icu/tags/release-60-2/icu4c/source -61 = http://source.icu-project.org/repos/icu/tags/release-61-1/icu4c/source -62 = http://source.icu-project.org/repos/icu/tags/release-62-1/icu4c/source diff --git a/src/Symfony/Component/Intl/Resources/bin/update-data.php b/src/Symfony/Component/Intl/Resources/bin/update-data.php index 80a34fe152..7238f46880 100644 --- a/src/Symfony/Component/Intl/Resources/bin/update-data.php +++ b/src/Symfony/Component/Intl/Resources/bin/update-data.php @@ -25,8 +25,7 @@ use Symfony\Component\Intl\Data\Provider\RegionDataProvider; use Symfony\Component\Intl\Data\Provider\ScriptDataProvider; use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\Locale; -use Symfony\Component\Intl\Util\IcuVersion; -use Symfony\Component\Intl\Util\SvnRepository; +use Symfony\Component\Intl\Util\GitRepository; require_once __DIR__.'/common.php'; require_once __DIR__.'/autoload.php'; @@ -40,7 +39,7 @@ Usage: php update-data.php Updates the ICU data for Symfony to the latest version of ICU. -If you downloaded the SVN repository before, you can pass the path to the +If you downloaded the git repository before, you can pass the path to the repository source in the first optional argument. If you also built the repository before, you can pass the directory where that @@ -64,37 +63,31 @@ if (!Intl::isExtensionLoaded()) { bailout('The intl extension for PHP is not installed.'); } -$filesystem = new Filesystem(); -$urls = parse_ini_file(__DIR__.'/icu.ini'); - -echo "icu.ini parsed. Available versions:\n"; - -$maxVersion = 0; - -foreach ($urls as $urlVersion => $url) { - $maxVersion = IcuVersion::compare($maxVersion, $urlVersion, '<') - ? $urlVersion - : $maxVersion; - - echo " $urlVersion\n"; -} - -$shortIcuVersion = strip_minor_versions($maxVersion); - if ($argc >= 2) { - $sourceDir = $argv[1]; - $svn = new SvnRepository($sourceDir); + $repoDir = $argv[1]; + $git = new GitRepository($repoDir); - echo "Using existing SVN repository at {$sourceDir}.\n"; + echo "Using the existing git repository at {$repoDir}.\n"; } else { - echo "Starting SVN checkout for version $shortIcuVersion. This may take a while...\n"; + echo "Starting git clone. This may take a while...\n"; - $sourceDir = sys_get_temp_dir().'/icu-data/'.$shortIcuVersion.'/source'; - $svn = SvnRepository::download($urls[$shortIcuVersion], $sourceDir); + $repoDir = sys_get_temp_dir().'/icu-data'; + $git = GitRepository::download('https://github.com/unicode-org/icu.git', $repoDir); - echo "SVN checkout to {$sourceDir} complete.\n"; + echo "Git clone to {$repoDir} complete.\n"; } +$gitTag = $git->getLastTag(function ($tag) { + return preg_match('#^release-[0-9]{1,}-[0-9]{1}$#', $tag); +}); +$shortIcuVersion = strip_minor_versions(preg_replace('#release-([0-9]{1,})-([0-9]{1,})#', '$1.$2', $gitTag)); + +echo "Checking out `{$gitTag}` for version `{$shortIcuVersion}`...\n"; +$git->checkout('tags/'.$gitTag); + +$filesystem = new Filesystem(); +$sourceDir = $repoDir.'/icu4c/source'; + if ($argc >= 3) { $buildDir = $argv[2]; } else { @@ -265,23 +258,23 @@ $generator->generateData($config); echo "Resource bundle compilation complete.\n"; -$svnInfo = <<getUrl()} -Revision: {$svn->getLastCommit()->getRevision()} -Author: {$svn->getLastCommit()->getAuthor()} -Date: {$svn->getLastCommit()->getDate()} +URL: {$git->getUrl()} +Revision: {$git->getLastCommitHash()} +Author: {$git->getLastAuthor()} +Date: {$git->getLastAuthoredDate()->format('c')} -SVN_INFO; +GIT_INFO; foreach ($targetDirs as $targetDir) { - $svnInfoFile = $targetDir.'/svn-info.txt'; + $gitInfoFile = $targetDir.'/git-info.txt'; - file_put_contents($svnInfoFile, $svnInfo); + file_put_contents($gitInfoFile, $gitInfo); - echo "Wrote $svnInfoFile.\n"; + echo "Wrote $gitInfoFile.\n"; $versionFile = $targetDir.'/version.txt'; diff --git a/src/Symfony/Component/Intl/Resources/data/git-info.txt b/src/Symfony/Component/Intl/Resources/data/git-info.txt new file mode 100644 index 0000000000..fe7ce33232 --- /dev/null +++ b/src/Symfony/Component/Intl/Resources/data/git-info.txt @@ -0,0 +1,7 @@ +Git information +=============== + +URL: https://github.com/unicode-org/icu.git +Revision: 4a3ba8eee90ea1414d4f7ee36563e6c9b28fda96 +Author: Yoshito Umaoka +Date: 2018-06-20T05:34:56+00:00 diff --git a/src/Symfony/Component/Intl/Resources/data/svn-info.txt b/src/Symfony/Component/Intl/Resources/data/svn-info.txt deleted file mode 100644 index 556c6cf358..0000000000 --- a/src/Symfony/Component/Intl/Resources/data/svn-info.txt +++ /dev/null @@ -1,7 +0,0 @@ -SVN information -=============== - -URL: http://source.icu-project.org/repos/icu/tags/release-62-1/icu4c/source -Revision: 41542 -Author: yoshito -Date: 2018-06-20T05:34:56.496986Z diff --git a/src/Symfony/Component/Intl/Tests/Util/GitRepositoryTest.php b/src/Symfony/Component/Intl/Tests/Util/GitRepositoryTest.php new file mode 100644 index 0000000000..a9eb531907 --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/Util/GitRepositoryTest.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Tests\Util; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Intl\Util\GitRepository; + +/** + * @group intl-data + */ +class GitRepositoryTest extends TestCase +{ + private $targetDir; + + const REPO_URL = 'https://github.com/symfony/intl.git'; + + /** + * @before + * @after + */ + protected function cleanup() + { + $this->targetDir = sys_get_temp_dir().'/GitRepositoryTest/source'; + + $fs = new Filesystem(); + $fs->remove($this->targetDir); + } + + public function testItThrowsAnExceptionIfInitialisedWithNonGitDirectory() + { + $this->expectException('Symfony\Component\Intl\Exception\RuntimeException'); + + @mkdir($this->targetDir, '0777', true); + + new GitRepository($this->targetDir); + } + + public function testItClonesTheRepository() + { + $git = GitRepository::download(self::REPO_URL, $this->targetDir); + + $this->assertInstanceOf('Symfony\Component\Intl\Util\GitRepository', $git); + $this->assertDirectoryExists($this->targetDir.'/.git'); + $this->assertSame($this->targetDir, $git->getPath()); + $this->assertSame(self::REPO_URL, $git->getUrl()); + $this->assertRegExp('#^[0-9a-z]{40}$#', $git->getLastCommitHash()); + $this->assertNotEmpty($git->getLastAuthor()); + $this->assertInstanceOf('DateTime', $git->getLastAuthoredDate()); + $this->assertStringMatchesFormat('v%s', $git->getLastTag()); + $this->assertStringMatchesFormat('v3%s', $git->getLastTag(function ($tag) { return 0 === strpos($tag, 'v3'); })); + } + + public function testItCheckoutsToTheLastTag() + { + $git = GitRepository::download(self::REPO_URL, $this->targetDir); + $lastCommitHash = $git->getLastCommitHash(); + $lastV3Tag = $git->getLastTag(function ($tag) { return 0 === strpos($tag, 'v3'); }); + + $git->checkout($lastV3Tag); + + $this->assertNotEquals($lastCommitHash, $git->getLastCommitHash()); + } +} diff --git a/src/Symfony/Component/Intl/Util/GitRepository.php b/src/Symfony/Component/Intl/Util/GitRepository.php new file mode 100644 index 0000000000..4f1eaaec06 --- /dev/null +++ b/src/Symfony/Component/Intl/Util/GitRepository.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Util; + +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Intl\Exception\RuntimeException; + +/** + * @internal + */ +final class GitRepository +{ + private $path; + + public function __construct(string $path) + { + $this->path = $path; + + $this->getUrl(); + } + + public static function download(string $remote, string $targetDir): self + { + self::exec('which git', 'The command "git" is not installed.'); + + $filesystem = new Filesystem(); + + if (!$filesystem->exists($targetDir.'/.git')) { + $filesystem->remove($targetDir); + $filesystem->mkdir($targetDir); + + self::exec(sprintf('git clone %s %s', escapeshellarg($remote), escapeshellarg($targetDir))); + } + + return new self(realpath($targetDir)); + } + + public function getPath() + { + return $this->path; + } + + public function getUrl() + { + return $this->getLastLine($this->execInPath('git config --get remote.origin.url')); + } + + public function getLastCommitHash() + { + return $this->getLastLine($this->execInPath('git log -1 --format="%H"')); + } + + public function getLastAuthor() + { + return $this->getLastLine($this->execInPath('git log -1 --format="%an"')); + } + + public function getLastAuthoredDate() + { + return new \DateTime($this->getLastLine($this->execInPath('git log -1 --format="%ai"'))); + } + + public function getLastTag(callable $filter = null) + { + $tags = $this->execInPath('git tag -l --sort=v:refname'); + + if (null !== $filter) { + $tags = array_filter($tags, $filter); + } + + return $this->getLastLine($tags); + } + + public function checkout($branch) + { + $this->execInPath(sprintf('git checkout %s', escapeshellarg($branch))); + } + + private function execInPath($command) + { + return self::exec(sprintf('cd %s && %s', escapeshellarg($this->path), $command)); + } + + private static function exec($command, $customErrorMessage = null) + { + exec(sprintf('%s 2>&1', $command), $output, $result); + + if (0 !== $result) { + throw new RuntimeException(null !== $customErrorMessage ? $customErrorMessage : sprintf('The `%s` command failed.', $command)); + } + + return $output; + } + + private function getLastLine(array $output) + { + return array_pop($output); + } +} diff --git a/src/Symfony/Component/Intl/Util/SvnCommit.php b/src/Symfony/Component/Intl/Util/SvnCommit.php deleted file mode 100644 index f83958fe38..0000000000 --- a/src/Symfony/Component/Intl/Util/SvnCommit.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Intl\Util; - -/** - * An SVN commit. - * - * @author Bernhard Schussek - */ -class SvnCommit -{ - private $svnInfo; - - /** - * Creates a commit from the given "svn info" data. - * - * @param \SimpleXMLElement $svnInfo the XML result from the "svn info" command - */ - public function __construct(\SimpleXMLElement $svnInfo) - { - $this->svnInfo = $svnInfo; - } - - /** - * Returns the revision of the commit. - * - * @return string The revision of the commit - */ - public function getRevision() - { - return (string) $this->svnInfo['revision']; - } - - /** - * Returns the author of the commit. - * - * @return string The author name - */ - public function getAuthor() - { - return (string) $this->svnInfo->author; - } - - /** - * Returns the date of the commit. - * - * @return string The commit date - */ - public function getDate() - { - return (string) $this->svnInfo->date; - } -} diff --git a/src/Symfony/Component/Intl/Util/SvnRepository.php b/src/Symfony/Component/Intl/Util/SvnRepository.php deleted file mode 100644 index 00049791d9..0000000000 --- a/src/Symfony/Component/Intl/Util/SvnRepository.php +++ /dev/null @@ -1,140 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Intl\Util; - -use Symfony\Component\Filesystem\Filesystem; -use Symfony\Component\Intl\Exception\RuntimeException; - -/** - * A SVN repository containing ICU data. - * - * @author Bernhard Schussek - */ -class SvnRepository -{ - /** - * @var string The path to the repository - */ - private $path; - - /** - * @var \SimpleXMLElement - */ - private $svnInfo; - - /** - * @var SvnCommit - */ - private $lastCommit; - - /** - * Downloads the ICU data for the given version. - * - * @param string $url The URL to download from - * @param string $targetDir The directory in which to store the repository - * - * @return static - * - * @throws RuntimeException if an error occurs during the download - */ - public static function download($url, $targetDir) - { - exec('which svn', $output, $result); - - if (0 !== $result) { - throw new RuntimeException('The command "svn" is not installed.'); - } - - $filesystem = new Filesystem(); - - if (!$filesystem->exists($targetDir.'/.svn')) { - $filesystem->remove($targetDir); - $filesystem->mkdir($targetDir); - - exec('svn checkout '.$url.' '.$targetDir, $output, $result); - - if (0 !== $result) { - throw new RuntimeException('The SVN checkout of '.$url.'failed.'); - } - } - - return new static(realpath($targetDir)); - } - - /** - * Reads the SVN repository at the given path. - * - * @param string $path The path to the repository - */ - public function __construct(string $path) - { - $this->path = $path; - } - - /** - * Returns the path to the repository. - * - * @return string The path to the repository - */ - public function getPath() - { - return $this->path; - } - - /** - * Returns the URL of the repository. - * - * @return string The URL of the repository - */ - public function getUrl() - { - return (string) $this->getSvnInfo()->entry->url; - } - - /** - * Returns the last commit of the repository. - * - * @return SvnCommit The last commit - */ - public function getLastCommit() - { - if (null === $this->lastCommit) { - $this->lastCommit = new SvnCommit($this->getSvnInfo()->entry->commit); - } - - return $this->lastCommit; - } - - /** - * Returns information about the SVN repository. - * - * @return \SimpleXMLElement The XML result from the "svn info" command - * - * @throws RuntimeException if the "svn info" command failed - */ - private function getSvnInfo() - { - if (null === $this->svnInfo) { - exec('svn info --xml '.$this->path, $output, $result); - - $svnInfo = simplexml_load_string(implode("\n", $output)); - - if (0 !== $result) { - throw new RuntimeException('svn info failed'); - } - - $this->svnInfo = $svnInfo; - } - - return $this->svnInfo; - } -}