Overhaul VcsDrivers, introduce TransportException for remote filesystem errors
parent
53ab5011f0
commit
3e22084ea4
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Downloader;
|
||||
|
||||
/**
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class TransportException extends \Exception
|
||||
{
|
||||
}
|
|
@ -49,7 +49,7 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
|
|||
public function getRootIdentifier()
|
||||
{
|
||||
if (null === $this->rootIdentifier) {
|
||||
$repoData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository), true);
|
||||
$repoData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository));
|
||||
$this->rootIdentifier = !empty($repoData['main_branch']) ? $repoData['main_branch'] : 'master';
|
||||
}
|
||||
|
||||
|
@ -93,13 +93,13 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
|
|||
if (!isset($this->infoCache[$identifier])) {
|
||||
$composer = $this->getContents($this->getScheme() . '://bitbucket.org/'.$this->owner.'/'.$this->repository.'/raw/'.$identifier.'/composer.json');
|
||||
if (!$composer) {
|
||||
throw new \UnexpectedValueException('Failed to retrieve composer information for identifier '.$identifier.' in '.$this->getUrl());
|
||||
return;
|
||||
}
|
||||
|
||||
$composer = JsonFile::parseJson($composer);
|
||||
|
||||
if (!isset($composer['time'])) {
|
||||
$changeset = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier), true);
|
||||
$changeset = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier));
|
||||
$composer['time'] = $changeset['timestamp'];
|
||||
}
|
||||
$this->infoCache[$identifier] = $composer;
|
||||
|
@ -114,7 +114,7 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
|
|||
public function getTags()
|
||||
{
|
||||
if (null === $this->tags) {
|
||||
$tagsData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'), true);
|
||||
$tagsData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'));
|
||||
$this->tags = array();
|
||||
foreach ($tagsData as $tag => $data) {
|
||||
$this->tags[$tag] = $data['raw_node'];
|
||||
|
@ -130,7 +130,7 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
|
|||
public function getBranches()
|
||||
{
|
||||
if (null === $this->branches) {
|
||||
$branchData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/branches'), true);
|
||||
$branchData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/branches'));
|
||||
$this->branches = array();
|
||||
foreach ($branchData as $branch => $data) {
|
||||
$this->branches[$branch] = $data['raw_node'];
|
||||
|
@ -140,20 +140,6 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
|
|||
return $this->branches;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function hasComposerFile($identifier)
|
||||
{
|
||||
try {
|
||||
$this->getComposerInformation($identifier);
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
|
|
@ -9,7 +9,7 @@ use Composer\IO\IOInterface;
|
|||
/**
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class GitDriver extends VcsDriver implements VcsDriverInterface
|
||||
class GitDriver extends VcsDriver
|
||||
{
|
||||
protected $tags;
|
||||
protected $branches;
|
||||
|
@ -117,7 +117,7 @@ class GitDriver extends VcsDriver implements VcsDriverInterface
|
|||
$this->process->execute(sprintf('cd %s && git show %s:composer.json', escapeshellarg($this->repoDir), escapeshellarg($identifier)), $composer);
|
||||
|
||||
if (!trim($composer)) {
|
||||
throw new \UnexpectedValueException('Failed to retrieve composer information for identifier '.$identifier.' in '.$this->getUrl());
|
||||
return;
|
||||
}
|
||||
|
||||
$composer = JsonFile::parseJson($composer);
|
||||
|
@ -173,20 +173,6 @@ class GitDriver extends VcsDriver implements VcsDriverInterface
|
|||
return $this->branches;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function hasComposerFile($identifier)
|
||||
{
|
||||
try {
|
||||
$this->getComposerInformation($identifier);
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
|
|
@ -8,7 +8,7 @@ use Composer\IO\IOInterface;
|
|||
/**
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class GitHubDriver extends VcsDriver implements VcsDriverInterface
|
||||
class GitHubDriver extends VcsDriver
|
||||
{
|
||||
protected $owner;
|
||||
protected $repository;
|
||||
|
@ -39,7 +39,7 @@ class GitHubDriver extends VcsDriver implements VcsDriverInterface
|
|||
public function getRootIdentifier()
|
||||
{
|
||||
if (null === $this->rootIdentifier) {
|
||||
$repoData = json_decode($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository), true);
|
||||
$repoData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository));
|
||||
$this->rootIdentifier = $repoData['master_branch'] ?: 'master';
|
||||
}
|
||||
|
||||
|
@ -83,13 +83,13 @@ class GitHubDriver extends VcsDriver implements VcsDriverInterface
|
|||
if (!isset($this->infoCache[$identifier])) {
|
||||
$composer = $this->getContents($this->getScheme() . '://raw.github.com/'.$this->owner.'/'.$this->repository.'/'.$identifier.'/composer.json');
|
||||
if (!$composer) {
|
||||
throw new \UnexpectedValueException('Failed to retrieve composer information for identifier '.$identifier.' in '.$this->getUrl());
|
||||
return;
|
||||
}
|
||||
|
||||
$composer = JsonFile::parseJson($composer);
|
||||
|
||||
if (!isset($composer['time'])) {
|
||||
$commit = json_decode($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/commits/'.$identifier), true);
|
||||
$commit = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/commits/'.$identifier));
|
||||
$composer['time'] = $commit['commit']['committer']['date'];
|
||||
}
|
||||
$this->infoCache[$identifier] = $composer;
|
||||
|
@ -104,7 +104,7 @@ class GitHubDriver extends VcsDriver implements VcsDriverInterface
|
|||
public function getTags()
|
||||
{
|
||||
if (null === $this->tags) {
|
||||
$tagsData = json_decode($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/tags'), true);
|
||||
$tagsData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/tags'));
|
||||
$this->tags = array();
|
||||
foreach ($tagsData as $tag) {
|
||||
$this->tags[$tag['name']] = $tag['commit']['sha'];
|
||||
|
@ -120,7 +120,7 @@ class GitHubDriver extends VcsDriver implements VcsDriverInterface
|
|||
public function getBranches()
|
||||
{
|
||||
if (null === $this->branches) {
|
||||
$branchData = json_decode($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/branches'), true);
|
||||
$branchData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/branches'));
|
||||
$this->branches = array();
|
||||
foreach ($branchData as $branch) {
|
||||
$this->branches[$branch['name']] = $branch['commit']['sha'];
|
||||
|
@ -130,20 +130,6 @@ class GitHubDriver extends VcsDriver implements VcsDriverInterface
|
|||
return $this->branches;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function hasComposerFile($identifier)
|
||||
{
|
||||
try {
|
||||
$this->getComposerInformation($identifier);
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
|
|
@ -18,7 +18,7 @@ use Composer\IO\IOInterface;
|
|||
/**
|
||||
* @author Per Bernhardt <plb@webfactory.de>
|
||||
*/
|
||||
class HgBitbucketDriver extends VcsDriver implements VcsDriverInterface
|
||||
class HgBitbucketDriver extends VcsDriver
|
||||
{
|
||||
protected $owner;
|
||||
protected $repository;
|
||||
|
@ -49,7 +49,7 @@ class HgBitbucketDriver extends VcsDriver implements VcsDriverInterface
|
|||
public function getRootIdentifier()
|
||||
{
|
||||
if (null === $this->rootIdentifier) {
|
||||
$repoData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'), true);
|
||||
$repoData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'));
|
||||
$this->rootIdentifier = $repoData['tip']['raw_node'];
|
||||
}
|
||||
|
||||
|
@ -93,13 +93,13 @@ class HgBitbucketDriver extends VcsDriver implements VcsDriverInterface
|
|||
if (!isset($this->infoCache[$identifier])) {
|
||||
$composer = $this->getContents($this->getScheme() . '://bitbucket.org/'.$this->owner.'/'.$this->repository.'/raw/'.$identifier.'/composer.json');
|
||||
if (!$composer) {
|
||||
throw new \UnexpectedValueException('Failed to retrieve composer information for identifier '.$identifier.' in '.$this->getUrl());
|
||||
return;
|
||||
}
|
||||
|
||||
$composer = JsonFile::parseJson($composer);
|
||||
|
||||
if (!isset($composer['time'])) {
|
||||
$changeset = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier), true);
|
||||
$changeset = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier));
|
||||
$composer['time'] = $changeset['timestamp'];
|
||||
}
|
||||
$this->infoCache[$identifier] = $composer;
|
||||
|
@ -114,7 +114,7 @@ class HgBitbucketDriver extends VcsDriver implements VcsDriverInterface
|
|||
public function getTags()
|
||||
{
|
||||
if (null === $this->tags) {
|
||||
$tagsData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'), true);
|
||||
$tagsData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'));
|
||||
$this->tags = array();
|
||||
foreach ($tagsData as $tag => $data) {
|
||||
$this->tags[$tag] = $data['raw_node'];
|
||||
|
@ -130,7 +130,7 @@ class HgBitbucketDriver extends VcsDriver implements VcsDriverInterface
|
|||
public function getBranches()
|
||||
{
|
||||
if (null === $this->branches) {
|
||||
$branchData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/branches'), true);
|
||||
$branchData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/branches'));
|
||||
$this->branches = array();
|
||||
foreach ($branchData as $branch => $data) {
|
||||
$this->branches[$branch] = $data['raw_node'];
|
||||
|
@ -140,25 +140,11 @@ class HgBitbucketDriver extends VcsDriver implements VcsDriverInterface
|
|||
return $this->branches;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function hasComposerFile($identifier)
|
||||
{
|
||||
try {
|
||||
$this->getComposerInformation($identifier);
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function supports($url, $deep = false)
|
||||
{
|
||||
return preg_match('#^https://bitbucket\.org/([^/]+)/([^/]+)/?$#', $url);
|
||||
return extension_loaded('openssl') && preg_match('#^https://bitbucket\.org/([^/]+)/([^/]+)/?$#', $url);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ use Composer\IO\IOInterface;
|
|||
/**
|
||||
* @author Per Bernhardt <plb@webfactory.de>
|
||||
*/
|
||||
class HgDriver extends VcsDriver implements VcsDriverInterface
|
||||
class HgDriver extends VcsDriver
|
||||
{
|
||||
protected $tags;
|
||||
protected $branches;
|
||||
|
@ -100,7 +100,7 @@ class HgDriver extends VcsDriver implements VcsDriverInterface
|
|||
$this->process->execute(sprintf('cd %s && hg cat -r %s composer.json', escapeshellarg($this->tmpDir), escapeshellarg($identifier)), $composer);
|
||||
|
||||
if (!trim($composer)) {
|
||||
throw new \UnexpectedValueException('Failed to retrieve composer information for identifier ' . $identifier . ' in ' . $this->getUrl());
|
||||
return;
|
||||
}
|
||||
|
||||
$composer = JsonFile::parseJson($composer);
|
||||
|
@ -159,20 +159,6 @@ class HgDriver extends VcsDriver implements VcsDriverInterface
|
|||
return $this->branches;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function hasComposerFile($identifier)
|
||||
{
|
||||
try {
|
||||
$this->getComposerInformation($identifier);
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
|
|
@ -9,7 +9,7 @@ use Composer\IO\IOInterface;
|
|||
/**
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class SvnDriver extends VcsDriver implements VcsDriverInterface
|
||||
class SvnDriver extends VcsDriver
|
||||
{
|
||||
protected $baseUrl;
|
||||
protected $tags;
|
||||
|
@ -108,7 +108,7 @@ class SvnDriver extends VcsDriver implements VcsDriverInterface
|
|||
);
|
||||
|
||||
if (!trim($composer)) {
|
||||
throw new \UnexpectedValueException('Failed to retrieve composer information for identifier '.$identifier.' in '.$this->getUrl());
|
||||
return;
|
||||
}
|
||||
|
||||
$composer = JsonFile::parseJson($composer);
|
||||
|
@ -226,20 +226,6 @@ class SvnDriver extends VcsDriver implements VcsDriverInterface
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function hasComposerFile($identifier)
|
||||
{
|
||||
try {
|
||||
$this->getComposerInformation($identifier);
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
namespace Composer\Repository\Vcs;
|
||||
|
||||
use Composer\Downloader\TransportException;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
|
@ -21,7 +22,7 @@ use Composer\Util\RemoteFilesystem;
|
|||
*
|
||||
* @author François Pluchino <francois.pluchino@opendisplay.com>
|
||||
*/
|
||||
abstract class VcsDriver
|
||||
abstract class VcsDriver implements VcsDriverInterface
|
||||
{
|
||||
protected $url;
|
||||
protected $io;
|
||||
|
@ -41,6 +42,20 @@ abstract class VcsDriver
|
|||
$this->process = $process ?: new ProcessExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function hasComposerFile($identifier)
|
||||
{
|
||||
try {
|
||||
return (Boolean) $this->getComposerInformation($identifier);
|
||||
} catch (TransportException $e) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the https or http protocol depending on SSL support.
|
||||
*
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Composer\Repository;
|
||||
|
||||
use Composer\Downloader\TransportException;
|
||||
use Composer\Repository\Vcs\VcsDriverInterface;
|
||||
use Composer\Package\Version\VersionParser;
|
||||
use Composer\Package\PackageInterface;
|
||||
|
@ -90,11 +91,14 @@ class VcsRepository extends ArrayRepository
|
|||
if ($parsedTag && $driver->hasComposerFile($identifier)) {
|
||||
try {
|
||||
$data = $driver->getComposerInformation($identifier);
|
||||
} catch (\Exception $e) {
|
||||
} catch (TransportException $e) {
|
||||
if ($debug) {
|
||||
$this->io->write('Skipped tag '.$tag.', '.$e->getMessage());
|
||||
}
|
||||
continue;
|
||||
} catch (\Exception $e) {
|
||||
$this->io->write('Skipped tag '.$tag.', '.$e->getMessage());
|
||||
continue;
|
||||
}
|
||||
|
||||
// manually versioned package
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
namespace Composer\Util;
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Downloader\TransportException;
|
||||
|
||||
/**
|
||||
* @author François Pluchino <francois.pluchino@opendisplay.com>
|
||||
|
@ -81,7 +82,7 @@ class RemoteFilesystem
|
|||
* @param boolean $progress Display the progression
|
||||
* @param boolean $firstCall Whether this is the first attempt at fetching this resource
|
||||
*
|
||||
* @throws \RuntimeException When the file could not be downloaded
|
||||
* @throws TransportException When the file could not be downloaded
|
||||
*/
|
||||
protected function get($originUrl, $fileUrl, $fileName = null, $progress = true, $firstCall = true)
|
||||
{
|
||||
|
@ -117,7 +118,7 @@ class RemoteFilesystem
|
|||
}
|
||||
|
||||
if (false === $this->result) {
|
||||
throw new \RuntimeException("The '$fileUrl' file could not be downloaded");
|
||||
throw new TransportException("The '$fileUrl' file could not be downloaded");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,7 +138,7 @@ class RemoteFilesystem
|
|||
case STREAM_NOTIFY_AUTH_REQUIRED:
|
||||
case STREAM_NOTIFY_FAILURE:
|
||||
if (404 === $messageCode && !$this->firstCall) {
|
||||
throw new \RuntimeException("The '" . $this->fileUrl . "' URL not found");
|
||||
throw new TransportException("The '" . $this->fileUrl . "' URL not found", 404);
|
||||
}
|
||||
|
||||
// for private repository returning 404 error when the authorization is incorrect
|
||||
|
@ -149,9 +150,9 @@ class RemoteFilesystem
|
|||
// get authorization informations
|
||||
if (401 === $messageCode || $attemptAuthentication) {
|
||||
if (!$this->io->isInteractive()) {
|
||||
$mess = "The '" . $this->fileUrl . "' URL required authentication.\nYou must be using the interactive console";
|
||||
$message = "The '" . $this->fileUrl . "' URL required authentication.\nYou must be using the interactive console";
|
||||
|
||||
throw new \RuntimeException($mess);
|
||||
throw new TransportException($message, 401);
|
||||
}
|
||||
|
||||
$this->io->overwrite(' Authentication required (<info>'.parse_url($this->fileUrl, PHP_URL_HOST).'</info>):');
|
||||
|
|
|
@ -111,7 +111,7 @@ class RemoteFilesystemTest extends \PHPUnit_Framework_TestCase
|
|||
$this->callCallbackGet($fs, STREAM_NOTIFY_FAILURE, 0, '', 404, 0, 0);
|
||||
$this->fail();
|
||||
} catch (\Exception $e) {
|
||||
$this->assertInstanceOf('RuntimeException', $e);
|
||||
$this->assertInstanceOf('Composer\Downloader\TransportException', $e);
|
||||
$this->assertContains('URL not found', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ class RemoteFilesystemTest extends \PHPUnit_Framework_TestCase
|
|||
$this->callCallbackGet($fs, STREAM_NOTIFY_FAILURE, 0, '', 404, 0, 0);
|
||||
$this->fail();
|
||||
} catch (\Exception $e) {
|
||||
$this->assertInstanceOf('RuntimeException', $e);
|
||||
$this->assertInstanceOf('Composer\Downloader\TransportException', $e);
|
||||
$this->assertContains('URL required authentication', $e->getMessage());
|
||||
$this->assertAttributeEquals(false, 'firstCall', $fs);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue