xref: /webtrees/resources/views/admin/trees-privacy.phtml (revision ac71572d8462e396ed5a307f05b29381e49f9e6e)
1<?php
2
3declare(strict_types=1);
4
5use Fisharebest\Webtrees\Auth;
6use Fisharebest\Webtrees\Http\RequestHandlers\ControlPanel;
7use Fisharebest\Webtrees\Http\RequestHandlers\ManageTrees;
8use Fisharebest\Webtrees\Http\RequestHandlers\TreePrivacyAction;
9use Fisharebest\Webtrees\I18N;
10use Fisharebest\Webtrees\Tree;
11use Fisharebest\Webtrees\View;
12
13/**
14 * @var array<string,string> $all_tags
15 * @var int                  $count_trees
16 * @var array<string,string> $privacy_constants
17 * @var array<int,object>    $privacy_restrictions
18 * @var string               $title
19 * @var Tree                 $tree
20 */
21
22?>
23
24<?= view('components/breadcrumbs', ['links' => [route(ControlPanel::class) => I18N::translate('Control panel'), route(ManageTrees::class, ['tree' => $tree->name()]) => I18N::translate('Manage family trees'), $title]]) ?>
25
26<h1><?= $title ?></h1>
27
28<form method="post" action="<?= e(route(TreePrivacyAction::class, ['tree' => $tree->name()])) ?>">
29    <!-- REQUIRE_AUTHENTICATION -->
30    <div class="row mb-3">
31        <div class="col-form-label col-sm-4">
32            <label>
33                <?= /* I18N: A configuration setting */ I18N::translate('Show the family tree') ?>
34            </label>
35            <div class="hidden-xs">
36                <span class="badge visitors"><?= I18N::translate('visitors') ?></span>
37                <span class="badge members"><?= I18N::translate('members') ?></span>
38            </div>
39        </div>
40        <div class="col-sm-8">
41            <?= view('components/select', ['name' => 'REQUIRE_AUTHENTICATION', 'selected' => $tree->getPreference('REQUIRE_AUTHENTICATION'), 'options' => ['0' => I18N::translate('Show to visitors'), '1' => I18N::translate('Show to members')]]) ?>
42            <div class="form-text">
43                <?= /* I18N: Help text for the “Family tree” configuration setting */ I18N::translate('Enabling this option will force all visitors to sign in before they can view any data on the website.') ?>
44            </div>
45        </div>
46    </div>
47
48    <!-- SHOW_DEAD_PEOPLE -->
49    <div class="row mb-3">
50        <div class="col-form-label col-sm-4">
51            <label for="SHOW_DEAD_PEOPLE">
52                <?= /* I18N: A configuration setting */ I18N::translate('Show dead individuals') ?>
53            </label>
54            <div class="hidden-xs">
55                <span class="badge visitors"><?= I18N::translate('visitors') ?></span>
56                <span class="badge members"><?= I18N::translate('members') ?></span>
57            </div>
58        </div>
59        <div class="col-sm-8">
60            <?= view('components/select', ['name' => 'SHOW_DEAD_PEOPLE', 'selected' => $tree->getPreference('SHOW_DEAD_PEOPLE'), 'options' => array_slice(Auth::accessLevelNames(), 0, 2, true)]) ?>
61            <div class="form-text">
62                <?= /* I18N: Help text for the “Show dead individuals” configuration setting */ I18N::translate('Set the privacy access level for all dead individuals.') ?>
63            </div>
64        </div>
65    </div>
66
67
68    <!-- MAX_ALIVE_AGE -->
69    <div class="row mb-3">
70        <label class="col-form-label col-sm-4" for="MAX_ALIVE_AGE">
71            <?= I18N::translate('Age at which to assume an individual is dead') ?>
72        </label>
73        <div class="col-sm-8">
74            <input class="form-control" id="MAX_ALIVE_AGE" min="1" max="9999" name="MAX_ALIVE_AGE" required type="number" value="<?= e($tree->getPreference('MAX_ALIVE_AGE')) ?>">
75            <div class="form-text">
76                <?= /* I18N: Help text for the “Age at which to assume an individual is dead” configuration setting */ I18N::translate('If this individual has any events other than death, burial, or cremation more recent than this number of years, they are considered to be “alive”. Children’s birth dates are considered to be such events for this purpose.') ?>
77            </div>
78        </div>
79    </div>
80
81    <!-- HIDE_LIVE_PEOPLE -->
82    <fieldset class="row mb-3">
83        <legend class="col-sm-4 col-form-label">
84            <?= /* I18N: A configuration setting */ I18N::translate('Show living individuals') ?>
85            <div class="hidden-xs">
86                <span class="badge visitors"><?= I18N::translate('visitors') ?></span>
87                <span class="badge members"><?= I18N::translate('members') ?></span>
88            </div>
89        </legend>
90
91        <div class="col-sm-8">
92            <?= view('components/select', ['name' => 'HIDE_LIVE_PEOPLE', 'selected' => $tree->getPreference('HIDE_LIVE_PEOPLE'), 'options' => ['0' => I18N::translate('Show to visitors'), '1' => I18N::translate('Show to members')]]) ?>
93            <div class="form-text">
94                <?= /* I18N: Help text for the “Show living individuals” configuration setting */ I18N::translate('If you show living individuals to visitors, all other privacy restrictions are ignored. Do this only if all the data in your tree is public.') ?>
95            </div>
96        </div>
97    </fieldset>
98
99    <!-- KEEP_ALIVE_YEARS_BIRTH / KEEP_ALIVE_YEARS_DEATH -->
100    <fieldset class="row mb-3">
101        <legend class="col-form-label col-sm-4">
102            <?= /* I18N: A configuration setting. …who were born in the last XX years or died in the    last YY years */ I18N::translate('Extend privacy to dead individuals') ?>
103        </legend>
104        <div class="col-sm-8">
105            <?php
106            echo
107                /* I18N: Extend privacy to dead individuals who were… */ I18N::translate(
108                    'born in the last %1$s years or died in the last %2$s years',
109                    '<input type="text" name="KEEP_ALIVE_YEARS_BIRTH" value="' . $tree->getPreference('KEEP_ALIVE_YEARS_BIRTH') . '" size="5" maxlength="3">',
110                    '<input type="text" name="KEEP_ALIVE_YEARS_DEATH" value="' . $tree->getPreference('KEEP_ALIVE_YEARS_DEATH') . '" size="5" maxlength="3">'
111                ) ?>
112            <div class="form-text">
113                <?= /* I18N: Help text for the “Extend privacy to dead individuals” configuration setting */ I18N::translate('In some countries, privacy laws apply not only to living individuals, but also to those who have died recently. This option will allow you to extend the privacy rules for living individuals to those who were born or died within a specified number of years. Leave these values empty to disable this feature.') ?>
114            </div>
115        </div>
116    </fieldset>
117
118    <!-- SHOW_LIVING_NAMES -->
119    <div class="row mb-3">
120        <div class="col-form-label col-sm-4">
121            <label for="SHOW_LIVING_NAMES">
122                <?= /* I18N: A configuration setting */ I18N::translate('Show names of private individuals') ?>
123            </label>
124            <div class="hidden-xs">
125                <span class="badge visitors"><?= I18N::translate('visitors') ?></span>
126                <span class="badge members"><?= I18N::translate('members') ?></span>
127                <span class="badge managers"><?= I18N::translate('managers') ?></span>
128            </div>
129        </div>
130        <div class="col-sm-8">
131            <?= view('components/select', ['name' => 'SHOW_LIVING_NAMES', 'selected' => $tree->getPreference('SHOW_LIVING_NAMES'), 'options' => array_slice(Auth::accessLevelNames(), 0, 3, true)]) ?>
132            <div class="form-text">
133                <?= /* I18N: Help text for the “Show names of private individuals” configuration setting */ I18N::translate('This option will show the names (but no other details) of private individuals. Individuals are private if they are still alive or if a privacy restriction has been added to their individual record. To hide a specific name, add a privacy restriction to that name record.') ?>
134            </div>
135        </div>
136    </div>
137
138    <!-- SHOW_PRIVATE_RELATIONSHIPS -->
139    <div class="row mb-3">
140        <div class="col-form-label col-sm-4">
141            <label for="SHOW_PRIVATE_RELATIONSHIPS">
142                <?= /* I18N: A configuration setting */ I18N::translate('Show private relationships') ?>
143            </label>
144            <div class="hidden-xs">
145                <span class="badge visitors"><?= I18N::translate('visitors') ?></span>
146                <span class="badge members"><?= I18N::translate('members') ?></span>
147            </div>
148        </div>
149        <div class="col-sm-8">
150            <?= view('components/select', ['name' => 'SHOW_PRIVATE_RELATIONSHIPS', 'selected' => $tree->getPreference('SHOW_PRIVATE_RELATIONSHIPS'), 'options' => ['0' => I18N::translate('Hide from everyone'), '1' => I18N::translate('Show to visitors')]]) ?>
151            <div class="form-text">
152                <?= /* I18N: Help text for the “Show private relationships” configuration setting */ I18N::translate('This option will retain family links in private records. This means that you will see empty “private” boxes on the pedigree chart and on other charts with private individuals.') ?>
153            </div>
154        </div>
155    </div>
156    <h2><?= /* I18N: Privacy restrictions are set by RESN tags in GEDCOM. */ I18N::translate('Privacy restrictions') ?></h2>
157    <p>
158        <?= /* I18N: Privacy restrictions are RESN tags in GEDCOM. */ I18N::translate('You can set the access for a specific record, fact, or event by adding a restriction to it. If a record, fact, or event does not have a restriction, the following default restrictions will be used.') ?>
159    </p>
160
161    <script id="new-resn-template" type="text/html">
162        <tr>
163            <td class="w-50">
164                <select class="form-select record-type-selector mb-3">
165                    <option value="all"><?= I18N::translate('All records') ?></option>
166                    <option value="individual"><?= I18N::translate('Individual') ?></option>
167                    <option value="family"><?= I18N::translate('Family') ?></option>
168                    <option value="source"><?= I18N::translate('Source') ?></option>
169                    <option value="repository"><?= I18N::translate('Repository') ?></option>
170                    <option value="note"><?= I18N::translate('Note') ?></option>
171                    <option value="media"><?= I18N::translate('Media object') ?></option>
172                </select>
173
174                <div class="select-record select-all">
175                    <div class="d-none">
176                        <select name="xref[]" required="required">
177                            <option selected="selected"></option>
178                        </select>
179                    </div>
180                </div>
181
182                <div class="select-record select-individual d-none">
183                    <?= view('components/select-individual', ['name' => 'xref[]', 'id' => '', 'tree' => $tree, 'disabled' => true, 'required' => true]) ?>
184                </div>
185
186                <div class="select-record select-family d-none">
187                    <?= view('components/select-family', ['name' => 'xref[]', 'id' => '', 'tree' => $tree, 'disabled' => true, 'required' => true]) ?>
188                </div>
189
190                <div class="select-record select-source d-none">
191                    <?= view('components/select-source', ['name' => 'xref[]', 'id' => '', 'tree' => $tree, 'disabled' => true, 'required' => true]) ?>
192                </div>
193
194                <div class="select-record select-repository d-none">
195                    <?= view('components/select-repository', ['name' => 'xref[]', 'id' => '', 'tree' => $tree, 'disabled' => true, 'required' => true]) ?>
196                </div>
197
198                <div class="select-record select-note d-none">
199                    <?= view('components/select-note', ['name' => 'xref[]', 'id' => '', 'tree' => $tree, 'disabled' => true, 'required' => true]) ?>
200                </div>
201
202                <div class="select-record select-media d-none">
203                    <?= view('components/select-media', ['name' => 'xref[]', 'id' => '', 'tree' => $tree, 'disabled' => true, 'required' => true]) ?>
204                </div>
205            </td>
206
207            <td>
208                <?= view('components/select', ['name' => 'tag_type[]', 'id' => '', 'selected' => '', 'options' => $all_tags]) ?>
209            </td>
210
211            <td>
212                <?= view('components/select', ['name' => 'resn[]', 'id' => '', 'selected' => 'privacy', 'options' => $privacy_constants]) ?>
213            </td>
214
215            <td>
216            </td>
217        </tr>
218    </script>
219
220    <table class="table table-bordered table-sm table-hover" id="default-resn">
221        <caption class="visually-hidden">
222            <?= I18N::translate('Privacy restrictions - these apply to records and facts that do not contain a GEDCOM RESN tag') ?>
223        </caption>
224        <thead>
225            <tr>
226                <th class="w-50">
227                    <?= I18N::translate('Record') ?>
228                </th>
229                <th>
230                    <?= I18N::translate('Fact or event') ?>
231                </th>
232                <th>
233                    <?= I18N::translate('Access level') ?>
234                </th>
235                <th>
236                    <button class="btn btn-primary" id="add-resn" type="button">
237                <?= view('icons/add') ?>
238                        <?= /* I18N: A button label. */ I18N::translate('add') ?>
239                    </button>
240                </th>
241            </tr>
242        </thead>
243        <tbody>
244            <?php foreach ($privacy_restrictions as $privacy_restriction) : ?>
245                <tr>
246                    <td>
247                        <?php if ($privacy_restriction->record) : ?>
248                            <a href="<?= e($privacy_restriction->record->url()) ?>"><?= $privacy_restriction->record->fullName() ?></a>
249                        <?php elseif ($privacy_restriction->xref) : ?>
250                            <div class="text-danger">
251                                <?= $privacy_restriction->xref ?><?= I18N::translate('This record does not exist.') ?>
252                            </div>
253                        <?php else : ?>
254                            <div class="text-muted">
255                                <?= I18N::translate('All records') ?>
256                            </div>
257                        <?php endif ?>
258                    </td>
259                    <td>
260                        <?php if ($privacy_restriction->tag_label === '') : ?>
261                            <div class="text-muted">
262                                <?= I18N::translate('All facts and events') ?>
263                            </div>
264                        <?php else : ?>
265                            <?= $privacy_restriction->tag_label ?>
266                        <?php endif ?>
267                    </td>
268                    <td>
269                        <?= Auth::privacyRuleNames()[$privacy_restriction->resn] ?>
270                    </td>
271                    <td>
272                        <label for="delete-<?= $privacy_restriction->default_resn_id ?>">
273                            <input id="delete-<?= $privacy_restriction->default_resn_id ?>" name="delete[]" type="checkbox" value="<?= $privacy_restriction->default_resn_id ?>">
274                            <?= I18N::translate('Delete') ?>
275                        </label>
276                    </td>
277                </tr>
278            <?php endforeach ?>
279        </tbody>
280    </table>
281
282    <div class="row mb-3">
283        <div class="offset-sm-4 col-sm-8">
284            <button type="submit" class="btn btn-primary">
285                <?= view('icons/save') ?>
286                <?= I18N::translate('save') ?>
287            </button>
288
289            <a class="btn btn-secondary" href="<?= route(ManageTrees::class, ['tree' => $tree->name()]) ?>">
290                <?= view('icons/cancel') ?>
291                <?= I18N::translate('cancel') ?>
292            </a>
293            <!-- Coming soon
294            <div class="form-check">
295                <?php if ($count_trees > 1) : ?>
296                <label>
297                    <input type="checkbox" name="all_trees">
298                    <?= /* I18N: Label for checkbox */ I18N::translate('Apply these preferences to all family trees') ?>
299                </label>
300                <?php endif ?>
301            </div>
302            <div class="form-check">
303                <label>
304                    <input type="checkbox" name="new_trees">
305                    <?= /* I18N: Label for checkbox */ I18N::translate('Apply these preferences to new family trees') ?>
306                </label>
307            </div>
308            -->
309        </div>
310    </div>
311
312    <?= csrf_field() ?>
313</form>
314
315<?php View::push('javascript') ?>
316<script>
317  'use strict';
318
319  /**
320   * Hide/show the feedback labels for a privacy option.
321   *
322   * @param sel    the control to change
323   * @param who    "visitors", "members" or "managers"
324   * @param access true or false
325   */
326  function setPrivacyFeedback (sel, who, access) {
327    var formGroup = $(sel).closest('.row');
328
329    if (access) {
330      $('.' + who, formGroup).addClass('bg-success').removeClass('bg-secondary');
331      $('.' + who + ' i', formGroup).addClass('fa-check').removeClass('fa-times');
332    } else {
333      $('.' + who, formGroup).addClass('bg-secondary').removeClass('bg-success');
334      $('.' + who + ' i', formGroup).addClass('fa-times').removeClass('fa-check');
335    }
336  }
337
338  /**
339   * Update all the privacy feedback labels.
340   */
341  function updatePrivacyFeedback () {
342    var requireAuthentication = parseInt($('[name=REQUIRE_AUTHENTICATION]').val(), 10);
343    var showDeadPeople = parseInt($('[name=SHOW_DEAD_PEOPLE]').val(), 10);
344    var hideLivePeople = parseInt($('[name=HIDE_LIVE_PEOPLE]').val(), 10);
345    var showLivingNames = parseInt($('[name=SHOW_LIVING_NAMES]').val(), 10);
346    var showPrivateRelationships = parseInt($('[name=SHOW_PRIVATE_RELATIONSHIPS]').val(), 10);
347
348    setPrivacyFeedback('[name=REQUIRE_AUTHENTICATION]', 'visitors', requireAuthentication === 0);
349    setPrivacyFeedback('[name=REQUIRE_AUTHENTICATION]', 'members', true);
350
351    setPrivacyFeedback('[name=SHOW_DEAD_PEOPLE]', 'visitors', requireAuthentication === 0 && (showDeadPeople >= 2 || hideLivePeople === 0));
352    setPrivacyFeedback('[name=SHOW_DEAD_PEOPLE]', 'members', showDeadPeople >= 1 || hideLivePeople === 0);
353
354    setPrivacyFeedback('[name=HIDE_LIVE_PEOPLE]', 'visitors', requireAuthentication === 0 && hideLivePeople === 0);
355    setPrivacyFeedback('[name=HIDE_LIVE_PEOPLE]', 'members', true);
356
357    setPrivacyFeedback('[name=SHOW_LIVING_NAMES]', 'visitors', requireAuthentication === 0 && showLivingNames >= 2);
358    setPrivacyFeedback('[name=SHOW_LIVING_NAMES]', 'members', showLivingNames >= 1);
359    setPrivacyFeedback('[name=SHOW_LIVING_NAMES]', 'managers', showLivingNames >= 0);
360
361    setPrivacyFeedback('[name=SHOW_PRIVATE_RELATIONSHIPS]', 'visitors', requireAuthentication === 0 && showPrivateRelationships >= 1);
362    setPrivacyFeedback('[name=SHOW_PRIVATE_RELATIONSHIPS]', 'members', showPrivateRelationships >= 1);
363  }
364
365  // Activate the privacy feedback labels.
366  updatePrivacyFeedback();
367  $('[name=REQUIRE_AUTHENTICATION], [name=HIDE_LIVE_PEOPLE], [name=SHOW_DEAD_PEOPLE], [name=SHOW_LIVING_NAMES], [name=SHOW_PRIVATE_RELATIONSHIPS]').on('change', function () {
368    updatePrivacyFeedback();
369  });
370
371  // Mute a line when it is marked for deletion
372  $('#default-resn').on('click', 'input[type=checkbox]', function () {
373    if ($(this).prop('checked')) {
374      $($(this).closest('tr').addClass('text-muted'));
375    } else {
376      $($(this).closest('tr').removeClass('text-muted'));
377    }
378  });
379
380  // Add a new row to the table
381  $('#add-resn').on('click', function () {
382    $('#default-resn tbody').prepend($('#new-resn-template').html());
383
384    document.querySelectorAll('#default-resn tbody tr:first-child select.tom-select')
385      .forEach(element => webtrees.initializeTomSelect(element));
386
387    let row = document.getElementById('default-resn').querySelector('tbody tr');
388    webtrees.initializeIFSRO(row.querySelector('select'), row);
389  });
390</script>
391<?php View::endpush() ?>
392