aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/files_sharing/tests/external/manager.php151
-rw-r--r--apps/files_sharing/tests/external/managertest.php70
2 files changed, 67 insertions, 154 deletions
diff --git a/apps/files_sharing/tests/external/manager.php b/apps/files_sharing/tests/external/manager.php
deleted file mode 100644
index 3f8ac951ea6..00000000000
--- a/apps/files_sharing/tests/external/manager.php
+++ /dev/null
@@ -1,151 +0,0 @@
-<?php
-/**
- * @author Robin Appelman <icewind@owncloud.com>
- *
- * @copyright Copyright (c) 2015, ownCloud, Inc.
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
- */
-namespace OCA\Files_sharing\Tests\External;
-
-use OC\Files\Storage\StorageFactory;
-use OCA\Files_Sharing\Tests\TestCase;
-
-class Manager extends TestCase {
- private $uid;
-
- /**
- * @var \OC\Files\Mount\Manager
- */
- private $mountManager;
-
- /**
- * @var \OCA\Files_Sharing\External\Manager
- */
- private $instance;
-
- public function setUp() {
- parent::setUp();
-
- $this->uid = $this->getUniqueID('user');
-
- $this->mountManager = new \OC\Files\Mount\Manager();
- $this->instance = new \OCA\Files_Sharing\External\Manager(
- \OC::$server->getDatabaseConnection(),
- $this->mountManager,
- new StorageFactory(),
- $this->getMockBuilder('\OC\HTTPHelper')->disableOriginalConstructor()->getMock(),
- $this->uid
- );
- }
-
- public function tearDown() {
- $this->instance->removeUserShares($this->uid);
- parent::tearDown();
- }
-
- private function getFullPath($path) {
- return '/' . $this->uid . '/files' . $path;
- }
-
- private function assertMount($mountPoint) {
- $mountPoint = rtrim($mountPoint, '/');
- $mount = $this->mountManager->find($this->getFullPath($mountPoint));
- $this->assertInstanceOf('\OCP\Files\Mount\IMountPoint', $mount);
- $this->assertEquals($this->getFullPath($mountPoint), rtrim($mount->getMountPoint(), '/'));
- $storage = $mount->getStorage();
- $this->assertInstanceOf('\OCA\Files_Sharing\External\Storage', $storage);
- }
-
- private function assertNotMount($mountPoint) {
- $mountPoint = rtrim($mountPoint, '/');
- $mount = $this->mountManager->find($this->getFullPath($mountPoint));
- if ($mount) {
- $this->assertInstanceOf('\OCP\Files\Mount\IMountPoint', $mount);
- $this->assertNotEquals($this->getFullPath($mountPoint), rtrim($mount->getMountPoint(), '/'));
- } else {
- $this->assertNull($mount);
- }
- }
-
- public function testAddBasic() {
- $this->instance->addShare('http://example.com', 'foo', 'bar', 'example', 'me', true);
- \Test_Helper::invokePrivate($this->instance, 'setupMounts');
- $this->assertMount('/example');
- }
-
- public function testAddBasicEmptyPassword() {
- $this->instance->addShare('http://example.com', 'foo', '', 'example', 'me', true);
- \Test_Helper::invokePrivate($this->instance, 'setupMounts');
- $this->assertMount('/example');
- }
-
- public function testAddNotAcceptedShare() {
- $this->instance->addShare('http://example.com', 'foo', 'bar', 'example', 'me', false);
- \Test_Helper::invokePrivate($this->instance, 'setupMounts');
- $this->assertNotMount('/example');
- }
-
- public function testAcceptMount() {
- $this->instance->addShare('http://example.com', 'foo', 'bar', 'example', 'me', false);
- $open = $this->instance->getOpenShares();
- $this->assertCount(1, $open);
- $this->instance->acceptShare($open[0]['id']);
- $this->assertEquals([], $this->instance->getOpenShares());
- \Test_Helper::invokePrivate($this->instance, 'setupMounts');
- $this->assertMount('/example');
- }
-
- public function testDeclineMount() {
- $this->instance->addShare('http://example.com', 'foo', 'bar', 'example', 'me', false);
- $open = $this->instance->getOpenShares();
- $this->assertCount(1, $open);
- $this->instance->declineShare($open[0]['id']);
- $this->assertEquals([], $this->instance->getOpenShares());
- \Test_Helper::invokePrivate($this->instance, 'setupMounts');
- $this->assertNotMount('/example');
- }
-
- public function testSetMountPoint() {
- $this->instance->addShare('http://example.com', 'foo', 'bar', 'example', 'me', true);
- \Test_Helper::invokePrivate($this->instance, 'setupMounts');
- $this->assertMount('/example');
- $this->instance->setMountPoint($this->getFullPath('/example'), $this->getFullPath('/renamed'));
- $this->mountManager->clear();
- \Test_Helper::invokePrivate($this->instance, 'setupMounts');
- $this->assertMount('/renamed');
- $this->assertNotMount('/example');
- }
-
- public function testRemoveShare() {
- $this->instance->addShare('http://example.com', 'foo', 'bar', 'example', 'me', true);
- \Test_Helper::invokePrivate($this->instance, 'setupMounts');
- $this->assertMount('/example');
- $this->instance->removeShare($this->getFullPath('/example'));
- $this->mountManager->clear();
- \Test_Helper::invokePrivate($this->instance, 'setupMounts');
- $this->assertNotMount('/example');
- }
-
- public function testRemoveShareForUser() {
- $this->instance->addShare('http://example.com', 'foo', 'bar', 'example', 'me', true);
- \Test_Helper::invokePrivate($this->instance, 'setupMounts');
- $this->assertMount('/example');
- $this->instance->removeUserShares($this->uid);
- $this->mountManager->clear();
- \Test_Helper::invokePrivate($this->instance, 'setupMounts');
- $this->assertNotMount('/example');
- }
-}
diff --git a/apps/files_sharing/tests/external/managertest.php b/apps/files_sharing/tests/external/managertest.php
index 0a8c3e5f6b4..4158b1bf445 100644
--- a/apps/files_sharing/tests/external/managertest.php
+++ b/apps/files_sharing/tests/external/managertest.php
@@ -22,6 +22,7 @@
namespace OCA\Files_Sharing\Tests\External;
+use OC\Files\Storage\StorageFactory;
use OCA\Files_Sharing\Tests\TestCase;
class ManagerTest extends TestCase {
@@ -29,16 +30,20 @@ class ManagerTest extends TestCase {
/** @var \OCA\Files_Sharing\External\Manager **/
private $manager;
+ /** @var \OC\Files\Mount\Manager */
+ private $mountManager;
+
private $uid;
protected function setUp() {
parent::setUp();
$this->uid = $this->getUniqueID('user');
+ $this->mountManager = new \OC\Files\Mount\Manager();
$this->manager = new \OCA\Files_Sharing\External\Manager(
\OC::$server->getDatabaseConnection(),
- $this->getMockBuilder('\OC\Files\Mount\Manager')->disableOriginalConstructor()->getMock(),
- $this->getMockBuilder('\OCP\Files\Storage\IStorageFactory')->disableOriginalConstructor()->getMock(),
+ $this->mountManager,
+ new StorageFactory(),
$this->getMockBuilder('\OC\HTTPHelper')->disableOriginalConstructor()->getMock(),
$this->uid
);
@@ -46,7 +51,7 @@ class ManagerTest extends TestCase {
public function testAddShare() {
$shareData1 = [
- 'remote' => 'localhost',
+ 'remote' => 'http://localhost',
'token' => 'token1',
'password' => '',
'name' => '/SharedFolder',
@@ -65,6 +70,10 @@ class ManagerTest extends TestCase {
$this->assertCount(1, $openShares);
$this->assertExternalShareEntry($shareData1, $openShares[0], 1, '{{TemporaryMountPointName#' . $shareData1['name'] . '}}');
+ \Test_Helper::invokePrivate($this->manager, 'setupMounts');
+ $this->assertNotMount('SharedFolder');
+ $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}');
+
// Add a second share for "user" with the same name
$this->assertSame(null, call_user_func_array([$this->manager, 'addShare'], $shareData2));
$openShares = $this->manager->getOpenShares();
@@ -73,6 +82,11 @@ class ManagerTest extends TestCase {
// New share falls back to "-1" appendix, because the name is already taken
$this->assertExternalShareEntry($shareData2, $openShares[1], 2, '{{TemporaryMountPointName#' . $shareData2['name'] . '}}-1');
+ \Test_Helper::invokePrivate($this->manager, 'setupMounts');
+ $this->assertNotMount('SharedFolder');
+ $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}');
+ $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1');
+
// Accept the first share
$this->manager->acceptShare($openShares[0]['id']);
@@ -86,6 +100,11 @@ class ManagerTest extends TestCase {
$this->assertCount(1, $openShares);
$this->assertExternalShareEntry($shareData2, $openShares[0], 2, '{{TemporaryMountPointName#' . $shareData2['name'] . '}}-1');
+ \Test_Helper::invokePrivate($this->manager, 'setupMounts');
+ $this->assertMount($shareData1['name']);
+ $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}');
+ $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1');
+
// Add another share for "user" with the same name
$this->assertSame(null, call_user_func_array([$this->manager, 'addShare'], $shareData3));
$openShares = $this->manager->getOpenShares();
@@ -94,9 +113,19 @@ class ManagerTest extends TestCase {
// New share falls back to the original name (no "-\d", because the name is not taken)
$this->assertExternalShareEntry($shareData3, $openShares[1], 3, '{{TemporaryMountPointName#' . $shareData3['name'] . '}}');
+ \Test_Helper::invokePrivate($this->manager, 'setupMounts');
+ $this->assertMount($shareData1['name']);
+ $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}');
+ $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1');
+
// Decline the third share
$this->manager->declineShare($openShares[1]['id']);
+ \Test_Helper::invokePrivate($this->manager, 'setupMounts');
+ $this->assertMount($shareData1['name']);
+ $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}');
+ $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1');
+
// Check remaining shares - Accepted
$acceptedShares = \Test_Helper::invokePrivate($this->manager, 'getShares', [true]);
$this->assertCount(1, $acceptedShares);
@@ -107,8 +136,19 @@ class ManagerTest extends TestCase {
$this->assertCount(1, $openShares);
$this->assertExternalShareEntry($shareData2, $openShares[0], 2, '{{TemporaryMountPointName#' . $shareData2['name'] . '}}-1');
+ \Test_Helper::invokePrivate($this->manager, 'setupMounts');
+ $this->assertMount($shareData1['name']);
+ $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}');
+ $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1');
+
$this->manager->removeUserShares($this->uid);
$this->assertEmpty(\Test_Helper::invokePrivate($this->manager, 'getShares', [null]), 'Asserting all shares for the user have been deleted');
+
+ $this->mountManager->clear();
+ \Test_Helper::invokePrivate($this->manager, 'setupMounts');
+ $this->assertNotMount($shareData1['name']);
+ $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}');
+ $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1');
}
/**
@@ -125,6 +165,30 @@ class ManagerTest extends TestCase {
$this->assertEquals($expected['accepted'], (int) $actual['accepted'], 'Asserting accept of a share #' . $share);
$this->assertEquals($expected['user'], $actual['user'], 'Asserting user of a share #' . $share);
$this->assertEquals($mountPoint, $actual['mountpoint'], 'Asserting mountpoint of a share #' . $share);
+ }
+
+ private function assertMount($mountPoint) {
+ $mountPoint = rtrim($mountPoint, '/');
+ $mount = $this->mountManager->find($this->getFullPath($mountPoint));
+ $this->assertInstanceOf('\OCA\Files_Sharing\External\Mount', $mount);
+ $this->assertInstanceOf('\OCP\Files\Mount\IMountPoint', $mount);
+ $this->assertEquals($this->getFullPath($mountPoint), rtrim($mount->getMountPoint(), '/'));
+ $storage = $mount->getStorage();
+ $this->assertInstanceOf('\OCA\Files_Sharing\External\Storage', $storage);
+ }
+
+ private function assertNotMount($mountPoint) {
+ $mountPoint = rtrim($mountPoint, '/');
+ $mount = $this->mountManager->find($this->getFullPath($mountPoint));
+ if ($mount) {
+ $this->assertInstanceOf('\OCP\Files\Mount\IMountPoint', $mount);
+ $this->assertNotEquals($this->getFullPath($mountPoint), rtrim($mount->getMountPoint(), '/'));
+ } else {
+ $this->assertNull($mount);
+ }
+ }
+ private function getFullPath($path) {
+ return '/' . $this->uid . '/files' . $path;
}
}
n class="p">{|r| r["project_id"] == project.id.to_s} end # Query generator for selecting groups of issue counts for a project # based on specific criteria # # Options # * project - Project to search in. # * with_subprojects - Includes subprojects issues if set to true. # * association - Symbol. Association for grouping. def self.count_and_group_by(options) assoc = reflect_on_association(options[:association]) select_field = assoc.foreign_key Issue. visible(User.current, :project => options[:project], :with_subprojects => options[:with_subprojects]). joins(:status). group(:status_id, :is_closed, select_field). count. map do |columns, total| status_id, is_closed, field_value = columns is_closed = ['t', 'true', '1'].include?(is_closed.to_s) { "status_id" => status_id.to_s, "closed" => is_closed, select_field => field_value.to_s, "total" => total.to_s } end end # Returns a scope of projects that user can assign the subtask def allowed_target_projects_for_subtask(user=User.current) if parent_issue_id.present? scope = filter_projects_scope(Setting.cross_project_subtasks) end self.class.allowed_target_projects(user, project, scope) end # Returns a scope of projects that user can assign the issue to def allowed_target_projects(user=User.current, scope=nil) current_project = new_record? ? nil : project if scope scope = filter_projects_scope(scope) end self.class.allowed_target_projects(user, current_project, scope) end # Returns a scope of projects that user can assign issues to # If current_project is given, it will be included in the scope def self.allowed_target_projects(user=User.current, current_project=nil, scope=nil) condition = Project.allowed_to_condition(user, :add_issues) if current_project condition = ["(#{condition}) OR #{Project.table_name}.id = ?", current_project.id] end if scope.nil? scope = Project end scope.where(condition).having_trackers end # Returns a scope of trackers that user can assign the issue to def allowed_target_trackers(user=User.current) self.class.allowed_target_trackers(project, user, tracker_id_was) end # Returns a scope of trackers that user can assign project issues to def self.allowed_target_trackers(project, user=User.current, current_tracker=nil) if project scope = project.trackers.sorted unless user.admin? roles = user.roles_for_project(project).select {|r| r.has_permission?(:add_issues)} unless roles.any? {|r| r.permissions_all_trackers?(:add_issues)} tracker_ids = roles.map {|r| r.permissions_tracker_ids(:add_issues)}.flatten.uniq if current_tracker tracker_ids << current_tracker end scope = scope.where(:id => tracker_ids) end end scope else Tracker.none end end private def user_tracker_permission?(user, permission) if project && !project.active? perm = Redmine::AccessControl.permission(permission) return false unless perm && perm.read? end if user.admin? true else roles = user.roles_for_project(project).select {|r| r.has_permission?(permission)} roles.any? do |r| r.permissions_all_trackers?(permission) || r.permissions_tracker_ids?(permission, tracker_id) end end end def after_project_change # Update project_id on related time entries TimeEntry.where({:issue_id => id}).update_all(["project_id = ?", project_id]) # Delete issue relations unless Setting.cross_project_issue_relations? relations_from.clear relations_to.clear end # Move subtasks that were in the same project children.each do |child| next unless child.project_id == project_id_before_last_save # Change project and keep project child.send :project=, project, true unless child.save errors.add( :base, l(:error_move_of_child_not_possible, :child => "##{child.id}", :errors => child.errors.full_messages.join(", ")) ) raise ActiveRecord::Rollback end end end # Callback for after the creation of an issue by copy # * adds a "copied to" relation with the copied issue # * copies subtasks from the copied issue def after_create_from_copy return unless copy? && !@after_create_from_copy_handled if (@copied_from.project_id == project_id || Setting.cross_project_issue_relations?) && @copy_options[:link] != false if @current_journal @copied_from.init_journal(@current_journal.user) end relation = IssueRelation.new(:issue_from => @copied_from, :issue_to => self, :relation_type => IssueRelation::TYPE_COPIED_TO) unless relation.save if logger logger.error( "Could not create relation while copying ##{@copied_from.id} to ##{id} " \ "due to validation errors: #{relation.errors.full_messages.join(', ')}" ) end end end unless @copied_from.leaf? || @copy_options[:subtasks] == false copy_options = (@copy_options || {}).merge(:subtasks => false) copied_issue_ids = {@copied_from.id => self.id} @copied_from.reload.descendants.reorder("#{Issue.table_name}.lft").each do |child| # Do not copy self when copying an issue as a descendant of the copied issue next if child == self # Do not copy subtasks of issues that were not copied next unless copied_issue_ids[child.parent_id] # Do not copy subtasks that are not visible to avoid potential disclosure of private data unless child.visible? if logger logger.error( "Subtask ##{child.id} was not copied during ##{@copied_from.id} copy " \ "because it is not visible to the current user" ) end next end copy = Issue.new.copy_from(child, copy_options) if @current_journal copy.init_journal(@current_journal.user) end copy.author = author copy.project = project copy.parent_issue_id = copied_issue_ids[child.parent_id] unless child.fixed_version.present? && child.fixed_version.status == 'open' copy.fixed_version_id = nil end unless child.assigned_to_id.present? && child.assigned_to.status == User::STATUS_ACTIVE copy.assigned_to = nil end unless copy.save if logger logger.error( "Could not copy subtask ##{child.id} " \ "while copying ##{@copied_from.id} to ##{id} due to validation errors: " \ "#{copy.errors.full_messages.join(', ')}" ) end next end copied_issue_ids[child.id] = copy.id end end @after_create_from_copy_handled = true end def update_nested_set_attributes if saved_change_to_parent_id? update_nested_set_attributes_on_parent_change end remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue) end # Updates the nested set for when an existing issue is moved def update_nested_set_attributes_on_parent_change former_parent_id = parent_id_before_last_save # delete invalid relations of all descendants self_and_descendants.each do |issue| issue.relations.each do |relation| relation.destroy unless relation.valid? end end # update former parent recalculate_attributes_for(former_parent_id) if former_parent_id end def update_parent_attributes if parent_id recalculate_attributes_for(parent_id) association(:parent).reset end end def recalculate_attributes_for(issue_id) if issue_id && p = Issue.find_by_id(issue_id) if p.priority_derived? # priority = highest priority of open children # priority is left unchanged if all children are closed and there's no default priority defined if priority_position = p.children.open.joins(:priority).maximum("#{IssuePriority.table_name}.position") p.priority = IssuePriority.find_by_position(priority_position) elsif default_priority = IssuePriority.default p.priority = default_priority end end if p.dates_derived? # start/due dates = lowest/highest dates of children p.start_date = p.children.minimum(:start_date) p.due_date = p.children.maximum(:due_date) if p.start_date && p.due_date && p.due_date < p.start_date p.start_date, p.due_date = p.due_date, p.start_date end end if p.done_ratio_derived? # done ratio = average ratio of children weighted with their total estimated hours unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio children = p.children.to_a if children.any? child_with_total_estimated_hours = children.select {|c| c.total_estimated_hours.to_f > 0.0} if child_with_total_estimated_hours.any? average = Rational( child_with_total_estimated_hours.sum(&:total_estimated_hours).to_s, child_with_total_estimated_hours.count ) else average = Rational(1) end done = children.sum do |c| estimated = Rational(c.total_estimated_hours.to_f.to_s) estimated = average unless estimated > 0.0 ratio = c.closed? ? 100 : (c.done_ratio || 0) estimated * ratio end progress = Rational(done, average * children.count) p.done_ratio = progress.floor end end end # ancestors will be recursively updated p.save(:validate => false) end end # Singleton class method is public class << self # Update issues so their versions are not pointing to a # fixed_version that is not shared with the issue's project def update_versions(conditions=nil) # Only need to update issues with a fixed_version from # a different project and that is not systemwide shared Issue.joins(:project, :fixed_version). where("#{Issue.table_name}.fixed_version_id IS NOT NULL" + " AND #{Issue.table_name}.project_id <> #{::Version.table_name}.project_id" + " AND #{::Version.table_name}.sharing <> 'system'"). where(conditions).each do |issue| next if issue.project.nil? || issue.fixed_version.nil? unless issue.project.shared_versions.include?(issue.fixed_version) retried = false begin issue.init_journal(User.current) issue.fixed_version = nil issue.save rescue ActiveRecord::StaleObjectError raise if retried retried = true issue.reload retry end end end end end def delete_selected_attachments if deleted_attachment_ids.present? objects = attachments.where(:id => deleted_attachment_ids.map(&:to_i)) attachments.delete(objects) end end # Callback on file attachment def attachment_added(attachment) if current_journal && !attachment.new_record? && !copy? current_journal.journalize_attachment(attachment, :added) end end # Callback on attachment deletion def attachment_removed(attachment) if current_journal && !attachment.new_record? current_journal.journalize_attachment(attachment, :removed) current_journal.save end end # Called after a relation is added def relation_added(relation) if current_journal current_journal.journalize_relation(relation, :added) current_journal.save end end # Called after a relation is removed def relation_removed(relation) if current_journal current_journal.journalize_relation(relation, :removed) current_journal.save end end # Default assignment based on project or category def default_assign if assigned_to.nil? if category && category.assigned_to self.assigned_to = category.assigned_to elsif project && project.default_assigned_to self.assigned_to = project.default_assigned_to end end end # Updates start/due dates of following issues def reschedule_following_issues if saved_change_to_start_date? || saved_change_to_due_date? relations_from.each do |relation| relation.set_issue_to_dates(@current_journal) end end end # Closes duplicates if the issue is being closed def close_duplicates if Setting.close_duplicate_issues? && closing? duplicates.each do |duplicate| # Reload is needed in case the duplicate was updated by a previous duplicate duplicate.reload # Don't re-close it if it's already closed next if duplicate.closed? # Same user and notes if @current_journal duplicate.init_journal(@current_journal.user, @current_journal.notes) duplicate.private_notes = @current_journal.private_notes end duplicate.update_attribute :status, self.status end end end # Make sure updated_on is updated when adding a note and set updated_on now # so we can set closed_on with the same value on closing def force_updated_on_change if @current_journal || changed? self.updated_on = current_time_from_proper_timezone if new_record? self.created_on = updated_on end end end # Callback for setting closed_on when the issue is closed. # The closed_on attribute stores the time of the last closing # and is preserved when the issue is reopened. def update_closed_on if closing? self.closed_on = updated_on end end # Saves the changes in a Journal # Called after_save def create_journal if current_journal current_journal.save end end def create_parent_issue_journal return if persisted? && !saved_change_to_parent_id? return if destroyed? && @without_nested_set_update child_id = self.id old_parent_id, new_parent_id = if persisted? [parent_id_before_last_save, parent_id] elsif destroyed? [parent_id, nil] else [nil, parent_id] end if old_parent_id.present? Issue.transaction do if old_parent_issue = Issue.visible.lock.find_by_id(old_parent_id) old_parent_issue.init_journal(User.current) old_parent_issue.current_journal.__send__(:add_attribute_detail, 'child_id', child_id, nil) old_parent_issue.save end end end if new_parent_id.present? Issue.transaction do if new_parent_issue = Issue.visible.lock.find_by_id(new_parent_id) new_parent_issue.init_journal(User.current) new_parent_issue.current_journal.__send__(:add_attribute_detail, 'child_id', nil, child_id) new_parent_issue.save end end end end def add_auto_watcher if author&.active? && author&.allowed_to?(:add_issue_watchers, project) && author.pref.auto_watch_on?('issue_created') && self.watcher_user_ids.exclude?(author.id) self.set_watcher(author, true) end end def send_notification if notify? && Setting.notified_events.include?('issue_added') Mailer.deliver_issue_add(self) end end def clear_disabled_fields if tracker tracker.disabled_core_fields.each do |attribute| send :"#{attribute}=", nil end self.priority_id ||= IssuePriority.default&.id || IssuePriority.active.first.id self.done_ratio ||= 0 end end def filter_projects_scope(scope=nil) case scope when 'system' Project when 'tree' project.root.self_and_descendants when 'hierarchy' project.hierarchy when 'descendants' project.self_and_descendants when '' Project.where(:id => project.id) else Project end end def roles_for_workflow(user) roles = user.admin ? Role.all.to_a : user.roles_for_project(project) roles.select(&:consider_workflow?) end end