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&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&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&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&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