private function updateWorkboardColumns($object, $const, $old, $new) { // If an object is removed from a project, remove it from any proxy // columns for that project. This allows a task which is moved up from a // milestone to the parent to move back into the "Backlog" column on the // parent workboard. if ($const != PhabricatorProjectObjectHasProjectEdgeType::EDGECONST) { return; } // TODO: This should likely be some future WorkboardInterface. $appears_on_workboards = $object instanceof ManiphestTask; if (!$appears_on_workboards) { return; } $removed_phids = array_keys(array_diff_key($old, $new)); if (!$removed_phids) { return; } // Find any proxy columns for the removed projects. $proxy_columns = id(new PhabricatorProjectColumnQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withProxyPHIDs($removed_phids)->execute(); if (!$proxy_columns) { return array(); } $proxy_phids = mpull($proxy_columns, 'getPHID'); $position_table = new PhabricatorProjectColumnPosition(); $conn_w = $position_table->establishConnection('w'); queryfx($conn_w, 'DELETE FROM %T WHERE objectPHID = %s AND columnPHID IN (%Ls)', $position_table->getTableName(), $object->getPHID(), $proxy_phids); }
protected function applyCustomExternalTransaction(PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case ManiphestTransaction::TYPE_PROJECT_COLUMN: $board_phid = idx($xaction->getNewValue(), 'projectPHID'); if (!$board_phid) { throw new Exception(pht("Expected '%s' in column transaction.", 'projectPHID')); } $old_phids = idx($xaction->getOldValue(), 'columnPHIDs', array()); $new_phids = idx($xaction->getNewValue(), 'columnPHIDs', array()); if (count($new_phids) !== 1) { throw new Exception(pht("Expected exactly one '%s' in column transaction.", 'columnPHIDs')); } $columns = id(new PhabricatorProjectColumnQuery())->setViewer($this->requireActor())->withPHIDs($new_phids)->execute(); $columns = mpull($columns, null, 'getPHID'); $positions = id(new PhabricatorProjectColumnPositionQuery())->setViewer($this->requireActor())->withObjectPHIDs(array($object->getPHID()))->withBoardPHIDs(array($board_phid))->execute(); $before_phid = idx($xaction->getNewValue(), 'beforePHID'); $after_phid = idx($xaction->getNewValue(), 'afterPHID'); if (!$before_phid && !$after_phid && $old_phids == $new_phids) { // If we are not moving the object between columns and also not // reordering the position, this is a move on some other order // (like priority). We can leave the positions untouched and just // bail, there's no work to be done. return; } // Otherwise, we're either moving between columns or adjusting the // object's position in the "natural" ordering, so we do need to update // some rows. // Remove all existing column positions on the board. foreach ($positions as $position) { $position->delete(); } // Add the new column positions. foreach ($new_phids as $phid) { $column = idx($columns, $phid); if (!$column) { throw new Exception(pht('No such column "%s" exists!', $phid)); } // Load the other object positions in the column. Note that we must // skip implicit column creation to avoid generating a new position // if the target column is a backlog column. $other_positions = id(new PhabricatorProjectColumnPositionQuery())->setViewer($this->requireActor())->withColumns(array($column))->withBoardPHIDs(array($board_phid))->setSkipImplicitCreate(true)->execute(); $other_positions = msort($other_positions, 'getOrderingKey'); // Set up the new position object. We're going to figure out the // right sequence number and then persist this object with that // sequence number. $new_position = id(new PhabricatorProjectColumnPosition())->setBoardPHID($board_phid)->setColumnPHID($column->getPHID())->setObjectPHID($object->getPHID()); $updates = array(); $sequence = 0; // If we're just dropping this into the column without any specific // position information, put it at the top. if (!$before_phid && !$after_phid) { $new_position->setSequence($sequence)->save(); $sequence++; } foreach ($other_positions as $position) { $object_phid = $position->getObjectPHID(); // If this is the object we're moving before and we haven't // saved yet, insert here. if ($before_phid == $object_phid && !$new_position->getID()) { $new_position->setSequence($sequence)->save(); $sequence++; } // This object goes here in the sequence; we might need to update // the row. if ($sequence != $position->getSequence()) { $updates[$position->getID()] = $sequence; } $sequence++; // If this is the object we're moving after and we haven't saved // yet, insert here. if ($after_phid == $object_phid && !$new_position->getID()) { $new_position->setSequence($sequence)->save(); $sequence++; } } // We should have found a place to put it. if (!$new_position->getID()) { throw new Exception(pht('Unable to find a place to insert object on column!')); } // If we changed other objects' column positions, bulk reorder them. if ($updates) { $position = new PhabricatorProjectColumnPosition(); $conn_w = $position->establishConnection('w'); $pairs = array(); foreach ($updates as $id => $sequence) { // This is ugly because MySQL gets upset with us if it is // configured strictly and we attempt inserts which can't work. // We'll never actually do these inserts since they'll always // collide (triggering the ON DUPLICATE KEY logic), so we just // provide dummy values in order to get there. $pairs[] = qsprintf($conn_w, '(%d, %d, "", "", "")', $id, $sequence); } queryfx($conn_w, 'INSERT INTO %T (id, sequence, boardPHID, columnPHID, objectPHID) VALUES %Q ON DUPLICATE KEY UPDATE sequence = VALUES(sequence)', $position->getTableName(), implode(', ', $pairs)); } } break; default: break; } }
public function applyPositionUpdates() { foreach ($this->remQueue as $position) { if ($position->getID()) { $position->delete(); } } $this->remQueue = array(); $adds = array(); $updates = array(); foreach ($this->addQueue as $position) { $id = $position->getID(); if ($id) { $updates[$id] = $position; } else { $adds[] = $position; } } $this->addQueue = array(); $table = new PhabricatorProjectColumnPosition(); $conn_w = $table->establishConnection('w'); $pairs = array(); foreach ($updates as $id => $position) { // This is ugly because MySQL gets upset with us if it is configured // strictly and we attempt inserts which can't work. We'll never actually // do these inserts since they'll always collide (triggering the ON // DUPLICATE KEY logic), so we just provide dummy values in order to get // there. $pairs[] = qsprintf($conn_w, '(%d, %d, "", "", "")', $id, $position->getSequence()); } if ($pairs) { queryfx($conn_w, 'INSERT INTO %T (id, sequence, boardPHID, columnPHID, objectPHID) VALUES %Q ON DUPLICATE KEY UPDATE sequence = VALUES(sequence)', $table->getTableName(), implode(', ', $pairs)); } foreach ($adds as $position) { $position->save(); } return $this; }