Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/wp-content/themes/tuleva/helpers/extras.php
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,55 @@ function get_member_count()
return $memberCount;
}

/*
* Number of active investors in Tuleva index funds.
* Source of truth: onboarding-service /v1/statistics/investor-count
* (analytics.mv_kpi_new.total_active_investors) — the same figure tracked in
* Metabase. The underlying data updates roughly monthly, so the value is cached
* for a day: the front page does not call the API on every load, and the number
* still picks up a change within a day. Fetched with the same file_get_contents
* + stream context style as the fund pages (see print_funds_js below). Falls
* back to the last known good value and then to the manual ACF `members_count`
* option, and always returns an int, so the number never disappears, drops to
* zero, or triggers a number_format() error.
*/
function get_investor_count()
{
$cached = get_transient('tuleva_investor_count');
if ($cached !== false) {
return $cached;
}

$context = stream_context_create(
[
'http' => [
'method' => 'GET',
'timeout' => 2,
]
]
);
$json = @file_get_contents('https://onboarding-service.tuleva.ee/v1/statistics/investor-count', false, $context);
$data = $json ? json_decode($json, true) : null;
$count = isset($data['count']) ? (int) $data['count'] : 0;

if ($count > 0) {
set_transient('tuleva_investor_count', $count, DAY_IN_SECONDS);
update_option('tuleva_investor_count_last_good', $count);
return $count;
}

// API unavailable or unusable: fall back to the last known good value, then
// the manual ACF field. Cache the fallback briefly so an outage does not
// refetch on every page load.
$fallback = (int) get_option('tuleva_investor_count_last_good', 0);
if ($fallback <= 0) {
$fallback = (int) get_field('members_count', 'option');
}
set_transient('tuleva_investor_count', $fallback, 5 * MINUTE_IN_SECONDS);

return $fallback;
}

function print_funds_js()
{
$context = stream_context_create(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<?php
$members_count = get_field('members_count', 'option');
$investor_count = get_investor_count();
$members_count_description = get_sub_field('members_count_description');
$security_text = get_sub_field('security_text');
$security_link_text = get_sub_field('security_link_text');
$security_link_url = get_sub_field('security_link_url');
?>
<section id="<?php the_sub_field('component_id'); ?>" class="section-credentials bg-gray-2 py-4 py-lg-5">
<!-- Credentials -->
<?php if ($security_text || ($members_count && $members_count_description)) { ?>
<?php if ($security_text || ($investor_count && $members_count_description)) { ?>
<div class="container">
<div class="row gx-xl-5">
<?php if ($security_text) { ?>
Expand All @@ -27,10 +27,10 @@
</div>
</div>
<?php } ?>
<?php if ($members_count && $members_count_description) { ?>
<?php if ($investor_count && $members_count_description) { ?>
<div class="col-md-6 d-none d-md-flex align-items-center text-navy">
<span class="membercount text-nowrap me-3 me-sm-4 display-5 lh-1">
<?php echo number_format($members_count, 0, '', ' '); ?>
<?php echo number_format($investor_count, 0, '', ' '); ?>
</span>
<span style="max-width: 11em"><?php echo $members_count_description; ?></span>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\Test;

// Stub the WordPress + theme functions that credentials.php depends on, so the
// template can be rendered in isolation and we can assert that it actually
// publishes the investor count on the page.
if (!function_exists('get_investor_count')) {
function get_investor_count()
{
return $GLOBALS['__test_investor_count'] ?? 0;
}
}
if (!function_exists('get_sub_field')) {
function get_sub_field($name)
{
return $GLOBALS['__test_sub_fields'][$name] ?? '';
}
}
if (!function_exists('the_sub_field')) {
function the_sub_field($name)
{
echo $GLOBALS['__test_sub_fields'][$name] ?? '';
}
}

final class CredentialsComponentTest extends TestCase
{
private function render(int $investorCount): string
{
$GLOBALS['__test_investor_count'] = $investorCount;
$GLOBALS['__test_sub_fields'] = [
'component_id' => 'credentials',
'members_count_description' => 'Eesti inimest kogub Tuleva indeksfondides',
'security_text' => '',
'security_link_text' => '',
'security_link_url' => '',
];

ob_start();
include __DIR__ . '/../../templates/components/credentials.php';
return ob_get_clean();
}

#[Test]
public function publishesInvestorCountInMembercountSpan(): void
{
$html = $this->render(85224);

$this->assertStringContainsString('class="membercount', $html);
// number_format(85224, 0, '', ' ') => "85 224"
$this->assertStringContainsString('85 224', $html);
$this->assertStringContainsString('Eesti inimest kogub Tuleva indeksfondides', $html);
}

#[Test]
public function hidesTheBlockWhenCountIsZero(): void
{
$html = $this->render(0);

// When every fallback resolves to 0 the number must not be published at all,
// rather than showing a misleading "0".
$this->assertStringNotContainsString('class="membercount', $html);
}
}