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
|
|
|
*/ |
16
|
|
|
declare(strict_types=1); |
17
|
|
|
|
18
|
|
|
namespace Fisharebest\Webtrees\Http\Controllers\Admin; |
19
|
|
|
|
20
|
|
|
use Fig\Http\Message\StatusCodeInterface; |
21
|
|
|
use Fisharebest\Algorithm\MyersDiff; |
22
|
|
|
use Fisharebest\Webtrees\Auth; |
23
|
|
|
use Fisharebest\Webtrees\Carbon; |
24
|
|
|
use Fisharebest\Webtrees\Gedcom; |
25
|
|
|
use Fisharebest\Webtrees\GedcomRecord; |
26
|
|
|
use Fisharebest\Webtrees\I18N; |
27
|
|
|
use Fisharebest\Webtrees\Services\DatatablesService; |
28
|
|
|
use Fisharebest\Webtrees\Services\UserService; |
29
|
|
|
use Fisharebest\Webtrees\Tree; |
30
|
|
|
use Illuminate\Database\Capsule\Manager as DB; |
31
|
|
|
use Illuminate\Database\Query\Builder; |
32
|
|
|
use Illuminate\Database\Query\Expression; |
33
|
|
|
use Psr\Http\Message\ResponseInterface; |
34
|
|
|
use Psr\Http\Message\ServerRequestInterface; |
35
|
|
|
use stdClass; |
36
|
|
|
use function explode; |
37
|
|
|
use function implode; |
38
|
|
|
use function preg_replace_callback; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Controller for the changes log. |
42
|
|
|
*/ |
43
|
|
|
class ChangesLogController extends AbstractAdminController |
44
|
|
|
{ |
45
|
|
|
/** |
46
|
|
|
* Show the edit history for a tree. |
47
|
|
|
* |
48
|
|
|
* @param ServerRequestInterface $request |
49
|
|
|
* @param UserService $user_service |
50
|
|
|
* |
51
|
|
|
* @return ResponseInterface |
52
|
|
|
*/ |
53
|
|
|
public function changesLog(ServerRequestInterface $request, UserService $user_service): ResponseInterface |
54
|
|
|
{ |
55
|
|
|
$tree_list = []; |
56
|
|
|
foreach (Tree::getAll() as $tree) { |
57
|
|
|
if (Auth::isManager($tree)) { |
58
|
|
|
$tree_list[$tree->name()] = $tree->title(); |
59
|
|
|
} |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
$user_list = ['' => '']; |
63
|
|
|
foreach ($user_service->all() as $tmp_user) { |
64
|
|
|
$user_list[$tmp_user->userName()] = $tmp_user->userName(); |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
$action = $request->getQueryParams()['action'] ?? ''; |
68
|
|
|
|
69
|
|
|
// @TODO This ought to be a POST action |
70
|
|
|
if ($action === 'delete') { |
71
|
|
|
$this->changesQuery($request)->delete(); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
// First and last change in the database. |
75
|
|
|
$earliest = DB::table('change')->min('change_time'); |
76
|
|
|
$latest = DB::table('change')->max('change_time'); |
77
|
|
|
|
78
|
|
|
$earliest = $earliest ? Carbon::make($earliest) : Carbon::now(); |
79
|
|
|
$latest = $latest ? Carbon::make($latest) : Carbon::now(); |
80
|
|
|
|
81
|
|
|
$earliest = $earliest->toDateString(); |
|
|
|
|
82
|
|
|
$latest = $latest->toDateString(); |
|
|
|
|
83
|
|
|
|
84
|
|
|
$ged = $request->getQueryParams()['ged'] ?? ''; |
85
|
|
|
$from = $request->getQueryParams()['from'] ?? $earliest; |
86
|
|
|
$to = $request->getQueryParams()['to'] ?? $latest; |
87
|
|
|
$type = $request->getQueryParams()['type'] ?? ''; |
88
|
|
|
$oldged = $request->getQueryParams()['oldged'] ?? ''; |
89
|
|
|
$newged = $request->getQueryParams()['newged'] ?? ''; |
90
|
|
|
$xref = $request->getQueryParams()['xref'] ?? ''; |
91
|
|
|
$username = $request->getQueryParams()['username'] ?? ''; |
92
|
|
|
$search = $request->getQueryParams()['search'] ?? []; |
93
|
|
|
$search = $search['value'] ?? null; |
94
|
|
|
|
95
|
|
|
if (!array_key_exists($ged, $tree_list)) { |
96
|
|
|
$ged = reset($tree_list); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
$statuses = [ |
100
|
|
|
'' => '', |
101
|
|
|
/* I18N: the status of an edit accepted/rejected/pending */ |
102
|
|
|
'accepted' => I18N::translate('accepted'), |
103
|
|
|
/* I18N: the status of an edit accepted/rejected/pending */ |
104
|
|
|
'rejected' => I18N::translate('rejected'), |
105
|
|
|
/* I18N: the status of an edit accepted/rejected/pending */ |
106
|
|
|
'pending' => I18N::translate('pending'), |
107
|
|
|
]; |
108
|
|
|
|
109
|
|
|
return $this->viewResponse('admin/changes-log', [ |
110
|
|
|
'action' => $action, |
111
|
|
|
'earliest' => $earliest, |
112
|
|
|
'from' => $from, |
113
|
|
|
'ged' => $ged, |
114
|
|
|
'latest' => $latest, |
115
|
|
|
'newged' => $newged, |
116
|
|
|
'oldged' => $oldged, |
117
|
|
|
'search' => $search, |
118
|
|
|
'statuses' => $statuses, |
119
|
|
|
'title' => I18N::translate('Changes log'), |
120
|
|
|
'to' => $to, |
121
|
|
|
'tree_list' => $tree_list, |
122
|
|
|
'type' => $type, |
123
|
|
|
'username' => $username, |
124
|
|
|
'user_list' => $user_list, |
125
|
|
|
'xref' => $xref, |
126
|
|
|
]); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Show the edit history for a tree. |
131
|
|
|
* |
132
|
|
|
* @param ServerRequestInterface $request |
133
|
|
|
* @param DatatablesService $datatables_service |
134
|
|
|
* @param MyersDiff $myers_diff |
135
|
|
|
* |
136
|
|
|
* @return ResponseInterface |
137
|
|
|
*/ |
138
|
|
|
public function changesLogData(ServerRequestInterface $request, DatatablesService $datatables_service, MyersDiff $myers_diff): ResponseInterface |
139
|
|
|
{ |
140
|
|
|
$query = $this->changesQuery($request); |
141
|
|
|
|
142
|
|
|
$callback = static function (stdClass $row) use ($myers_diff): array { |
143
|
|
|
$old_lines = explode("\n", $row->old_gedcom); |
144
|
|
|
$new_lines = explode("\n", $row->new_gedcom); |
145
|
|
|
|
146
|
|
|
$differences = $myers_diff->calculate($old_lines, $new_lines); |
147
|
|
|
$diff_lines = []; |
148
|
|
|
|
149
|
|
|
foreach ($differences as $difference) { |
150
|
|
|
switch ($difference[1]) { |
151
|
|
|
case MyersDiff::DELETE: |
152
|
|
|
$diff_lines[] = '<del>' . $difference[0] . '</del>'; |
153
|
|
|
break; |
154
|
|
|
case MyersDiff::INSERT: |
155
|
|
|
$diff_lines[] = '<ins>' . $difference[0] . '</ins>'; |
156
|
|
|
break; |
157
|
|
|
default: |
158
|
|
|
$diff_lines[] = $difference[0]; |
159
|
|
|
} |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
// Only convert valid xrefs to links |
163
|
|
|
$tree = Tree::findByName($row->gedcom_name); |
164
|
|
|
$record = GedcomRecord::getInstance($row->xref, $tree); |
|
|
|
|
165
|
|
|
|
166
|
|
|
return [ |
167
|
|
|
$row->change_id, |
168
|
|
|
Carbon::make($row->change_time)->local()->format('Y-m-d H:i:s'), |
169
|
|
|
I18N::translate($row->status), |
170
|
|
|
$record ? '<a href="' . e($record->url()) . '">' . $record->xref() . '</a>' : $row->xref, |
171
|
|
|
'<div class="gedcom-data" dir="ltr">' . |
172
|
|
|
preg_replace_callback( |
173
|
|
|
'/@(' . Gedcom::REGEX_XREF . ')@/', |
174
|
|
|
static function (array $match) use ($tree) : string { |
175
|
|
|
$record = GedcomRecord::getInstance($match[1], $tree); |
|
|
|
|
176
|
|
|
|
177
|
|
|
return $record ? '<a href="' . e($record->url()) . '">' . $match[0] . '</a>' : $match[0]; |
178
|
|
|
}, |
179
|
|
|
implode("\n", $diff_lines) |
180
|
|
|
) . |
181
|
|
|
'</div>', |
182
|
|
|
$row->user_name, |
183
|
|
|
$row->gedcom_name, |
184
|
|
|
]; |
185
|
|
|
}; |
186
|
|
|
|
187
|
|
|
return $datatables_service->handle($request, $query, [], [], $callback); |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* Show the edit history for a tree. |
192
|
|
|
* |
193
|
|
|
* @param ServerRequestInterface $request |
194
|
|
|
* |
195
|
|
|
* @return ResponseInterface |
196
|
|
|
*/ |
197
|
|
|
public function changesLogDownload(ServerRequestInterface $request): ResponseInterface |
198
|
|
|
{ |
199
|
|
|
$content = $this->changesQuery($request) |
200
|
|
|
->get() |
201
|
|
|
->map(static function (stdClass $row): string { |
202
|
|
|
// Convert to CSV |
203
|
|
|
return implode(',', [ |
204
|
|
|
'"' . $row->change_time . '"', |
205
|
|
|
'"' . $row->status . '"', |
206
|
|
|
'"' . $row->xref . '"', |
207
|
|
|
'"' . str_replace('"', '""', $row->old_gedcom) . '"', |
208
|
|
|
'"' . str_replace('"', '""', $row->new_gedcom) . '"', |
209
|
|
|
'"' . str_replace('"', '""', $row->user_name) . '"', |
210
|
|
|
'"' . str_replace('"', '""', $row->gedcom_name) . '"', |
211
|
|
|
]); |
212
|
|
|
}) |
213
|
|
|
->implode("\n"); |
214
|
|
|
|
215
|
|
|
return response($content, StatusCodeInterface::STATUS_OK, [ |
216
|
|
|
'Content-Type' => 'text/csv; charset=utf-8', |
217
|
|
|
'Content-Length' => strlen($content), |
218
|
|
|
'Content-Disposition' => 'attachment; filename="changes.csv"', |
219
|
|
|
]); |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* Generate a query for filtering the changes log. |
224
|
|
|
* |
225
|
|
|
* @param ServerRequestInterface $request |
226
|
|
|
* |
227
|
|
|
* @return Builder |
228
|
|
|
*/ |
229
|
|
|
private function changesQuery(ServerRequestInterface $request): Builder |
230
|
|
|
{ |
231
|
|
|
$from = $request->getQueryParams()['from'] ?? ''; |
232
|
|
|
$to = $request->getQueryParams()['to'] ?? ''; |
233
|
|
|
$type = $request->getQueryParams()['type'] ?? ''; |
234
|
|
|
$oldged = $request->getQueryParams()['oldged'] ?? ''; |
235
|
|
|
$newged = $request->getQueryParams()['newged'] ?? ''; |
236
|
|
|
$xref = $request->getQueryParams()['xref'] ?? ''; |
237
|
|
|
$username = $request->getQueryParams()['username'] ?? ''; |
238
|
|
|
$ged = $request->getQueryParams()['ged'] ?? ''; |
239
|
|
|
$search = $request->getQueryParams()['search'] ?? []; |
240
|
|
|
$search = $search['value'] ?? ''; |
241
|
|
|
|
242
|
|
|
|
243
|
|
|
$query = DB::table('change') |
244
|
|
|
->leftJoin('user', 'user.user_id', '=', 'change.user_id') |
245
|
|
|
->join('gedcom', 'gedcom.gedcom_id', '=', 'change.gedcom_id') |
246
|
|
|
->select(['change.*', new Expression("COALESCE(user_name, '<none>') AS user_name"), 'gedcom_name']); |
247
|
|
|
|
248
|
|
|
if ($search !== '') { |
249
|
|
|
$query->where(static function (Builder $query) use ($search): void { |
250
|
|
|
$query |
251
|
|
|
->whereContains('old_gedcom', $search) |
252
|
|
|
->whereContains('new_gedcom', $search, 'or'); |
253
|
|
|
}); |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
if ($from !== '') { |
257
|
|
|
$query->where('change_time', '>=', $from); |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
if ($to !== '') { |
261
|
|
|
// before end of the day |
262
|
|
|
$query->where('change_time', '<', Carbon::make($to)->addDay()); |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
if ($type !== '') { |
266
|
|
|
$query->where('status', '=', $type); |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
if ($oldged !== '') { |
270
|
|
|
$query->whereContains('old_gedcom', $oldged); |
271
|
|
|
} |
272
|
|
|
if ($newged !== '') { |
273
|
|
|
$query->whereContains('new_gedcom', $oldged); |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
if ($xref !== '') { |
277
|
|
|
$query->where('xref', '=', $xref); |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
if ($username !== '') { |
281
|
|
|
$query->whereContains('user_name', $username); |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
if ($ged !== '') { |
285
|
|
|
$query->whereContains('gedcom_name', $ged); |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
return $query; |
289
|
|
|
} |
290
|
|
|
} |
291
|
|
|
|
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.
This is most likely a typographical error or the method has been renamed.