1<?php
2/**
3 * webtrees: online genealogy
4 * Copyright (C) 2019 webtrees development team
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16namespace Fisharebest\Webtrees;
17
18/**
19 * Defined in session.php
20 *
21 * @global Tree $WT_TREE
22 */
23global $WT_TREE;
24
25use Fisharebest\Webtrees\Controller\SimpleController;
26use Fisharebest\Webtrees\Functions\FunctionsDate;
27use Fisharebest\Webtrees\Functions\FunctionsImport;
28
29define('WT_SCRIPT_NAME', 'edit_changes.php');
30require './includes/session.php';
31
32$controller = new SimpleController;
33$controller
34    ->restrictAccess(Auth::isModerator($WT_TREE))
35    ->setPageTitle(I18N::translate('Pending changes'))
36    ->pageHeader()
37    ->addInlineJavascript("
38		function show_diff(diffurl) {
39			window.opener.location = diffurl;
40			return false;
41		}
42	");
43
44$action    = Filter::get('action');
45$change_id = Filter::getInteger('change_id');
46$index     = Filter::get('index');
47$ged       = Filter::getInteger('ged');
48
49echo '<div id="pending"><h2>', I18N::translate('Pending changes'), '</h2>';
50
51switch ($action) {
52    case 'undo':
53        $gedcom_id = Database::prepare("SELECT gedcom_id FROM `##change` WHERE change_id=?")->execute(array($change_id))->fetchOne();
54        $xref      = Database::prepare("SELECT xref      FROM `##change` WHERE change_id=?")->execute(array($change_id))->fetchOne();
55        // Undo a change, and subsequent changes to the same record
56        Database::prepare(
57        "UPDATE `##change`" .
58        " SET   status     = 'rejected'" .
59        " WHERE status     = 'pending'" .
60        " AND   gedcom_id  = ?" .
61        " AND   xref       = ?" .
62        " AND   change_id >= ?"
63        )->execute(array($gedcom_id, $xref, $change_id));
64        break;
65    case 'accept':
66        $gedcom_id = Database::prepare("SELECT gedcom_id FROM `##change` WHERE change_id=?")->execute(array($change_id))->fetchOne();
67        $xref      = Database::prepare("SELECT xref      FROM `##change` WHERE change_id=?")->execute(array($change_id))->fetchOne();
68        // Accept a change, and all previous changes to the same record
69        $changes = Database::prepare(
70        "SELECT change_id, gedcom_id, gedcom_name, xref, old_gedcom, new_gedcom" .
71        " FROM  `##change` c" .
72        " JOIN  `##gedcom` g USING (gedcom_id)" .
73        " WHERE c.status   = 'pending'" .
74        " AND   gedcom_id  = ?" .
75        " AND   xref       = ?" .
76        " AND   change_id <= ?" .
77        " ORDER BY change_id"
78        )->execute(array($gedcom_id, $xref, $change_id))->fetchAll();
79        foreach ($changes as $change) {
80            if (empty($change->new_gedcom)) {
81                // delete
82                FunctionsImport::updateRecord($change->old_gedcom, $gedcom_id, true);
83            } else {
84                // add/update
85                FunctionsImport::updateRecord($change->new_gedcom, $gedcom_id, false);
86            }
87            Database::prepare("UPDATE `##change` SET status='accepted' WHERE change_id=?")->execute(array($change->change_id));
88            Log::addEditLog("Accepted change {$change->change_id} for {$change->xref} / {$change->gedcom_name} into database");
89        }
90        break;
91    case 'undoall':
92        Database::prepare(
93        "UPDATE `##change`" .
94        " SET status='rejected'" .
95        " WHERE status='pending' AND gedcom_id=?"
96        )->execute(array($WT_TREE->getTreeId()));
97        break;
98    case 'acceptall':
99        $changes = Database::prepare(
100        "SELECT change_id, gedcom_id, gedcom_name, xref, old_gedcom, new_gedcom" .
101        " FROM `##change` c" .
102        " JOIN `##gedcom` g USING (gedcom_id)" .
103        " WHERE c.status='pending' AND gedcom_id=?" .
104        " ORDER BY change_id"
105        )->execute(array($WT_TREE->getTreeId()))->fetchAll();
106        foreach ($changes as $change) {
107            if (empty($change->new_gedcom)) {
108                // delete
109                FunctionsImport::updateRecord($change->old_gedcom, $change->gedcom_id, true);
110            } else {
111                // add/update
112                FunctionsImport::updateRecord($change->new_gedcom, $change->gedcom_id, false);
113            }
114            Database::prepare("UPDATE `##change` SET status='accepted' WHERE change_id=?")->execute(array($change->change_id));
115            Log::addEditLog("Accepted change {$change->change_id} for {$change->xref} / {$change->gedcom_name} into database");
116        }
117        break;
118}
119
120$changed_gedcoms = Database::prepare(
121    "SELECT g.gedcom_name" .
122    " FROM `##change` c" .
123    " JOIN `##gedcom` g USING (gedcom_id)" .
124    " WHERE c.status='pending'" .
125    " GROUP BY g.gedcom_name"
126)->fetchOneColumn();
127
128if ($changed_gedcoms) {
129    $changes = Database::prepare(
130        "SELECT c.*, UNIX_TIMESTAMP(c.change_time) + :offset AS change_timestamp, u.user_name, u.real_name, g.gedcom_name, new_gedcom, old_gedcom" .
131        " FROM `##change` c" .
132        " JOIN `##user`   u USING (user_id)" .
133        " JOIN `##gedcom` g USING (gedcom_id)" .
134        " WHERE c.status='pending'" .
135        " ORDER BY gedcom_id, c.xref, c.change_id"
136    )
137    ->execute(array('offset' => WT_TIMESTAMP_OFFSET))
138    ->fetchAll();
139
140    $output         = '<br><br><table class="list_table">';
141    $prev_xref      = null;
142    $prev_gedcom_id = null;
143    foreach ($changes as $change) {
144        $tree = Tree::findById($change->gedcom_id);
145        preg_match('/^0 (?:@' . WT_REGEX_XREF . '@ )?(' . WT_REGEX_TAG . ')/', $change->old_gedcom . $change->new_gedcom, $match);
146
147
148        switch ($match[1]) {
149            case 'INDI':
150                $record = new Individual($change->xref, $change->old_gedcom, $change->new_gedcom, $tree);
151                break;
152            case 'FAM':
153                $record = new Family($change->xref, $change->old_gedcom, $change->new_gedcom, $tree);
154                break;
155            case 'SOUR':
156                $record = new Source($change->xref, $change->old_gedcom, $change->new_gedcom, $tree);
157                break;
158            case 'REPO':
159                $record = new Repository($change->xref, $change->old_gedcom, $change->new_gedcom, $tree);
160                break;
161            case 'OBJE':
162                $record = new Media($change->xref, $change->old_gedcom, $change->new_gedcom, $tree);
163                break;
164            case 'NOTE':
165                $record = new Note($change->xref, $change->old_gedcom, $change->new_gedcom, $tree);
166                break;
167            default:
168                $record = new GedcomRecord($change->xref, $change->old_gedcom, $change->new_gedcom, $tree);
169                break;
170        }
171        if ($change->xref != $prev_xref || $change->gedcom_id != $prev_gedcom_id) {
172            if ($prev_xref) {
173                $output .= '</table></td></tr>';
174            }
175            $prev_xref      = $change->xref;
176            $prev_gedcom_id = $change->gedcom_id;
177            $output .= '<tr><td class="list_value">';
178            $output .= '<b><a href="#" onclick="return show_diff(\'' . $record->getHtmlUrl() . '\');"> ' . $record->getFullName() . '</a></b>';
179            $output .= '<div class="indent">';
180            $output .= '<table class="list_table"><tr>';
181            $output .= '<td class="list_label">' . I18N::translate('Accept') . '</td>';
182            $output .= '<td class="list_label">' . I18N::translate('Changes') . '</td>';
183            $output .= '<td class="list_label">' . I18N::translate('User') . '</td>';
184            $output .= '<td class="list_label">' . I18N::translate('Date') . '</td>';
185            $output .= '<td class="list_label">' . I18N::translate('Family tree') . '</td>';
186            $output .= '<td class="list_label">' . I18N::translate('Reject') . '</td>';
187            $output .= '</tr>';
188        }
189        $output .= '<td class="list_value"><a href="edit_changes.php?action=accept&amp;change_id=' . $change->change_id . '">' . I18N::translate('Accept') . '</a></td>';
190        $output .= '<td class="list_value">';
191        foreach ($record->getFacts() as $fact) {
192            if ($fact->getTag() != 'CHAN') {
193                if ($fact->isPendingAddition()) {
194                    $output .= '<div class="new" title="' . strip_tags($fact->summary()) . '">' . $fact->getLabel() . '</div>';
195                } elseif ($fact->isPendingDeletion()) {
196                    $output .= '<div class="old" title="' . strip_tags($fact->summary()) . '">' . $fact->getLabel() . '</div>';
197                }
198            }
199        }
200        echo '</td>';
201        $output .= '<td class="list_value"><a href="#" onclick="return reply(\'' . $change->user_name . '\', \'' . I18N::translate('Moderate pending changes') . '\')" title="' . I18N::translate('Send a message') . '">';
202        $output .= Filter::escapeHtml($change->real_name);
203        $output .= ' - ' . Filter::escapeHtml($change->user_name) . '</a></td>';
204        $output .= '<td class="list_value">' . FunctionsDate::formatTimestamp($change->change_timestamp) . '</td>';
205        $output .= '<td class="list_value">' . $change->gedcom_name . '</td>';
206        $output .= '<td class="list_value"><a href="edit_changes.php?action=undo&amp;change_id=' . $change->change_id . '">' . I18N::translate('Reject') . '</a></td>';
207        $output .= '</tr>';
208    }
209    $output .= '</table></td></tr></td></tr></table>';
210
211    //-- Now for the global Action bar:
212    $output2 = '<br><table class="list_table">';
213    // Row 1 column 1: title "Accept all"
214    $output2 .= '<tr><td class="list_label">' . I18N::translate('Accept all changes') . '</td>';
215    // Row 1 column 2: title "Undo all"
216    $output2 .= '<td class="list_label">' . I18N::translate('Reject all changes') . '</td></tr>';
217
218    // Row 2 column 1: action "Accept all"
219    $output2 .= '<tr><td class="list_value">';
220    $count = 0;
221    foreach ($changed_gedcoms as $gedcom_name) {
222        if ($count != 0) {
223            $output2 .= '<br>';
224        }
225        $output2 .= $gedcom_name . ' — ' . '<a href="edit_changes.php?action=acceptall&amp;ged=' . rawurlencode($gedcom_name) . '">' . I18N::translate('Accept all changes') . '</a>';
226        $count++;
227    }
228    $output2 .= '</td>';
229    // Row 2 column 2: action "Undo all"
230    $output2 .= '<td class="list_value">';
231    $count = 0;
232    foreach ($changed_gedcoms as $gedcom_name) {
233        if ($count != 0) {
234            $output2 .= '<br>';
235        }
236        $output2 .= $gedcom_name . ' — ' . '<a href="edit_changes.php?action=undoall&amp;ged=' . rawurlencode($gedcom_name) . '" onclick="return confirm(\'' . I18N::translate('Are you sure you want to reject all the changes to this family tree?') . '\');">' . I18N::translate('Reject all changes') . '</a>';
237        $count++;
238    }
239    $output2 .= '</td></tr></table>';
240
241    echo
242        $output2, $output, $output2,
243        '<br><br><br><br>',
244        '<p id="save-cancel">',
245        '<input type="button" class="cancel" value="', I18N::translate('close'), '" onclick="closePopupAndReloadParent();">',
246        '</p>';
247} else {
248    // No pending changes - refresh the parent window and close this one
249    $controller->addInlineJavascript('closePopupAndReloadParent();');
250}
251
252echo '</div>';
253