From 915cda86ac53179d56852edce77ff882c7fe6aa2 Mon Sep 17 00:00:00 2001 From: stenehrlich-tuleva Date: Mon, 25 May 2026 11:56:03 +0300 Subject: [PATCH 1/2] Auto-populate front-page investor count from onboarding-service Replace the manually-edited ACF members_count on the credentials block with get_investor_count(), which fetches /v1/statistics/investor-count using the theme's existing file_get_contents + stream context style, caches the value for a day (the underlying data updates ~monthly), and falls back to last-known-good then the ACF value. Always returns an int. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../themes/tuleva/helpers/extras.php | 49 +++++++++++++++++++ .../templates/components/credentials.php | 8 +-- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/wp-content/themes/tuleva/helpers/extras.php b/src/wp-content/themes/tuleva/helpers/extras.php index 27f2f813..75d18cdf 100644 --- a/src/wp-content/themes/tuleva/helpers/extras.php +++ b/src/wp-content/themes/tuleva/helpers/extras.php @@ -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( diff --git a/src/wp-content/themes/tuleva/templates/components/credentials.php b/src/wp-content/themes/tuleva/templates/components/credentials.php index cf41874a..7c611606 100644 --- a/src/wp-content/themes/tuleva/templates/components/credentials.php +++ b/src/wp-content/themes/tuleva/templates/components/credentials.php @@ -1,5 +1,5 @@
- +
@@ -27,10 +27,10 @@
- +
- +
From 4e9a38475a68eb4fa509a1d94cde531c622755e1 Mon Sep 17 00:00:00 2001 From: stenehrlich-tuleva Date: Mon, 25 May 2026 13:12:24 +0300 Subject: [PATCH 2/2] Test that the credentials component publishes the investor count Add a PHPUnit render test that includes credentials.php in isolation (stubbing the WordPress functions it calls) and asserts the formatted investor count appears in the .membercount span, and that the block is hidden when the count is zero. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../templates/CredentialsComponentTest.php | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/wp-content/themes/tuleva/tests/templates/CredentialsComponentTest.php diff --git a/src/wp-content/themes/tuleva/tests/templates/CredentialsComponentTest.php b/src/wp-content/themes/tuleva/tests/templates/CredentialsComponentTest.php new file mode 100644 index 00000000..0d1bc69a --- /dev/null +++ b/src/wp-content/themes/tuleva/tests/templates/CredentialsComponentTest.php @@ -0,0 +1,67 @@ + '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); + } +}