Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.74% covered (success)
94.74%
18 / 19
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
BacklinksShortcode
94.74% covered (success)
94.74%
18 / 19
50.00% covered (danger)
50.00%
1 / 2
5.00
0.00% covered (danger)
0.00%
0 / 1
 register
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 render
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
3.00
1<?php
2/**
3 * SwapAds Client — BacklinksShortcode (F236 refactor).
4 *
5 * `[swapads_backlinks]` shortcode that renders a list of approved
6 * (received) backlinks on the operator's site. Operators can drop it
7 * into any post or page to show their partners.
8 *
9 * Attributes:
10 *   - count     : int   Number of links to show (1-50, default 5)
11 *   - mode      : string Display mode: "list" | "card" | "text" (default "list")
12 *   - audience  : string Filter by audience code (default: all)
13 *   - category  : string Filter by host substring (default: all)
14 *   - random    : bool  Randomize order (default false; ordered by date desc)
15 *
16 * F236 (2026-07-31): refactored to delegate to the shared
17 * BacklinksRenderer so the shortcode + Gutenberg block stay in lockstep.
18 * F236 also removed the deprecated `swapads_server_url` option check
19 * (F201 removed it from the operator UI; `RestClient::fromOption()` is
20 * the canonical server-URL source now).
21 *
22 * @since 1.4.0
23 * @since 1.5.3 F236 — delegate to BacklinksRenderer; drop server_url option
24 */
25
26declare(strict_types=1);
27
28namespace SwapAds\Client\Shortcodes;
29
30use SwapAds\Client\Backlinks\BacklinksRenderer;
31
32/**
33 * Class BacklinksShortcode.
34 *
35 * @since 1.4.0
36 */
37final class BacklinksShortcode
38{
39    public const SHORTCODE = 'swapads_backlinks';
40
41    /**
42     * Register the shortcode.
43     */
44    public static function register(): void
45    {
46        if (!shortcode_exists(self::SHORTCODE)) {
47            add_shortcode(self::SHORTCODE, [self::class, 'render']);
48        }
49    }
50
51    /**
52     * Shortcode callback.
53     *
54     * Accepts the same attributes as the Gutenberg block (F236).
55     * Returns the rendered HTML from BacklinksRenderer so the two
56     * surfaces (shortcode + block) stay byte-identical.
57     *
58     * @param array<string, string> $atts
59     * @param string|null          $content
60     *
61     * @return string Rendered HTML, empty when nothing to show.
62     */
63    public static function render($atts, $content = null): string
64    {
65        $atts = shortcode_atts([
66            'count'    => (string) BacklinksRenderer::DEFAULT_COUNT,
67            'mode'     => BacklinksRenderer::MODE_LIST,
68            'audience' => '',
69            'category' => '',
70            'random'   => 'false',
71        ], $atts, self::SHORTCODE);
72
73        $html = BacklinksRenderer::render([
74            'count'    => (int) $atts['count'],
75            'mode'     => (string) $atts['mode'],
76            'audience' => (string) $atts['audience'],
77            'category' => (string) $atts['category'],
78            'random'   => filter_var($atts['random'], FILTER_VALIDATE_BOOLEAN),
79        ]);
80
81        if ($html === '' && $content !== null) {
82            // Shortcode nested content (e.g. `[swapads_backlinks]fallback html[/swapads_backlinks]`)
83            // renders only when there's no data — lets the operator show
84            // a manual block when their account is empty.
85            return do_shortcode($content);
86        }
87        return $html;
88    }
89}