225 lines
8.2 KiB
PHP
225 lines
8.2 KiB
PHP
|
<?php
|
||
|
class History extends Rails\ActiveRecord\Base
|
||
|
{
|
||
|
public function aux()
|
||
|
{
|
||
|
if (!$this->aux_as_json) {
|
||
|
return [];
|
||
|
}
|
||
|
return json_decode($this->aux_as_json);
|
||
|
}
|
||
|
|
||
|
public function setAux($o)
|
||
|
{
|
||
|
if (!$o) {
|
||
|
$this->aux_as_json = null;
|
||
|
} else {
|
||
|
$this->aux_as_json = json_encode($o);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function group_by_table_class()
|
||
|
{
|
||
|
return Rails::services()->get('inflector')->classify($this->group_by_table);
|
||
|
}
|
||
|
|
||
|
public function get_group_by_controller()
|
||
|
{
|
||
|
$class_name = $this->group_by_table_class();
|
||
|
return $class_name::versioning()->get_versioning_group_by()['controller'];
|
||
|
}
|
||
|
|
||
|
public function get_group_by_action()
|
||
|
{
|
||
|
$class_name = $this->group_by_table_class();
|
||
|
return $class_name::versioning()->get_versioning_group_by()['action'];
|
||
|
}
|
||
|
|
||
|
public function group_by_obj()
|
||
|
{
|
||
|
$class_name = $this->group_by_table_class();
|
||
|
return $class_name::find($this->group_by_id);
|
||
|
}
|
||
|
|
||
|
public function user()
|
||
|
{
|
||
|
return User::find($this->user_id);
|
||
|
}
|
||
|
|
||
|
public function author()
|
||
|
{
|
||
|
return User::find($this->user_id)->name;
|
||
|
}
|
||
|
|
||
|
# Undo all changes in the array changes.
|
||
|
static public function undo($changes, $user, $redo_change = false, array &$errors)
|
||
|
{
|
||
|
# Save parent objects after child objects, so changes to the children are
|
||
|
# committed when we save the parents.
|
||
|
$objects = new ArrayObject();
|
||
|
|
||
|
foreach ($changes as $change) {
|
||
|
// # MI: If we're redoing, why would we need a previous?
|
||
|
// # Adding this conditional so redos won't need a previous.
|
||
|
// if (!$redo_change) {
|
||
|
# If we have no previous change, this was the first change to this property
|
||
|
# and we have no default, so this change can't be undone.
|
||
|
$previous_change = $change->previous;
|
||
|
|
||
|
if (!$previous_change && empty($change->options()['allow_reverting_to_default'])) {
|
||
|
continue;
|
||
|
}
|
||
|
// }
|
||
|
|
||
|
if (!$user->can_change($change->obj(), $change->column_name)) {
|
||
|
$errors[spl_object_hash($change)] = 'denied';
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
# Add this node and its parent objects to objects.
|
||
|
$node = self::cache_object_recurse($objects, $change->table_name, $change->remote_id, $change->obj());
|
||
|
if (!isset($node['changes'])) {
|
||
|
$node['changes'] = [];
|
||
|
}
|
||
|
$node['changes'][] = $change;
|
||
|
}
|
||
|
|
||
|
if (empty($objects['objects'])) {
|
||
|
return;
|
||
|
}
|
||
|
# objects contains one or more trees of objects. Flatten this to an ordered
|
||
|
# list, so we can always save child nodes before parent nodes.
|
||
|
$done = new ArrayObject();
|
||
|
$stack = new ArrayObject();
|
||
|
foreach ($objects['objects'] as $table_name => $rhs) {
|
||
|
foreach ($rhs as $id => $node) {
|
||
|
# Start adding from the node at the top of the tree.
|
||
|
while (!empty($node['parent'])) {
|
||
|
$node = $node['parent'];
|
||
|
}
|
||
|
self::stack_object_recurse($node, $stack, $done);
|
||
|
}
|
||
|
}
|
||
|
$stack = $stack->getArrayCopy();
|
||
|
|
||
|
foreach (array_reverse($stack) as $node) {
|
||
|
$object = $node['o'];
|
||
|
/**
|
||
|
* MI: Only Pool model sets the undo (:after) callback.
|
||
|
* Calling it manually at the end because runCallbacks doesn't behave like in Rails.
|
||
|
* TODO: fix callbacks in the framework and update this.
|
||
|
*/
|
||
|
// $object->runCallbacks('undo', function() {
|
||
|
$changes = !empty($node['changes']) ? $node['changes'] : [];
|
||
|
|
||
|
if ($changes) {
|
||
|
foreach ($changes as $change) {
|
||
|
if ($redo_change) {
|
||
|
$redo_func = $change->column_name . '_redo';
|
||
|
if (method_exists($object, $redo_func)) {
|
||
|
$object->$redo_func($change);
|
||
|
} else {
|
||
|
$object->{$change->column_name} = $change->value;
|
||
|
}
|
||
|
} else {
|
||
|
$undo_func = $change->column_name . '_undo';
|
||
|
if (method_exists($object, $undo_func)) {
|
||
|
$object->$undo_func($change);
|
||
|
} else {
|
||
|
if ($change->previous) {
|
||
|
$previous = $change->previous->value;
|
||
|
} else {
|
||
|
$previous = $change->options()['default']; # when :allow_reverting_to_default
|
||
|
}
|
||
|
|
||
|
$object->{$change->column_name} = $previous;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
$object->runCallbacks('after_undo');
|
||
|
// });
|
||
|
|
||
|
$object->save();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static public function generate_sql($options = [])
|
||
|
{
|
||
|
$sql = self::none();
|
||
|
if (isset($options['remote_id']))
|
||
|
$sql->where("histories.remote_id = ?", $options['remote_id']);
|
||
|
if (isset($options['remote_id']))
|
||
|
$sql->where("histories.user_id = ?", $options['user_id']);
|
||
|
|
||
|
if (isset($options['user_name'])) {
|
||
|
$sql->joins("JOIN users ON users.id = histories.user_id")
|
||
|
->where("users.name = ?", $options['user_name']);
|
||
|
}
|
||
|
return $sql;
|
||
|
}
|
||
|
|
||
|
# Find and return the node for table_name/id in objects. If the node doesn't
|
||
|
# exist, create it and point it at object.
|
||
|
static protected function cache_object($objects, $table_name, $id, $object)
|
||
|
{
|
||
|
if (empty($objects['objects']))
|
||
|
$objects['objects'] = [];
|
||
|
if (empty($objects['objects'][$table_name]))
|
||
|
$objects['objects'][$table_name] = [];
|
||
|
if (empty($objects['objects'][$table_name][$id]))
|
||
|
$objects['objects'][$table_name][$id] = new ArrayObject([
|
||
|
'o' => $object
|
||
|
]);
|
||
|
return $objects['objects'][$table_name][$id];
|
||
|
}
|
||
|
|
||
|
# Find and return the node for table_name/id in objects. Recursively create
|
||
|
# nodes for parent objects.
|
||
|
static protected function cache_object_recurse($objects, $table_name, $id, $object)
|
||
|
{
|
||
|
$node = self::cache_object($objects, $table_name, $id, $object);
|
||
|
|
||
|
# If this class has a master class, register the master object for update callbacks too.
|
||
|
$master = $object->versioned_master_object();
|
||
|
if ($master) {
|
||
|
$master_node = self::cache_object_recurse($objects, get_class($master), $master->id, $master);
|
||
|
|
||
|
if (empty($master_node['children']))
|
||
|
$master_node['children'] = [];
|
||
|
$master_node['children'][] = $node;
|
||
|
$node['parent'] = $master_node;
|
||
|
}
|
||
|
|
||
|
return $node;
|
||
|
}
|
||
|
|
||
|
static protected function stack_object_recurse($node, $stack, $done = [])
|
||
|
{
|
||
|
$nodeHash = spl_object_hash($node);
|
||
|
if (!empty($done[$nodeHash]))
|
||
|
return;
|
||
|
$done[$nodeHash] = true;
|
||
|
|
||
|
$stack[] = $node;
|
||
|
|
||
|
if (!empty($node['children'])) {
|
||
|
foreach ($node['children'] as $child) {
|
||
|
self::stack_object_recurse($child, $stack, $done);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected function associations()
|
||
|
{
|
||
|
return [
|
||
|
'belongs_to' => [
|
||
|
'user'
|
||
|
],
|
||
|
'has_many' => [
|
||
|
'history_changes' => [function() { $this->order('id'); }]
|
||
|
]
|
||
|
];
|
||
|
}
|
||
|
}
|