cc-1.0.x-dev/modules/cc_cex/src/Controller/MarketPairsController.php
modules/cc_cex/src/Controller/MarketPairsController.php
<?php
namespace Drupal\cc_cex\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Drupal\cc_cex\Service\CcxtService;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
/**
* Controller for displaying market pairs.
*/
class MarketPairsController extends ControllerBase implements ContainerInjectionInterface {
/**
* The CCXT service.
*
* @var \Drupal\cc_cex\Service\CcxtService
*/
protected $ccxtService;
/**
* The date formatter.
*
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected $dateFormatter;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* Constructs a new MarketPairsController object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Drupal\cc_cex\Service\CcxtService $ccxt_service
* The CCXT service.
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct(
ConfigFactoryInterface $config_factory,
CcxtService $ccxt_service,
DateFormatterInterface $date_formatter,
MessengerInterface $messenger
) {
$this->configFactory = $config_factory;
$this->ccxtService = $ccxt_service;
$this->dateFormatter = $date_formatter;
$this->messenger = $messenger;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('cc_cex.ccxt_service'),
$container->get('date.formatter'),
$container->get('messenger')
);
}
/**
* Displays the market pairs page.
*
* @return array
* A render array for the market pairs page.
*/
public function content() {
$config = $this->configFactory->get('cc.settings');
$enabled_exchanges = $config->get('enabled_exchanges') ?: '';
$enabled_exchanges = !empty($enabled_exchanges) ? array_map('trim', explode(',', $enabled_exchanges)) : [];
$currencies = $config->get('currencies') ?: [];
$accepted_currencies = $currencies['accepted'] ?? [];
$needed_currencies = $currencies['needed'] ?? [];
$build = [];
// Add cache contexts and tags
$build['#cache'] = [
'contexts' => ['user'],
'tags' => ['cc:markets', 'cc:symbols', 'cc:prices', 'config:cc.settings'],
'max-age' => 31536000, // 1 year
];
// Add refresh buttons
$build['actions'] = [
'#type' => 'container',
'#attributes' => [
'class' => ['cc-market-pairs-actions'],
],
'refresh_prices' => [
'#type' => 'link',
'#title' => $this->t('Refresh Prices'),
'#url' => Url::fromRoute('cc_cex.market_pairs_refresh', ['type' => 'prices']),
'#attributes' => [
'class' => ['button', 'button--primary'],
],
],
'refresh_markets' => [
'#type' => 'link',
'#title' => $this->t('Refresh Available Markets'),
'#url' => Url::fromRoute('cc_cex.market_pairs_refresh', ['type' => 'markets']),
'#attributes' => [
'class' => ['button'],
],
],
];
$build['description'] = [
'#markup' => '<p>' . $this->t('Available market pairs based on your <a href="@settings">exchange settings</a>.', [
'@settings' => Url::fromRoute('cc_cex.exchange_settings')->toString(),
]) . '</p>',
];
if (empty($enabled_exchanges) || empty($needed_currencies) || empty($accepted_currencies)) {
$build['empty'] = [
'#markup' => '<p>' . $this->t('Please <a href="@settings">configure your exchange settings</a> first.', [
'@settings' => '/admin/config/services/cc-cex-exchange',
]) . '</p>',
];
return $build;
}
// Validate currency arrays
$needed_currencies = array_filter($needed_currencies);
$accepted_currencies = array_filter($accepted_currencies);
// Get valid symbols from cache
$exchange_symbols = $this->ccxtService->getAvailableSymbols(
$enabled_exchanges,
$needed_currencies,
$accepted_currencies,
);
$all_symbols = [];
if (!empty($exchange_symbols)) {
foreach ($exchange_symbols as $symbols) {
$all_symbols = array_merge($all_symbols, $symbols);
}
$all_symbols = array_unique($all_symbols);
}
// Only proceed if we have symbols
if (empty($all_symbols)) {
$build['empty'] = [
'#markup' => '<p>' . $this->t('No valid market pairs found for the selected currencies.') . '</p>',
];
return $build;
}
// Only fetch prices for active symbols
$active_symbols = array_filter($all_symbols, function($symbol) use ($needed_currencies, $accepted_currencies) {
if (!str_contains($symbol, '/')) {
\Drupal::logger('cc')->warning('Invalid symbol format: @symbol', ['@symbol' => $symbol]);
return false;
}
list($base, $quote) = explode('/', $symbol);
if (!$base || !$quote) {
\Drupal::logger('cc')->warning('Invalid symbol format: @symbol', ['@symbol' => $symbol]);
return false;
}
return in_array($base, $accepted_currencies) && in_array($quote, $needed_currencies);
});
// Use the cached symbols to fetch only the prices we need
$market_pairs = $this->ccxtService->getPrices(
$enabled_exchanges,
$active_symbols,
false // Don't force refresh prices on normal page load
);
if (empty($market_pairs)) {
$build['no_pairs'] = [
'#markup' => '<p>' . $this->t('No market pairs found for your current configuration.') . '</p>',
];
return $build;
}
// Create table
$header = [
$this->t('Exchange'),
$this->t('Symbol'),
$this->t('Price'),
$this->t('Last update'),
];
$rows = [];
foreach ($market_pairs as $pair) {
// Convert millisecond timestamp to seconds if needed
$timestamp = $pair['timestamp'];
if ($timestamp > 1000000000000) { // If timestamp is in milliseconds
$timestamp = (int)($timestamp / 1000);
}
$rows[] = [
$pair['exchange'],
$pair['symbol'],
$pair['price'],
$this->t('@time ago', ['@time' => $this->dateFormatter->formatTimeDiffSince($timestamp)]),
];
}
$build['market_pairs'] = [
'#type' => 'table',
'#header' => $header,
'#rows' => $rows,
'#empty' => $this->t('No market pairs available.'),
'#attributes' => [
'class' => ['cc-market-pairs-table'],
],
];
return $build;
}
/**
* Refreshes market pair prices.
*
* @param string $type
* The type of refresh. Either 'prices' or 'markets'.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* A redirect response to the market pairs page.
*/
public function refresh($type) {
// Get current config
$config = $this->configFactory->get('cc.settings');
$enabled_exchanges = $config->get('enabled_exchanges') ?: '';
$enabled_exchanges = !empty($enabled_exchanges) ? array_map('trim', explode(',', $enabled_exchanges)) : [];
$currencies = $config->get('currencies') ?: [];
$accepted_currencies = $currencies['accepted'] ?? [];
$needed_currencies = $currencies['needed'] ?? [];
if (empty($enabled_exchanges) || empty($needed_currencies) || empty($accepted_currencies)) {
$this->messenger->addError($this->t('Please configure your exchange settings first.'));
return new RedirectResponse(Url::fromRoute('cc_cex.market_pairs')->toString());
}
if ($type === 'markets') {
// Clear all caches related to markets and symbols
\Drupal::service('cache_tags.invalidator')->invalidateTags(['cc:markets', 'cc:symbols', 'config:cc.settings']);
$this->messenger->addStatus($this->t('Market data refreshed.'));
} else {
// Clear price caches to force a refresh
\Drupal::service('cache_tags.invalidator')->invalidateTags(['cc:prices', 'config:cc.settings']);
$this->messenger->addStatus($this->t('Prices refreshed.'));
}
return new RedirectResponse(Url::fromRoute('cc_cex.market_pairs')->toString());
}
}
