diff --git a/app/models/History.php b/app/models/History.php index c5c2c3d..6c99246 100755 --- a/app/models/History.php +++ b/app/models/History.php @@ -1,215 +1,215 @@ -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) { - # 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']; - - $object->runCallbacks('undo', function() use ($node, $redo_change, $object) { - $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->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'); }] - ] - ]; - } -} +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) { + # 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']; + + $object->runCallbacks('undo', function() use ($node, $redo_change, $object) { + $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->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'); }] + ] + ]; + } +}