231 lines
8.1 KiB
PHP
231 lines
8.1 KiB
PHP
|
<?php
|
||
|
use Moebooru\Versioning as Versioned;
|
||
|
|
||
|
class HistoryController extends ApplicationController
|
||
|
{
|
||
|
public function index()
|
||
|
{
|
||
|
$this->helper('Tag', 'Post');
|
||
|
|
||
|
$search = trim($this->params()->search) ?: "";
|
||
|
|
||
|
$q = [
|
||
|
'keywords' => []
|
||
|
];
|
||
|
|
||
|
if ($search) {
|
||
|
foreach (explode(' ', $search) as $s) {
|
||
|
if (preg_match('/^(.+?):(.*)/', $s, $m)) {
|
||
|
$search_type = $m[1];
|
||
|
$param = $m[2];
|
||
|
|
||
|
if ($search_type == "user") {
|
||
|
$q['user'] = $param;
|
||
|
} elseif ($search_type == "change") {
|
||
|
$q['change'] = (int)$param;
|
||
|
} elseif ($search_type == "type") {
|
||
|
$q['type'] = $param;
|
||
|
} elseif ($search_type == "id") {
|
||
|
$q['id'] = (int)$param;
|
||
|
} elseif ($search_type == "field") {
|
||
|
# 'type' must also be set for this to be used.
|
||
|
$q['field'] = $param;
|
||
|
} else {
|
||
|
# pool'123'
|
||
|
$q['type'] = $search_type;
|
||
|
$q['id'] = (int)$param;
|
||
|
}
|
||
|
} else {
|
||
|
$q['keywords'][] = $s;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$inflector = Rails::services()->get('inflector');
|
||
|
|
||
|
if (!empty($q['type'])) {
|
||
|
$q['type'] = $inflector->pluralize($q['type']);
|
||
|
}
|
||
|
if (!empty($q['inner_type'])) {
|
||
|
$q['inner_type'] = $inflector->pluralize($q['inner_type']);
|
||
|
}
|
||
|
|
||
|
# If notes'id' has been specified, search using the inner key in history_changes
|
||
|
# rather than the grouping table in histories. We don't expose this in general.
|
||
|
# Searching based on hc.table_name without specifying an ID is slow, and the
|
||
|
# details here shouldn't be visible anyway.
|
||
|
if (array_key_exists('type', $q) and array_key_exists('id', $q) and $q['type'] == "notes") {
|
||
|
$q['inner_type'] = $q['type'];
|
||
|
$q['remote_id'] = $q['id'];
|
||
|
|
||
|
unset($q['type']);
|
||
|
unset($q['id']);
|
||
|
}
|
||
|
|
||
|
$query = History::none();
|
||
|
|
||
|
$hc_conds = [];
|
||
|
$hc_cond_params = [];
|
||
|
|
||
|
if (!empty($q['user'])) {
|
||
|
$user = User::where('name', $q['user'])->first();
|
||
|
if ($user) {
|
||
|
$query->where("histories.user_id = ?", $user->id);
|
||
|
} else {
|
||
|
$query->where("false");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!empty($q['id'])) {
|
||
|
$query->where("group_by_id = ?", $q['id']);
|
||
|
}
|
||
|
|
||
|
if (!empty($q['type'])) {
|
||
|
$query->where("group_by_table = ?", $q['type']);
|
||
|
}
|
||
|
|
||
|
if (!empty($q['change'])) {
|
||
|
$query->where("histories.id = ?", $q['change']);
|
||
|
}
|
||
|
|
||
|
if (!empty($q['inner_type'])) {
|
||
|
$q['inner_type'] = $inflector->pluralize($q['inner_type']);
|
||
|
|
||
|
$hc_conds[] = "hc.table_name = ?";
|
||
|
$hc_cond_params[] = $q['inner_type'];
|
||
|
}
|
||
|
|
||
|
if (!empty($q['remote_id'])) {
|
||
|
$hc_conds[] = "hc.remote_id = ?";
|
||
|
$hc_cond_params[] = $q['remote_id'];
|
||
|
}
|
||
|
|
||
|
if ($q['keywords']) {
|
||
|
$hc_conds[] = 'hc.value LIKE ?';
|
||
|
$hc_cond_params[] = '%' . implode('%', $q['keywords']) . '%';
|
||
|
}
|
||
|
|
||
|
if (!empty($q['field']) and !empty($q['type'])) {
|
||
|
# Look up a particular field change, eg. "type'posts' field'rating'".
|
||
|
# XXX: The WHERE id IN (SELECT id...) used to implement this is slow when we don't have
|
||
|
# anything } else { filtering the results.
|
||
|
$field = $q['field'];
|
||
|
$table = $q['type'];
|
||
|
|
||
|
# For convenience:
|
||
|
if ($field == "tags") {
|
||
|
$field = "cached_tags";
|
||
|
}
|
||
|
|
||
|
# Look up the named class.
|
||
|
if (!Versioned::is_versioned_class($cls)) {
|
||
|
$query->where("false");
|
||
|
} else {
|
||
|
$hc_conds[] = "hc.column_name = ?";
|
||
|
$hc_cond_params[] = $field;
|
||
|
|
||
|
# A changes that has no previous value is the initial value for that object. Don't show
|
||
|
# these changes unless they're different from the default for that field.
|
||
|
list ($default_value, $has_default) = $cls::versioning()->get_versioned_default($field);
|
||
|
if ($has_default) {
|
||
|
$hc_conds[] = "(hc.previous_id IS NOT NULL OR value <> ?)";
|
||
|
$hc_cond_params[] = $default_value;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($hc_conds) {
|
||
|
array_unshift($hc_cond_params, 'histories.id IN (SELECT history_id FROM history_changes hc JOIN histories h ON (hc.history_id = h.id) WHERE ' . implode(" AND ", $hc_conds) . ')');
|
||
|
call_user_func_array([$query, 'where'], $hc_cond_params);
|
||
|
}
|
||
|
|
||
|
if (!empty($q['type']) and empty($q['change'])) {
|
||
|
$this->type = $q['type'];
|
||
|
} else {
|
||
|
$this->type = "all";
|
||
|
}
|
||
|
|
||
|
# 'specific_history' => showing only one history
|
||
|
# 'specific_table' => showing changes only for a particular table
|
||
|
# 'show_all_tags' => don't omit post tags that didn't change
|
||
|
$this->options = [
|
||
|
'show_all_tags' => $this->params()->show_all_tags == "1",
|
||
|
'specific_object' => (!empty($q['type']) and !empty($q['id'])),
|
||
|
'specific_history' => !empty($q['change']),
|
||
|
];
|
||
|
|
||
|
$this->options['show_name'] = false;
|
||
|
if ($this->type != "all") {
|
||
|
$cn = $inflector->classify($this->type);
|
||
|
try {
|
||
|
if (Versioned::is_versioned_class($cls) && class_exists($cn)) {
|
||
|
$obj = new $cn();
|
||
|
if (method_exists($obj, "pretty_name"))
|
||
|
$this->options['show_name'] = true;
|
||
|
}
|
||
|
} catch (Rails\Loader\Exception\ExceptionInterface $e) {
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$this->changes = $query->order("histories.id DESC")
|
||
|
->select('*')
|
||
|
->paginate($this->page_number(), 20);
|
||
|
|
||
|
# If we're searching for a specific change, force the display to the
|
||
|
# type of the change we found.
|
||
|
if (!empty($q['change']) && $this->changes->any()) {
|
||
|
$this->type = $inflector->pluralize($this->changes[0]->group_by_table);
|
||
|
}
|
||
|
|
||
|
$this->render(['action' => 'index']);
|
||
|
}
|
||
|
|
||
|
public function undo()
|
||
|
{
|
||
|
$ids = explode(',', $this->params()->id);
|
||
|
|
||
|
$this->changes = HistoryChange::emptyCollection();
|
||
|
foreach ($ids as $id)
|
||
|
$this->changes[] = HistoryChange::where("id = ?", $id)->first();
|
||
|
|
||
|
$histories = [];
|
||
|
$total_histories = 0;
|
||
|
foreach ($this->changes as $change) {
|
||
|
if (isset($histories[$change->history_id]))
|
||
|
continue;
|
||
|
|
||
|
$histories[$change->history_id] = true;
|
||
|
$total_histories += 1;
|
||
|
}
|
||
|
|
||
|
if ($total_histories > 1 && !$this->current_user->is_privileged_or_higher()) {
|
||
|
$this->respond_to_error("Only privileged users can undo more than one change at once", ['status' => 403]);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$errors = [];
|
||
|
History::undo($this->changes, $this->current_user, $this->params()->redo == "1", $errors);
|
||
|
|
||
|
$error_texts = [];
|
||
|
$successful = 0;
|
||
|
$failed = 0;
|
||
|
foreach ($this->changes as $change) {
|
||
|
$objectHash = spl_object_hash($change);
|
||
|
if (empty($errors[$objectHash])) {
|
||
|
$successful += 1;
|
||
|
continue;
|
||
|
}
|
||
|
$failed += 1;
|
||
|
|
||
|
switch ($errors[$objectHash]) {
|
||
|
case 'denied':
|
||
|
$error_texts[] = "Some changes were not made because you do not have access to make them.";
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
$error_texts = array_unique($error_texts);
|
||
|
|
||
|
$this->respond_to_success("Changes made.", ['action' => "index"], ['api' => ['successful' => $successful, 'failed' => $failed, 'errors' => $error_texts]]);
|
||
|
}
|
||
|
}
|