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
20declare(strict_types=1);
21
22namespace SwapAds\Client\Blocks;
23
24use SwapAds\Client\Backlinks\BacklinksRenderer;
25
26/**
27 * Class BacklinksBlock.
28 *
29 * @since 1.5.3
30 */
31final 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}