Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
TextBlocks
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
3 / 3
16
100.00% covered (success)
100.00%
1 / 1
 content
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
 doc
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
7
 block
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\Migration;
6
7use Cosray\Block\RichText;
8use Cosray\Field;
9use Cosray\Richtext\Envelope;
10
11/**
12 * Plain text blocks turned into rich text blocks, wherever content holds
13 * them: on a field's grid, in a split or in a blocks field nested in a
14 * block. A run of lines between blank lines becomes a paragraph and each
15 * single line break a hard break, so the words and their breaks stay; a
16 * paragraph now comes wrapped in `<p>` where the plain text stood bare
17 * with `<br>`s. The block keeps its uid, layout and settings. Content
18 * without plain text blocks comes back as it was, so the conversion is
19 * safe to run again.
20 */
21final class TextBlocks
22{
23    /** The plain text block type, removed since; stored content may still name it. */
24    public const string TYPE = 'Cosray\\Block\\Text';
25
26    /**
27     * @param array<array-key, mixed> $data
28     * @return array<array-key, mixed>
29     */
30    public static function content(array $data): array
31    {
32        if (($data['type'] ?? null) === self::TYPE && is_array($data['fields'] ?? null)) {
33            return self::block($data);
34        }
35
36        foreach ($data as $key => $value) {
37            if (is_array($value)) {
38                $data[$key] = self::content($value);
39            }
40        }
41
42        return $data;
43    }
44
45    /**
46     * A plain text as a rich text document; none for one that holds
47     * nothing but whitespace, as an untouched rich text holds none.
48     *
49     * @return array{type: 'doc', content: list<array<string, mixed>>}|null
50     */
51    public static function doc(string $text): ?array
52    {
53        $text = trim(str_replace(["\r\n", "\r"], "\n", $text));
54
55        if ($text === '') {
56            return null;
57        }
58
59        $paragraphs = [];
60
61        foreach (preg_split('/\n[ \t]*\n\s*/', $text) ?: [] as $paragraph) {
62            $inline = [];
63
64            foreach (explode("\n", $paragraph) as $index => $line) {
65                if ($index > 0) {
66                    $inline[] = ['type' => 'hardBreak'];
67                }
68
69                if ($line !== '') {
70                    $inline[] = ['type' => 'text', 'text' => $line];
71                }
72            }
73
74            $paragraphs[] = ['type' => 'paragraph', 'content' => $inline];
75        }
76
77        return ['type' => 'doc', 'content' => $paragraphs];
78    }
79
80    /**
81     * @param array<array-key, mixed> $row
82     * @return array<array-key, mixed>
83     */
84    private static function block(array $row): array
85    {
86        $text = $row['fields']['text'] ?? null;
87        $values = is_array($text) && is_array($text['value'] ?? null) ? $text['value'] : [];
88
89        $row['type'] = RichText::class;
90        $row['fields']['text'] = [
91            'type' => Field\RichText::class,
92            'value' => array_map(
93                static fn(mixed $value): ?array => is_string($value) ? self::doc($value) : null,
94                $values,
95            ),
96            'format' => Envelope::FORMAT,
97            'version' => Envelope::VERSION,
98        ];
99
100        return $row;
101    }
102}