Skip to content

Commit

Permalink
Merge branch 'develop' into feature/PLUG-155
Browse files Browse the repository at this point in the history
  • Loading branch information
stefandanaita authored Oct 29, 2024
2 parents d589d2d + 16657ef commit b88b2b7
Show file tree
Hide file tree
Showing 19 changed files with 467 additions and 33 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/phpstan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ jobs:
- name: Set minimum-stability for composer (temp)
run: docker exec magento-project-community-edition composer config minimum-stability dev

- name: Configure composer to mirror repository
run: docker exec magento-project-community-edition composer config repositories.truelayer \{\"type\":\"path\",\"url\":\"/data/extensions/$(basename $(pwd))\",\"options\":\{\"symlink\":false\}\}

- name: Install the extensions in Magento
run: docker exec magento-project-community-edition composer require truelayer/magento2:@dev --no-plugins --with-all-dependencies

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
.DS_Store
docker
.vscode
.php-cs-fixer.cache
56 changes: 56 additions & 0 deletions .php-cs-fixer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

use PhpCsFixer\Config;
use PhpCsFixer\Finder;

$finder = Finder::create()
->in(__DIR__)
->exclude(['.github', '.vscode', 'docker', 'vendor'])
->ignoreDotFiles(false);

$rules = [
'@PSR1' => true,
'@PSR2' => true,
'@PSR12' => true,
'ordered_imports' => [
'sort_algorithm' => 'alpha',
'imports_order' => [
'class',
'function',
'const'
]
],
'single_line_empty_body' => true,
'array_indentation' => true,
'array_syntax' => ['syntax' => 'short'],
'no_whitespace_in_blank_line' => true,
'whitespace_after_comma_in_array' => [
'ensure_single_space' => true
],
'no_multiline_whitespace_around_double_arrow' => true,
'no_trailing_comma_in_singleline_array' => true,
'normalize_index_brace' => true,
'trim_array_spaces' => true,
'single_class_element_per_statement' => [
'elements' => [
'const',
'property'
]
],
'visibility_required' => [
'elements' => [
'property',
'method',
'const'
]
],
'align_multiline_comment' => true
];

$config = new Config();
$config->setFinder($finder);
$config->setRules($rules);
$config->setIndent(' ');
$config->setLineEnding("\n");

return $config;
1 change: 1 addition & 0 deletions Api/Config/System/ConnectionInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface ConnectionInterface extends DebugInterface
public const XML_PATH_PRODUCTION_CLIENT_SECRET = 'payment/truelayer/production_client_secret';
public const XML_PATH_PRODUCTION_PRIVATE_KEY = 'payment/truelayer/production_private_key';
public const XML_PATH_PRODUCTION_KEY_ID = 'payment/truelayer/production_key_id';
public const XML_PATH_CACHE_ENCRYPTION_KEY = 'payment/truelayer/cache_encryption_key';

/**
* Get Merchant Account Name
Expand Down
65 changes: 57 additions & 8 deletions Block/Adminhtml/System/Config/Button/VersionCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@

namespace TrueLayer\Connect\Block\Adminhtml\System\Config\Button;

use TrueLayer\Connect\Api\Config\RepositoryInterface as ConfigRepository;
use Magento\Backend\Block\Template\Context;
use Magento\Config\Block\System\Config\Form\Field;
use Magento\Framework\Data\Form\Element\AbstractElement;
use Magento\Framework\HTTP\Client\Curl;
use TrueLayer\Connect\Api\Config\RepositoryInterface as ConfigRepository;
use TrueLayer\Connect\Api\Log\LogServiceInterface;

/**
* Version check class
Expand All @@ -22,10 +24,6 @@ class VersionCheck extends Field
* @var string
*/
protected $_template = 'TrueLayer_Connect::system/config/button/version.phtml';
/**
* @var ConfigRepository
*/
private $configRepository;

/**
* VersionCheck constructor.
Expand All @@ -35,10 +33,11 @@ class VersionCheck extends Field
*/
public function __construct(
Context $context,
ConfigRepository $configRepository,
private ConfigRepository $configRepository,
private Curl $curl,
private LogServiceInterface $logger,
array $data = []
) {
$this->configRepository = $configRepository;
parent::__construct($context, $data);
}

Expand Down Expand Up @@ -66,6 +65,56 @@ public function _getElementHtml(AbstractElement $element): string
*/
public function getVersion(): string
{
return 'v' . $this->configRepository->getExtensionVersion();
return $this->configRepository->getExtensionVersion();
}

public function getLatestVersion()
{
$curlVersion = $this->getCurlVersion();
$this->curl->addHeader('Accept', 'application/vnd.github+json');
$this->curl->addHeader('User-Agent', 'curl/'.$curlVersion);
$this->curl->setOption(CURLOPT_RETURNTRANSFER, true);
$this->curl->get('https://api.github.com/repos/TrueLayer/magento2/releases');
$responseStatus = $this->curl->getStatus();
if ($responseStatus !== 200) {
$this->logger->error('Plugin version check failed, could not retrieve releases from github api', [
'response_status' => $responseStatus,
'response_body' => $this->curl->getBody()
]);
return false;
}
$response = $this->curl->getBody();
try {
$releases = json_decode($response, true, JSON_THROW_ON_ERROR);
} catch (\Exception $e) {
$this->logger->error('Plugin version check failed, json_decode error', [
'response_body' => $response,
'json_exception' => $e->getMessage()
]);
return false;
}
foreach ($releases as $release) {
if (!$release['draft'] && !$release['prerelease']) {
$latestRelease = $release;
break;
}
}
if (!isset($latestRelease)) {
$this->logger->error('Plugin version check failed, no valid release in github api response');
return false;
}
$latestVersion = ltrim($latestRelease['name'], 'v');
return $latestVersion;
}

private function getCurlVersion()
{
$curlVersion = curl_version();
if (is_array($curlVersion) && array_key_exists('version', $curlVersion)) {
$curlVersion = $curlVersion['version'];
} else {
$curlVersion = 'unknown';
}
return $curlVersion;
}
}
3 changes: 2 additions & 1 deletion Controller/Adminhtml/Credentials/Check.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ private function getCredentials(): array
'client_id' => $clientId,
'client_secret' => $clientSecret,
'private_key' => $this->getPrivateKeyPath($configCredentials),
'key_id' => $keyId
'key_id' => $keyId,
'cache_encryption_key' => $configCredentials['cache_encryption_key']
]
];
}
Expand Down
29 changes: 29 additions & 0 deletions Model/Cache/CacheType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace TrueLayer\Connect\Model\Cache;

use Magento\Framework\App\Cache\Type\FrontendPool;
use Magento\Framework\Cache\Frontend\Decorator\TagScope;

class CacheType extends TagScope
{
/**
* Cache type code unique among all cache types
* @var string
*/
public const TYPE_IDENTIFIER = 'truelayer';

/**
* The tag name that limits the cache cleaning scope within a particular tag
* @var string
*/
public const CACHE_TAG = 'TRUELAYER';

public function __construct(FrontendPool $cacheFrontendPool)
{
parent::__construct(
$cacheFrontendPool->get(self::TYPE_IDENTIFIER),
self::CACHE_TAG
);
}
}
18 changes: 17 additions & 1 deletion Model/Config/System/ConnectionRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public function getCredentials(?int $storeId = null, ?bool $forceSandbox = null)
"client_id" => $this->getClientId($storeId, $isSandBox),
"client_secret" => $this->getClientSecret($storeId, $isSandBox),
"private_key" => $this->getPathToPrivateKey($storeId, $isSandBox),
"key_id" => $this->getKeyId($storeId, $isSandBox)
"key_id" => $this->getKeyId($storeId, $isSandBox),
"cache_encryption_key" => $this->getCacheEncryptionKey($storeId)
];
}

Expand Down Expand Up @@ -103,4 +104,19 @@ private function getClientSecret(?int $storeId = null, bool $isSandBox = false):

return '';
}

/**
* @param int|null $storeId
*
* @return string
*/
private function getCacheEncryptionKey(?int $storeId = null): ?string
{
$path = self::XML_PATH_CACHE_ENCRYPTION_KEY;
$value = $this->getStoreValue($path, $storeId);
if (!empty($value)) {
return $this->encryptor->decrypt($value);
}
return null;
}
}
80 changes: 61 additions & 19 deletions Model/System/Config/Backend/PrivateKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
*/
class PrivateKey extends Value
{
public const FILENAME = 'private-key.pem';
/**
* @var File
*/
Expand Down Expand Up @@ -76,40 +77,59 @@ public function beforeSave(): self
{
$value = (array)$this->getValue();
$sandbox = $this->getPath() === ConnectionInterface::XML_PATH_SANDBOX_PRIVATE_KEY;
$directory = $this->getDirectory($sandbox);

if (!empty($value['delete']) && !empty($value['value'])) {
$this->deleteCertificateAndReset($value['value']);
if (!empty($value['delete'])) {
$this->deleteCertificateAndReset($this->isObjectNew() ? '' : $this->getOldValue());
return $this;
}

if (!empty($value['value'])) {
$this->setValue($value['value']);
}

if (empty($value['tmp_name'])) {
$tmpName = $this->getTmpName($sandbox);
$isUploading = (is_string($tmpName) && !empty($tmpName) && $this->tmpDirectory->isExist($tmpName));

if (!$isUploading) {
$this->setValue($this->isObjectNew() ? '' : $this->getOldValue());
return $this;
}

$tmpPath = $this->tmpDirectory->getAbsolutePath($value['tmp_name']);
if ($tmpPath && $this->tmpDirectory->isExist($tmpPath)) {
if ($isUploading) {
$tmpPath = $this->tmpDirectory->getAbsolutePath($tmpName);
if (!$this->tmpDirectory->stat($tmpPath)['size']) {
throw new LocalizedException(__('The TrueLayer certificate file is empty.'));
}

$filePath = $this->getFilePath($sandbox);
$destinationPath = $this->varDirectory->getAbsolutePath('truelayer/' . $filePath);
$destinationPath = $this->varDirectory->getAbsolutePath('truelayer/' . $directory);

$filePath = $directory . self::FILENAME;
$this->file->checkAndCreateFolder($destinationPath);
$this->file->mv(
$tmpPath,
$this->varDirectory->getAbsolutePath('truelayer/' . $filePath . $value['name'])
$this->varDirectory->getAbsolutePath('truelayer/' . $filePath)
);
$this->setValue($filePath . $value['name']);
$this->setValue($filePath);
}

return $this;
}

/**
* Delete the cert file from disk when deleting the setting.
*
* @return $this
*/
public function beforeDelete()
{
$returnValue = parent::beforeDelete();
$filePath = $this->isObjectNew() ? '' : $this->getOldValue();
if ($filePath) {
$absolutePath = $this->varDirectory->getAbsolutePath('truelayer/' . $filePath);
if ($this->file->fileExists($absolutePath)) {
$this->file->rm($absolutePath);
}
}
return $returnValue;
}

/**
* Delete the cert file and unset the config value.
*
Expand All @@ -118,25 +138,47 @@ public function beforeSave(): self
*/
private function deleteCertificateAndReset(string $filePath): void
{
$absolutePath = $this->varDirectory->getAbsolutePath('truelayer/' . $filePath);
if ($this->file->fileExists($absolutePath)) {
$this->file->rm($absolutePath);
if (!empty($filePath)) {
$absolutePath = $this->varDirectory->getAbsolutePath('truelayer/' . $filePath);
if ($this->file->fileExists($absolutePath)) {
$this->file->rm($absolutePath);
}
}

$this->setValue('');
}

/**
* Returns the filepath based on set scope.
* Returns the directory based on set scope.
*
* @param bool $sandbox
* @return string
*/
private function getFilePath(bool $sandbox): string
private function getDirectory(bool $sandbox): string
{
$mode = $sandbox ? 'sandbox' : 'production';
return $this->getScope() !== 'default'
? sprintf('%s/%s/%s/', $mode, $this->getScope(), $this->getScopeId())
: sprintf('%s/default/', $mode);
}

/**
* Returns the path to the uploaded tmp_file based on set scope.
*
* @param bool $sandbox
* @return string
*/
private function getTmpName(bool $sandbox): ?string
{
$files = $_FILES;
if (empty($files)) {
return null;
}
try {
$tmpName = $files['groups']['tmp_name']['general']['fields'][$sandbox ? 'sandbox_private_key' : 'production_private_key']['value'];
return empty($tmpName) ? null : $tmpName;
} catch (\Exception $e) {
return null;
}
}
}
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ bin/magento cache:flush
bin/magento setup:static-content:deploy
```
7. After the installation, go to your Magento® admin portal and open ‘Stores’ > ‘Configuration’ > ‘Sales’ > ‘TrueLayer’.
8. It's recommended that you also enable the cache for TrueLayer. There's two ways you can do this.
1. In your Magento® admin portal open ‘System‘ > ‘Cache Management‘, click the checkbox for TrueLayer, select ‘Enable‘ from ‘Actions‘, and click ‘Submit‘.
2. On your server running Magento® 2 run the following command from the command line: `bin/magento cache:enable truelayer`

# Local development
A basic docker-compose configuration is provided to make local development easier. To start it, run the following:
Expand Down
Loading

0 comments on commit b88b2b7

Please sign in to comment.