diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php --- a/src/applications/project/storage/PhabricatorProject.php +++ b/src/applications/project/storage/PhabricatorProject.php @@ -748,9 +748,75 @@ $slug->delete(); } + // Destroy my milestones because they cannot live without me. + // Do not use PhabricatorProjectQuery to avoid a circular dependency, + // and, to do not have a load silent fail, since these milestones do not + // have their parent anymore. + $milestones = id(new self()) + ->loadAllWhere('parentProjectPHID = %s AND milestoneNumber IS NOT NULL', + $this->getPHID()); + foreach ($milestones as $milestone) { + $milestone->attachParentProject($this); + $engine->destroyObject($milestone); + } + + // Update my children to eventually fix holes in the tree. + $this->onDestroyTouchChildren(true); + + // After the tree is fixed, update my parent hasSubProjects field. + if ($this->getParentProject()) { + id(new PhabricatorProjectsMembershipIndexEngineExtension()) + ->rematerialize($this->getParentProject()); + } + $this->saveTransaction(); } + /** + * On destroy, eventually bubble up my direct children, to take my place. + * Refresh all remaining children, to consolidate depth, path key, etc. + */ + private function onDestroyTouchChildren($close_my_hole) { + // Micro-optimization. + if (!$this->supportsSubprojects() && !$this->supportsMilestones()) { + return; + } + + // Get direct sub-projects and milestones and their new desired parent. + // We must skip my direct milestones since they are under removal. + // Do not use PhabricatorProjectQuery to avoid a circular dependency. + $query_children = new self(); + if ($close_my_hole) { + $desired_parent = $this->getParentProject(); + $children = $query_children->loadAllWhere( + 'parentProjectPHID = %s AND milestoneNumber IS NULL', + $this->getPHID()); + } else { + $desired_parent = $this; + $children = $query_children->loadAllWhere( + 'parentProjectPHID = %s', + $this->getPHID()); + } + + // The desired parent PHID for my children may become NULL, + // when we are closing my hole but my parent it's a root-project. + $desired_parent_phid = null; + if ($desired_parent) { + $desired_parent_phid = $desired_parent->getPHID(); + } + + // Eventually bubble up my direct children. Update the others. + foreach ($children as $child) { + $child->attachParentProject($desired_parent); + $child->setParentProjectPHID($desired_parent_phid); + $child->setProjectPathKey(null); // Force a new path key and depth. + $child->save(); + + // Descend the tree. + $child->onDestroyTouchChildren(false); + } + } + /* -( PhabricatorFulltextInterface )--------------------------------------- */