-
Notifications
You must be signed in to change notification settings - Fork 29
Expand file tree
/
Copy pathdiff.php
More file actions
376 lines (332 loc) · 13.9 KB
/
diff.php
File metadata and controls
376 lines (332 loc) · 13.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
<?php
$relPath = "./../../pinc/";
include_once($relPath.'base.inc');
include_once($relPath.'theme.inc');
include_once($relPath.'Project.inc');
include_once($relPath.'links.inc');
include_once($relPath."DifferenceEngineWrapperTable.inc");
include_once($relPath."DifferenceEngineWrapperBB.inc");
include_once($relPath."PageUnformatter.inc"); // PageUnformatter()
require_login();
$projectid = get_projectID_param($_GET, 'project');
$image = get_page_image_param($_GET, 'image', true);
// older code used round numbers, which we keep for backwards compatibility
if (isset($_GET['L_round_num'])) {
$L_round_num = get_integer_param($_GET, 'L_round_num', null, 0, Rounds::get_last()->round_number);
$R_round_num = get_integer_param($_GET, 'R_round_num', null, 0, Rounds::get_last()->round_number);
$L_round = Rounds::get_by_number($L_round_num) ?? get_OCR_pseudoround();
$R_round = Rounds::get_by_number($R_round_num) ?? get_OCR_pseudoround();
} else {
$L_round = get_round_or_ocr_param($_GET, "L_round", null, false);
$R_round = get_round_or_ocr_param($_GET, "R_round", null, false);
}
$format = get_enumerated_param($_GET, "format", null, ["keep", "remove"], true);
$only_nonempty_diffs = @$_GET['only_nonempty_diffs'] === 'on';
$bb_diffs = (@$_GET['bb_diffs'] === 'on' and user_can_mentor_in_any_round());
$project = new Project($projectid);
$state = $project->state;
$project_title = $project->nameofwork;
if (!$project->pages_table_exists) {
// This shouldn't normally happen --
// if the page table doesn't exist, a "diff" link shouldn't be shown.
// But a user might have a bookmarked or otherwise saved a 'diff' URL.
echo "<p>", _("Page details are not available for this project."), "</p>\n";
echo "<p>", _("Project ID"), ": $projectid</p>\n";
echo "<p>", _("Title"), ": " . html_safe($project_title) . "</p>\n";
exit;
}
// --------------------------------------------------------------
// get information about this diff
$L_format = $L_round->id == "OCR" ? false : is_formatting_round($L_round);
$R_format = $R_round->id == "OCR" ? false : is_formatting_round($R_round);
if (!$format) { // no parameter passed
// default to remove format if only one of the rounds is formatting
$format = ($L_format xor $R_format) ? "remove" : "keep";
}
// Create a label for the two columns with a link to the page text in the
// Image Viewer. $projectid & $image have been validated above and
// L/R_round_name are known round names or 'OCR', so no need to escape them.
$L_label = new_window_link("../page_browser.php?project=$projectid&imagefile=$image&mode=text&round_id=$L_round->id", $L_round->id);
$R_label = new_window_link("../page_browser.php?project=$projectid&imagefile=$image&mode=text&round_id=$R_round->id", $R_round->id);
$query = sprintf(
"
SELECT $L_round->text_column_name, $R_round->text_column_name,
$L_round->user_column_name, $R_round->user_column_name
FROM $project->projectid
WHERE image='%s'
",
DPDatabase::escape($image)
);
$res = DPDatabase::query($query);
[$L_text, $R_text, $L_user, $R_user] = mysqli_fetch_row($res);
$can_see_names_for_this_page = can_see_names_for_page($projectid, $image);
if ($can_see_names_for_this_page) {
$L_label .= " ($L_user)";
$R_label .= " ($R_user)";
}
if ($format == "remove") {
$un_formatter = new PageUnformatter();
// also remove blank lines and leading and trailing spaces
$L_text = $un_formatter->remove_formatting($L_text, false);
$R_text = $un_formatter->remove_formatting($R_text, false);
$link_text = _('Difference for page %s with formatting removed');
} else {
$link_text = _('Difference for page %s');
}
// now have the image, users, labels etc all set up
// -----------------------------------------------------------------------------
$title = sprintf(_('Difference for page %s'), $image);
$image_url = "$code_url/tools/page_browser.php?project=$projectid&imagefile=$image";
$image_link = sprintf($link_text, new_window_link($image_url, $image));
$extra_args = [
"css_data" => get_DifferenceEngine_css_data(),
"js_data" => "
function copyToClip(textstring) {
navigator.clipboard.writeText(textstring).catch(function () {alert('Unable to copy!');});
}
",
];
output_header("$title: $project_title", NO_STATSBAR, $extra_args);
echo "<h1>" . html_safe($project_title) . "</h1>\n";
echo "<h2>$image_link</h2>\n";
$navigation_text = get_navigation(
$project,
$image,
$L_round,
$R_round,
$L_user,
$format,
$only_nonempty_diffs,
$bb_diffs
);
echo $navigation_text;
echo "\n<p>" . return_to_project_page_link($projectid, ["expected_state=$state"]) . "\n";
if ($L_format || $R_format) {
// show option to change compare method
$format_save = array_get($_GET, "format", $format);
if ($format == "remove") {
$format_label = _("Compare with formatting");
$_GET["format"] = "keep";
} else {
$format_label = _("Compare without formatting");
$_GET["format"] = "remove";
}
$format_url = "?" . attr_safe(http_build_query($_GET));
$format_label = html_safe($format_label);
echo "| <a href='$format_url'>$format_label</a>\n";
// Restore $_GET in case needed below
$_GET["format"] = $format_save;
}
if (user_can_mentor_in_any_round()) {
// show option to change diff style
$bb_diffs_save = array_get($_GET, "bb_diffs", "off");
if ($bb_diffs) {
$bb_diffs_label = _("Show table-style diff");
$_GET["bb_diffs"] = "off";
} else {
$bb_diffs_label = _("Show BBCode-style diff");
$_GET["bb_diffs"] = "on";
}
$bb_diffs_url = "?" . attr_safe(http_build_query($_GET));
$bb_diffs_label = html_safe($bb_diffs_label);
echo "| <a href='$bb_diffs_url'>$bb_diffs_label</a>\n";
// Restore $_GET in case needed below
$_GET["bb_diffs"] = $bb_diffs_save;
}
echo "</p>\n";
// ---------------------------------------------------------
$diffurl = "$code_url/tools/project_manager/diff.php?project=$projectid&image=$image&L_round=$L_round->id&R_round=$R_round->id";
$diffEngine = $bb_diffs ? new DifferenceEngineWrapperBBHTML($diffurl) : new DifferenceEngineWrapperTable();
$diffEngine->setText($L_text, $R_text);
echo $diffEngine->getDiff($L_label, $R_label);
// don't print out the navigation bit again if there is no difference
// at the top of the page it's buttons, then project page
// we reverse the order here for symmetry :)
if ($L_text != $R_text) {
echo "\n<p>" . return_to_project_page_link($projectid, ["expected_state=$state"]) . "\n";
echo $navigation_text;
}
// Load an associative array for which pages in the project have diffs.
function load_page_diff_array(Project $project, $L_round, $R_round, bool $include_no_format = false): array
{
if ($include_no_format) {
$un_formatter = new PageUnformatter();
$text_columns = "
$L_round->text_column_name as L_text,
$R_round->text_column_name as R_text,
";
} else {
$text_columns = "";
}
$sql = "
SELECT image,
$L_round->user_column_name as username,
$text_columns
CAST($L_round->text_column_name AS BINARY) = CAST($R_round->text_column_name AS BINARY) AS is_same
FROM $project->projectid
ORDER BY image ASC
";
$res = DPDatabase::query($sql);
$result = [];
while ($row = mysqli_fetch_assoc($res)) {
$result[$row["image"]] = [
"username" => $row["username"],
"is_diff" => ! $row["is_same"],
];
if ($include_no_format) {
$L_text = $un_formatter->remove_formatting($row["L_text"], false);
$R_text = $un_formatter->remove_formatting($row["R_text"], false);
$result[$row["image"]]["is_diff_without_formatting"] = $L_text != $R_text;
}
}
return $result;
}
/**
* Build up the text for the navigation bit, so we can repeat it
* again at the bottom of the page
*/
function get_navigation(
$project,
$image,
$L_round,
$R_round,
$L_user,
$format,
$only_nonempty_diffs,
$bb_diffs
) {
$navigation_text = "";
$jump_to_js = "this.form.image.value=this.form.jumpto[this.form.jumpto.selectedIndex].value; this.form.submit();";
$navigation_text .= "\n<form method='get' action='diff.php'>";
$navigation_text .= "\n<input type='hidden' name='project' value='$project->projectid'>";
$navigation_text .= "\n<input type='hidden' name='image' value='$image'>";
$navigation_text .= "\n<input type='hidden' name='L_round' value='$L_round->id'>";
$navigation_text .= "\n<input type='hidden' name='R_round' value='$R_round->id'>";
$navigation_text .= "\n<input type='hidden' name='format' value='$format'>";
if ($bb_diffs) {
$navigation_text .= "\n<input type='hidden' name='bb_diffs' value='on'>";
}
$navigation_text .= "\n" . _("Jump to") . ": <select name='jumpto' onChange='$jump_to_js'>\n";
$diff_array = load_page_diff_array($project, $L_round, $R_round, $format == "remove");
$prev_image = "";
$next_image = "";
$prev_from_proofer = "";
$next_from_proofer = "";
$got_there = false;
// construct the dropdown; work out where previous and next buttons should take us
foreach ($diff_array as $this_val => $diff_record) {
$this_user = $diff_record["username"];
$is_diff = $format == "remove" ? $diff_record["is_diff_without_formatting"] : $diff_record["is_diff"];
$navigation_text .= "\n<option value='$this_val'";
if ($this_val == $image) {
$navigation_text .= " selected"; // make the correct element of the drop down selected
$got_there = true;
} elseif ($only_nonempty_diffs && !$is_diff) {
$navigation_text .= " disabled"; // Disable empty diffs in the dropdown and skip the other checks
} elseif ($got_there) {
// we are at the one after the current one
if ($next_image == "") {
$next_image = $this_val;
}
if ($next_from_proofer == "" && $this_user == $L_user) {
$next_from_proofer = $this_val;
}
} elseif (!$got_there) {
$prev_image = $this_val; // keep track of what the previous image was
if ($this_user == $L_user) {
$prev_from_proofer = $this_val;
}
}
$navigation_text .= ">$this_val</option>";
}
$navigation_text .= "\n</select>";
$previous_js = "this.form.image.value='$prev_image'; this.form.submit();";
$next_js = "this.form.image.value='$next_image'; this.form.submit();";
$previous_from_proofer_js = "this.form.image.value='$prev_from_proofer'; this.form.submit();";
$next_from_proofer_js = "this.form.image.value='$next_from_proofer'; this.form.submit();";
$navigation_text .= "\n<input type='button' value='" . attr_safe(_("Previous")) . "' onClick=\"$previous_js\"";
if ($prev_image == "") {
$navigation_text .= " disabled";
}
$navigation_text .= ">";
$navigation_text .= "\n<input type='button' value='" . attr_safe(_("Next")) . "' onClick=\"$next_js\"";
if ($next_image == "") {
$navigation_text .= " disabled";
}
$navigation_text .= ">";
if (can_navigate_by_proofer($project->projectid, $L_user)) {
$navigation_text .= "\n<input type='button' value='" . attr_safe(_("Proofreader previous")) . "' onClick=\"$previous_from_proofer_js\"";
if ($prev_from_proofer == "") {
$navigation_text .= " disabled";
}
$navigation_text .= ">";
$navigation_text .= "\n<input type='button' value='" . attr_safe(_("Proofreader next")) . "' onClick=\"$next_from_proofer_js\"";
if ($next_from_proofer == "") {
$navigation_text .= " disabled";
}
$navigation_text .= ">";
}
$checked_attribute = $only_nonempty_diffs ? 'checked' : '';
$checkbox_title = $format == "remove" ? _('Skip empty diffs, ignoring formatting') : _('Skip empty diffs');
$navigation_text .= "\n<input type='checkbox' name='only_nonempty_diffs' $checked_attribute id='only_nonempty_diffs' onclick='this.form.submit()'>\n";
$navigation_text .= "\n<label for='only_nonempty_diffs'>" . html_safe($checkbox_title) . "</label>\n";
$navigation_text .= "\n</form>\n";
return $navigation_text;
}
/**
* Discover whether the user is allowed to see proofreader names for this page
*/
function can_see_names_for_page($projectid, $image)
{
global $pguser;
// If requester isn't logged in, they can't see any names.
if ($pguser == '') {
return false;
}
$project = new Project($projectid);
$answer = $project->names_can_be_seen_by_current_user; // can see for all pages
if (! $answer) {
$fields = "";
foreach (Rounds::get_all() as $round) {
if ($fields != "") {
$fields .= ", ";
}
$fields .= $round->user_column_name;
}
validate_projectID($projectid);
$query = sprintf(
"SELECT $fields from $projectid WHERE image = '%s'",
DPDatabase::escape($image)
);
$res = DPDatabase::query($query);
$page_res = mysqli_fetch_array($res);
foreach ($page_res as $page_user) {
if ($page_user == $pguser) {
$answer = true;
break;
}
}
}
return $answer;
}
/**
* Discover whether the user is allowed to navigate by proofreader for this page
*/
function can_navigate_by_proofer($projectid, $L_user)
{
global $pguser;
$answer = false;
// If user isn't logged in, they definitely can't
if ($pguser == '') {
return false;
}
$project = new Project($projectid);
// if user can manage project, or is evaluator they can
$answer = $project->can_be_managed_by_current_user ||
user_is_an_access_request_reviewer();
// otherwise, they can if this diff is one of theirs
if (! $answer) {
$answer = ($pguser == $L_user);
}
return $answer;
}