| 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 |
26 | declare(strict_types=1);
27 |
28 | namespace SwapAds\Client\Shortcodes;
29 |
30 | use SwapAds\Client\Backlinks\BacklinksRenderer;
31 |
32 | /**
33 | * Class BacklinksShortcode.
34 | *
35 | * @since 1.4.0
36 | */
37 | final 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 | }