| Code Coverage
| Lines | Functions and Methods | Classes and Traits
Total | 98.51% covered (success) | 98.51% | 66 / 67 | 50.00% covered (danger) | 50.00% | 1 / 2 | CRAP | 0.00% covered (danger) | 0.00% | 0 / 1
BacklinksBlock | 98.51% covered (success) | 98.51% | 66 / 67 | 50.00% covered (danger) | 50.00% | 1 / 2 | 6 | 0.00% covered (danger) | 0.00% | 0 / 1
register | 98.04% covered (success) | 98.04% | 50 / 51 | 0.00% covered (danger) | 0.00% | 0 / 1 | 3 |
render | 100.00% covered (success) | 100.00% | 16 / 16 | 100.00% covered (success) | 100.00% | 1 / 1 | 3 |

1 | <?php
2 | /**
3 | * SwapAds Client — Backlinks Gutenberg Block (F236).
4 | *
5 | * Registers `swapads/backlinks` Gutenberg block for drag-and-drop
6 | * placement in post + page editors. Renders the same output as
7 | * `[swapads_backlinks]` shortcode via the shared BacklinksRenderer.
8 | *
9 | * Block attributes mirror the shortcode attributes:
10 | * - count : int 1-50 (default 5)
11 | * - mode : string list|card|text (default list)
12 | * - audience : string filter by audience code (optional)
13 | * - category : string filter by host substring (optional)
14 | * - random : bool random order (default false)
15 | * - align : string wide|full|center|right|left|'' (WP align attr)
16 | *
17 | * @since 1.5.3
18 | */
19 |
20 | declare(strict_types=1);
21 |
22 | namespace SwapAds\Client\Blocks;
23 |
24 | use SwapAds\Client\Backlinks\BacklinksRenderer;
25 |
26 | /**
27 | * Class BacklinksBlock.
28 | *
29 | * @since 1.5.3
30 | */
31 | final class BacklinksBlock
32 | {
33 | public const BLOCK_NAME = 'swapads/backlinks';
34 | public const SCRIPT_HANDLE = 'swapads-backlinks-block-editor';
35 |
36 | /**
37 | * Register the block + editor script.
38 | */
39 | public static function register(): void
40 | {
41 | if (!function_exists('register_block_type')) {
42 | return;
43 | }
44 |
45 | // F236: editor script defines the inspector controls + live
46 | // preview. Front-end posts use the server-side render_callback
47 | // below — no JS needed on the visitor side.
48 | if (function_exists('wp_register_script')) {
49 | wp_register_script(
50 | self::SCRIPT_HANDLE,
51 | plugin_dir_url(__FILE__) . '../../assets/js/backlinks-block-editor.js',
52 | ['wp-blocks', 'wp-element', 'wp-block-editor', 'wp-components', 'wp-i18n'],
53 | '1.5.7',
54 | true
55 | );
56 | }
57 |
58 | register_block_type(self::BLOCK_NAME, [
59 | 'api_version' => 2,
60 | 'title' => __('SwapAds Backlinks', 'swapads'),
61 | 'description' => __('Display approved backlinks from your SwapAds partners. Configurable count, display mode, audience + category filters.', 'swapads'),
62 | 'category' => 'swapads',
63 | 'icon' => 'admin-links',
64 | 'keywords' => ['backlinks', 'swapads', 'partners', 'barter'],
65 | 'supports' => [
66 | 'align' => ['wide', 'full'],
67 | 'html' => false,
68 | 'reusable' => false,
69 | ],
70 | 'attributes' => [
71 | 'count' => [
72 | 'type' => 'number',
73 | 'default' => BacklinksRenderer::DEFAULT_COUNT,
74 | ],
75 | 'mode' => [
76 | 'type' => 'string',
77 | 'default' => BacklinksRenderer::MODE_LIST,
78 | ],
79 | 'audience' => [
80 | 'type' => 'string',
81 | 'default' => '',
82 | ],
83 | 'category' => [
84 | 'type' => 'string',
85 | 'default' => '',
86 | ],
87 | 'random' => [
88 | 'type' => 'boolean',
89 | 'default' => false,
90 | ],
91 | 'align' => [
92 | 'type' => 'string',
93 | 'default' => '',
94 | ],
95 | ],
96 | 'editor_script' => self::SCRIPT_HANDLE,
97 | 'render_callback' => [self::class, 'render'],
98 | ]);
99 | }
100 |
101 | /**
102 | * Server-side render callback for the block.
103 | *
104 | * @param array<string, mixed> $attributes
105 | *
106 | * @return string Rendered HTML, empty string when nothing to show.
107 | */
108 | public static function render(array $attributes): string
109 | {
110 | $html = BacklinksRenderer::render([
111 | 'count' => (int) ($attributes['count'] ?? BacklinksRenderer::DEFAULT_COUNT),
112 | 'mode' => (string) ($attributes['mode'] ?? BacklinksRenderer::MODE_LIST),
113 | 'audience' => (string) ($attributes['audience'] ?? ''),
114 | 'category' => (string) ($attributes['category'] ?? ''),
115 | 'random' => (bool) ($attributes['random'] ?? false),
116 | // F236: block editor previews should NOT cache. Operators see
117 | // the latest state of their approvals as they tweak controls.
118 | 'cache' => false,
119 | ]);
120 | if ($html === '') {
121 | // Empty placeholder so the block doesn't disappear in the editor.
122 | $count = (int) ($attributes['count'] ?? BacklinksRenderer::DEFAULT_COUNT);
123 | $mode = (string) ($attributes['mode'] ?? BacklinksRenderer::MODE_LIST);
124 | $html = '<div class="swapads-backlinks swapads-backlinks-placeholder swapads-backlinks-' . esc_attr($mode) . '" data-count="' . esc_attr((string) $count) . '"></div>';
125 | }
126 |
127 | $align = (string) ($attributes['align'] ?? '');
128 | if ($align !== '') {
129 | $html = '<div class="wp-block-swapads-backlinks align' . esc_attr($align) . '">' . $html . '</div>';
130 | }
131 | return $html;
132 | }
133 | }