Passed
Push — dev ( f79383...5f589d )
by Greg
06:20
created

FunctionsPrintFacts::getSourceStructure()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 27
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 17
nc 3
nop 1
dl 0
loc 27
rs 9.7
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2021 webtrees development team
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\Functions;
21
22
use Fisharebest\Webtrees\Fact;
23
use Fisharebest\Webtrees\I18N;
24
use Fisharebest\Webtrees\Media;
25
use Fisharebest\Webtrees\Registry;
26
use Fisharebest\Webtrees\Tree;
27
use Ramsey\Uuid\Uuid;
28
29
use function count;
30
use function e;
31
use function ob_get_clean;
32
use function ob_start;
33
use function preg_match_all;
34
use function preg_replace;
35
use function str_contains;
36
use function strlen;
37
use function strpos;
38
use function substr;
39
use function trim;
40
use function view;
41
42
use const PREG_SET_ORDER;
43
44
/**
45
 * Class FunctionsPrintFacts - common functions
46
 *
47
 * @deprecated since 2.0.6.  Will be removed in 2.1.0
48
 */
49
class FunctionsPrintFacts
50
{
51
    /**
52
     * print a source linked to a fact (2 SOUR)
53
     * this function is called by the FunctionsPrintFacts::print_fact function and other functions to
54
     * print any source information attached to the fact
55
     *
56
     * @param Tree   $tree
57
     * @param string $factrec The fact record to look for sources in
58
     * @param int    $level   The level to look for sources at
59
     *
60
     * @return string HTML text
61
     */
62
    public static function printFactSources(Tree $tree, string $factrec, int $level): string
63
    {
64
        $data   = '';
65
        $nlevel = $level + 1;
66
67
        // Systems not using source records
68
        // The old style is not supported when entering or editing sources, but may be found in imported trees.
69
        // Also, the old style sources allow histo.* files to use tree independent source citations, which
70
        // will display nicely when markdown is used.
71
        $ct = preg_match_all('/' . $level . ' SOUR (.*)((?:\n\d CONT.*)*)/', $factrec, $match, PREG_SET_ORDER);
72
        for ($j = 0; $j < $ct; $j++) {
73
            if (!str_contains($match[$j][1], '@')) {
74
                $source = e($match[$j][1] . preg_replace('/\n\d CONT ?/', "\n", $match[$j][2]));
75
                $data .= '<div class="fact_SOUR"><span class="label">' . I18N::translate('Source') . ':</span> <span class="field" dir="auto">';
76
77
                if ($tree->getPreference('FORMAT_TEXT') === 'markdown') {
78
                    $data .= '<div class="markdown" dir="auto">';
79
                    $data .= Registry::markdownFactory()->markdown($source, $tree);
80
                    $data .= '</div>';
81
                } else {
82
                    $data .= '<div class="markdown" dir="auto">';
83
                    $data .= Registry::markdownFactory()->autolink($source, $tree);
84
                    $data .= '</div>';
85
                }
86
87
                $data .= '</span></div>';
88
            }
89
        }
90
        // Find source for each fact
91
        $ct    = preg_match_all("/$level SOUR @(.*)@/", $factrec, $match, PREG_SET_ORDER);
92
        $spos2 = 0;
93
        for ($j = 0; $j < $ct; $j++) {
94
            $sid    = $match[$j][1];
95
            $source = Registry::sourceFactory()->make($sid, $tree);
96
            if ($source) {
97
                if ($source->canShow()) {
98
                    $spos1 = strpos($factrec, "$level SOUR @" . $sid . '@', $spos2);
99
                    $spos2 = strpos($factrec, "\n$level", $spos1);
100
                    if (!$spos2) {
101
                        $spos2 = strlen($factrec);
102
                    }
103
104
                    $srec = substr($factrec, $spos1, $spos2 - $spos1);
105
                    $lt   = preg_match_all("/$nlevel \w+/", $srec, $matches);
106
                    $data .= '<div class="fact_SOUR">';
107
                    $id       = 'collapse-' . Uuid::uuid4()->toString();
108
                    $expanded = (bool) $tree->getPreference('EXPAND_SOURCES');
109
110
                    if ($lt > 0) {
111
                        $data .= '<a href="#' . e($id) . '" role="button" data-bs-toggle="collapse" aria-controls="' . e($id) . '" aria-expanded="' . ($expanded ? 'true' : 'false') . '">';
112
                        $data .= view('icons/expand');
113
                        $data .= view('icons/collapse');
114
                        $data .= '</a>';
115
                    } elseif ($ct > 1) {
116
                        $data .= view('icons/spacer');
117
                    }
118
119
                    $value = '<a href="' . e($source->url()) . '">' . $source->fullName() . '</a>';
120
                    $data .= I18N::translate('<span class="label">%1$s:</span> <span class="field" dir="auto">%2$s</span>', I18N::translate('Source'), $value);
121
                    $data .= '</div>';
122
                    $data .= '<div id="' . e($id) . '" class="collapse ' . ($expanded ? 'show' : '') . '">';
123
                    $data .= self::printSourceStructure($tree, self::getSourceStructure($srec));
124
                    $data .= '<div class="indent">';
125
                    ob_start();
126
                    self::printMediaLinks($tree, $srec, $nlevel);
127
                    $data .= ob_get_clean();
128
                    $data .= FunctionsPrint::printFactNotes($tree, $srec, $nlevel);
129
                    $data .= '</div>';
130
                    $data .= '</div>';
131
                }
132
            } else {
133
                $value = '<span class="error">' . $sid . '</span>';
134
                $data .= I18N::translate('<span class="label">%1$s:</span> <span class="field" dir="auto">%2$s</span>', I18N::translate('Source'), $value);
135
            }
136
        }
137
138
        return $data;
139
    }
140
141
    /**
142
     * Print the links to media objects
143
     *
144
     * @param Tree   $tree
145
     * @param string $factrec
146
     * @param int    $level
147
     *
148
     * @return void
149
     */
150
    public static function printMediaLinks(Tree $tree, string $factrec, int $level): void
151
    {
152
        $nlevel = $level + 1;
0 ignored issues
show
Unused Code introduced by
The assignment to $nlevel is dead and can be removed.
Loading history...
153
        if (preg_match_all("/$level OBJE @(.*)@/", $factrec, $omatch, PREG_SET_ORDER) === 0) {
154
            return;
155
        }
156
        $objectNum = 0;
157
        while ($objectNum < count($omatch)) {
158
            $media_id = $omatch[$objectNum][1];
159
            $media    = Registry::mediaFactory()->make($media_id, $tree);
160
            if ($media) {
161
                if ($media->canShow()) {
162
                    echo '<div class="d-flex align-items-center"><div class="p-1">';
163
                    foreach ($media->mediaFiles() as $media_file) {
164
                        echo $media_file->displayImage(100, 100, 'contain', []);
165
                    }
166
                    echo '</div>';
167
                    echo '<div>';
168
                    echo '<a href="', e($media->url()), '">', $media->fullName(), '</a>';
169
                    echo '<p>';
170
171
                    foreach ($media->facts(['SOUR']) as $fact) {
172
                        echo view('fact-gedcom-fields', ['gedcom' => $fact->gedcom(), 'hierarchy' => [$media->tag()], 'tree' => $tree]);
173
                    }
174
175
                    foreach ($media->facts(['NOTE']) as $fact) {
176
                        echo view('fact-gedcom-fields', ['gedcom' => $fact->gedcom(), 'hierarchy' => [$media->tag()], 'tree' => $tree]);
177
                    }
178
179
                    echo '</div>'; //close div "media-display-title"
180
                    echo '</div>'; //close div "media-display"
181
                }
182
            } elseif ($tree->getPreference('HIDE_GEDCOM_ERRORS') === '1') {
183
                echo '<p class="alert alert-danger">', $media_id, '</p>';
184
            }
185
            $objectNum++;
186
        }
187
    }
188
189
    /**
190
     * Print a row for the sources tab on the individual page.
191
     *
192
     * @param Fact $fact
193
     * @param int  $level
194
     *
195
     * @return void
196
     */
197
    public static function printMainSources(Fact $fact, int $level): void
198
    {
199
        $factrec = $fact->gedcom();
200
        $tree    = $fact->record()->tree();
201
202
        $nlevel = $level + 1;
203
        if ($fact->isPendingAddition()) {
204
            $styleadd = 'wt-new';
205
            $can_edit = $level === 1 && $fact->canEdit();
206
        } elseif ($fact->isPendingDeletion()) {
207
            $styleadd = 'wt-old';
208
            $can_edit = false;
209
        } else {
210
            $styleadd = '';
211
            $can_edit = $level === 1 && $fact->canEdit();
212
        }
213
214
        // -- find source for each fact
215
        preg_match_all('/(?:^|\n)(' . $level . ' SOUR (.*)(?:\n[' . $nlevel . '-9] .*)*)/', $fact->gedcom(), $matches, PREG_SET_ORDER);
216
217
        foreach ($matches as $match) {
218
            $srec   = $match[1];
219
            $sid    = $match[2];
220
            $source = Registry::sourceFactory()->make(trim($sid, '@'), $tree);
221
            // Allow access to "1 SOUR @non_existent_source@", so it can be corrected/deleted
222
            if (!$source || $source->canShow()) {
223
                if ($level > 1) {
224
                    echo '<tr class="wt-level-two-source collapse">';
225
                } else {
226
                    echo '<tr>';
227
                }
228
                echo '<th class="';
229
                if ($level > 1) {
230
                    echo 'rela ';
231
                }
232
                echo $styleadd, '">';
233
                echo $fact->label();
234
                if ($can_edit) {
235
                    echo view('fact-edit-links', ['fact' => $fact, 'url' => null]);
236
                }
237
                echo '</th>';
238
                echo '<td class="', $styleadd, '">';
239
                if ($source) {
240
                    echo '<a href="', e($source->url()), '">', $source->fullName(), '</a>';
241
                    // 2 RESN tags. Note, there can be more than one, such as "privacy" and "locked"
242
                    if (preg_match_all("/\n2 RESN (.+)/", $factrec, $rmatches)) {
243
                        $label = Registry::elementFactory()->make($fact->tag() . ':RESN')->label();
244
                        foreach ($rmatches[1] as $rmatch) {
245
                            echo '<br><span class="label">', $label, ':</span> <span class="field">';
246
                            switch ($rmatch) {
247
                                case 'none':
248
                                    // Note: "2 RESN none" is not valid gedcom, and the GUI will not let you add it.
249
                                    // However, webtrees privacy rules will interpret it as "show an otherwise private fact to public".
250
                                    echo '<i class="icon-resn-none"></i> ', I18N::translate('Show to visitors');
251
                                    break;
252
                                case 'privacy':
253
                                    echo '<i class="icon-resn-privacy"></i> ', I18N::translate('Show to members');
254
                                    break;
255
                                case 'confidential':
256
                                    echo '<i class="icon-resn-confidential"></i> ', I18N::translate('Show to managers');
257
                                    break;
258
                                case 'locked':
259
                                    echo '<i class="icon-resn-locked"></i> ', I18N::translate('Only managers can edit');
260
                                    break;
261
                                default:
262
                                    echo $rmatch;
263
                                    break;
264
                            }
265
                            echo '</span>';
266
                        }
267
                    }
268
                    echo self::printSourceStructure($tree, self::getSourceStructure($srec));
269
                    echo '<div class="indent">';
270
                    self::printMediaLinks($tree, $srec, $nlevel);
271
                    if ($nlevel === 2) {
272
                        self::printMediaLinks($tree, $source->gedcom(), 1);
273
                    }
274
                    echo FunctionsPrint::printFactNotes($tree, $srec, $nlevel);
275
                    echo '</div>';
276
                } else {
277
                    echo $sid;
278
                }
279
                echo '</td></tr>';
280
            }
281
        }
282
    }
283
284
    /**
285
     * Print SOUR structure
286
     *  This function prints the input array of SOUR sub-records built by the
287
     *  getSourceStructure() function.
288
     *
289
     * @param Tree                        $tree
290
     * @param array<string|array<string>> $textSOUR
291
     *
292
     * @return string
293
     */
294
    public static function printSourceStructure(Tree $tree, array $textSOUR): string
295
    {
296
        $html = '';
297
298
        if ($textSOUR['PAGE'] !== '') {
299
            $html .= Registry::elementFactory()->make('INDI:SOUR:PAGE')->labelValue($textSOUR['PAGE'], $tree);
0 ignored issues
show
Bug introduced by
It seems like $textSOUR['PAGE'] can also be of type string[]; however, parameter $value of Fisharebest\Webtrees\Con...Interface::labelValue() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

299
            $html .= Registry::elementFactory()->make('INDI:SOUR:PAGE')->labelValue(/** @scrutinizer ignore-type */ $textSOUR['PAGE'], $tree);
Loading history...
300
        }
301
302
        if ($textSOUR['EVEN'] !== '') {
303
            $html .= Registry::elementFactory()->make('INDI:SOUR:EVEN')->labelValue($textSOUR['EVEN'], $tree);
304
305
            if ($textSOUR['ROLE']) {
306
                $html .= Registry::elementFactory()->make('INDI:SOUR:EVEN:ROLE')->labelValue($textSOUR['ROLE'], $tree);
307
            }
308
        }
309
310
        if ($textSOUR['DATE'] !== '') {
311
            $html .= Registry::elementFactory()->make('INDI:SOUR:DATA:DATE')->labelValue($textSOUR['DATE'], $tree);
312
        }
313
314
        foreach ($textSOUR['TEXT'] as $text) {
315
            $html .= Registry::elementFactory()->make('INDI:SOUR:DATA:TEXT')->labelValue($text, $tree);
316
        }
317
318
        if ($textSOUR['QUAY'] !== '') {
319
            $html .= Registry::elementFactory()->make('INDI:SOUR:QUAY')->labelValue($textSOUR['QUAY'], $tree);
320
        }
321
322
        return '<div class="indent">' . $html . '</div>';
323
    }
324
325
    /**
326
     * Extract SOUR structure from the incoming Source sub-record
327
     * The output array is defined as follows:
328
     *  $textSOUR['PAGE'] = Source citation
329
     *  $textSOUR['EVEN'] = Event type
330
     *  $textSOUR['ROLE'] = Role in event
331
     *  $textSOUR['DATA'] = place holder (no text in this sub-record)
332
     *  $textSOUR['DATE'] = Entry recording date
333
     *  $textSOUR['TEXT'] = (array) Text from source
334
     *  $textSOUR['QUAY'] = Certainty assessment
335
     *
336
     * @param string $srec
337
     *
338
     * @return array<array<string>>
339
     */
340
    public static function getSourceStructure(string $srec): array
341
    {
342
        // Set up the output array
343
        $textSOUR = [
344
            'PAGE' => '',
345
            'EVEN' => '',
346
            'ROLE' => '',
347
            'DATE' => '',
348
            'TEXT' => [],
349
            'QUAY' => '',
350
        ];
351
352
        preg_match_all('/^\d (PAGE|EVEN|ROLE|DATE|TEXT|QUAY) ?(.*(\n\d CONT.*)*)$/m', $srec, $matches, PREG_SET_ORDER);
353
354
        foreach ($matches as $match) {
355
            $tag   = $match[1];
356
            $value = $match[2];
357
            $value = preg_replace('/\n\d CONT ?/', "\n", $value);
358
359
            if ($tag === 'TEXT') {
360
                $textSOUR[$tag][] = $value;
361
            } else {
362
                $textSOUR[$tag] = $value;
363
            }
364
        }
365
366
        return $textSOUR;
367
    }
368
369
    /**
370
     * Print a row for the media tab on the individual page.
371
     *
372
     * @param Fact $fact
373
     * @param int  $level
374
     *
375
     * @return void
376
     */
377
    public static function printMainMedia(Fact $fact, int $level): void
378
    {
379
        $tree = $fact->record()->tree();
380
381
        if ($fact->isPendingAddition()) {
382
            $styleadd = 'wt-new';
383
        } elseif ($fact->isPendingDeletion()) {
384
            $styleadd = 'wt-old';
385
        } else {
386
            $styleadd = '';
387
        }
388
389
        // -- find source for each fact
390
        preg_match_all('/(?:^|\n)' . $level . ' OBJE @(.*)@/', $fact->gedcom(), $matches);
391
        foreach ($matches[1] as $xref) {
392
            $media = Registry::mediaFactory()->make($xref, $tree);
393
            // Allow access to "1 OBJE @non_existent_source@", so it can be corrected/deleted
394
            if (!$media instanceof Media || $media->canShow()) {
395
                echo '<tr class="', $styleadd, '">';
396
                echo '<th scope="row">';
397
                echo $fact->label();
398
399
                if ($level === 1 && $fact->canEdit()) {
400
                    echo view('fact-edit-links', ['fact' => $fact, 'url' => null]);
401
                }
402
403
                echo '</th>';
404
                echo '<td>';
405
                if ($media instanceof Media) {
406
                    foreach ($media->mediaFiles() as $media_file) {
407
                        echo '<div>';
408
                        echo $media_file->displayImage(100, 100, 'contain', []);
409
                        echo '<br>';
410
                        echo '<a href="' . e($media->url()) . '"> ';
411
                        echo '<em>';
412
                        echo e($media_file->title() ?: $media_file->filename());
413
                        echo '</em>';
414
                        echo '</a>';
415
                        echo '</div>';
416
                    }
417
418
                    foreach ($media->facts(['NOTE']) as $note) {
419
                        echo view('fact-gedcom-fields', ['gedcom' => $note->gedcom(), 'hierarchy' => [$media->tag()], 'tree' => $tree]);
420
                    }
421
422
                    foreach ($media->facts(['SOUR']) as $note) {
423
                        echo view('fact-gedcom-fields', ['gedcom' => $note->gedcom(), 'hierarchy' => [$media->tag()], 'tree' => $tree]);
424
                    }
425
                } else {
426
                    echo $xref;
427
                }
428
                echo '</td></tr>';
429
            }
430
        }
431
    }
432
}
433