protected function mergePHIDOrEdgeTransactions(PhabricatorApplicationTransaction $u, PhabricatorApplicationTransaction $v) { $result = $u->getNewValue(); foreach ($v->getNewValue() as $key => $value) { if ($u->getTransactionType() == PhabricatorTransactions::TYPE_EDGE) { if (empty($result[$key])) { $result[$key] = $value; } else { // We're merging two lists of edge adds, sets, or removes. Merge // them by merging individual PHIDs within them. $merged = $result[$key]; foreach ($value as $dst => $v_spec) { if (empty($merged[$dst])) { $merged[$dst] = $v_spec; } else { // Two transactions are trying to perform the same operation on // the same edge. Normalize the edge data and then merge it. This // allows transactions to specify how data merges execute in a // precise way. $u_spec = $merged[$dst]; if (!is_array($u_spec)) { $u_spec = array('dst' => $u_spec); } if (!is_array($v_spec)) { $v_spec = array('dst' => $v_spec); } $ux_data = idx($u_spec, 'data', array()); $vx_data = idx($v_spec, 'data', array()); $merged_data = $this->mergeEdgeData($u->getMetadataValue('edge:type'), $ux_data, $vx_data); $u_spec['data'] = $merged_data; $merged[$dst] = $u_spec; } } $result[$key] = $merged; } } else { $result[$key] = array_merge($value, idx($result, $key, array())); } } $u->setNewValue($result); // When combining an "ignore" transaction with a normal transaction, make // sure we don't propagate the "ignore" flag. if (!$v->getIgnoreOnNoEffect()) { $u->setIgnoreOnNoEffect(false); } return $u; }
protected function getCustomTransactionNewValue(PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PonderQuestionTransaction::TYPE_TITLE: case PonderQuestionTransaction::TYPE_CONTENT: case PonderQuestionTransaction::TYPE_STATUS: return $xaction->getNewValue(); case PonderQuestionTransaction::TYPE_ANSWERS: $raw_new_value = $xaction->getNewValue(); $new_value = array(); foreach ($raw_new_value as $key => $answers) { $phids = array(); foreach ($answers as $answer) { $obj = idx($answer, 'answer'); if (!$answer) { continue; } $phids[] = $obj->getPHID(); } $new_value[$key] = $phids; } $xaction->setNewValue($new_value); return $this->getPHIDTransactionNewValue($xaction); } }
protected function getCustomTransactionNewValue(PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PholioTransactionType::TYPE_NAME: case PholioTransactionType::TYPE_DESCRIPTION: case PholioTransactionType::TYPE_STATUS: case PholioTransactionType::TYPE_IMAGE_NAME: case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: case PholioTransactionType::TYPE_IMAGE_SEQUENCE: return $xaction->getNewValue(); case PholioTransactionType::TYPE_IMAGE_REPLACE: $raw = $xaction->getNewValue(); return $raw->getPHID(); case PholioTransactionType::TYPE_IMAGE_FILE: $raw_new_value = $xaction->getNewValue(); $new_value = array(); foreach ($raw_new_value as $key => $images) { $new_value[$key] = mpull($images, 'getPHID'); } $xaction->setNewValue($new_value); return $this->getPHIDTransactionNewValue($xaction); } }
private function buildMoveTransaction(PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { $new = $xaction->getNewValue(); if (!is_array($new)) { $this->validateColumnPHID($new); $new = array($new); } $nearby_phids = array(); foreach ($new as $key => $value) { if (!is_array($value)) { $this->validateColumnPHID($value); $value = array('columnPHID' => $value); } PhutilTypeSpec::checkMap($value, array('columnPHID' => 'string', 'beforePHID' => 'optional string', 'afterPHID' => 'optional string')); $new[$key] = $value; if (!empty($value['beforePHID'])) { $nearby_phids[] = $value['beforePHID']; } if (!empty($value['afterPHID'])) { $nearby_phids[] = $value['afterPHID']; } } if ($nearby_phids) { $nearby_objects = id(new PhabricatorObjectQuery())->setViewer($this->getActor())->withPHIDs($nearby_phids)->execute(); $nearby_objects = mpull($nearby_objects, null, 'getPHID'); } else { $nearby_objects = array(); } $column_phids = ipull($new, 'columnPHID'); if ($column_phids) { $columns = id(new PhabricatorProjectColumnQuery())->setViewer($this->getActor())->withPHIDs($column_phids)->execute(); $columns = mpull($columns, null, 'getPHID'); } else { $columns = array(); } $board_phids = mpull($columns, 'getProjectPHID'); $object_phid = $object->getPHID(); $object_phids = $nearby_phids; // Note that we may not have an object PHID if we're creating a new // object. if ($object_phid) { $object_phids[] = $object_phid; } if ($object_phids) { $layout_engine = id(new PhabricatorBoardLayoutEngine())->setViewer($this->getActor())->setBoardPHIDs($board_phids)->setObjectPHIDs($object_phids)->setFetchAllBoards(true)->executeLayout(); } foreach ($new as $key => $spec) { $column_phid = $spec['columnPHID']; $column = idx($columns, $column_phid); if (!$column) { throw new Exception(pht('Column move transaction specifies column PHID "%s", but there ' . 'is no corresponding column with this PHID.', $column_phid)); } $board_phid = $column->getProjectPHID(); $nearby = array(); if (!empty($spec['beforePHID'])) { $nearby['beforePHID'] = $spec['beforePHID']; } if (!empty($spec['afterPHID'])) { $nearby['afterPHID'] = $spec['afterPHID']; } if (count($nearby) > 1) { throw new Exception(pht('Column move transaction moves object to multiple positions. ' . 'Specify only "beforePHID" or "afterPHID", not both.')); } foreach ($nearby as $where => $nearby_phid) { if (empty($nearby_objects[$nearby_phid])) { throw new Exception(pht('Column move transaction specifies object "%s" as "%s", but ' . 'there is no corresponding object with this PHID.', $object_phid, $where)); } $nearby_columns = $layout_engine->getObjectColumns($board_phid, $nearby_phid); $nearby_columns = mpull($nearby_columns, null, 'getPHID'); if (empty($nearby_columns[$column_phid])) { throw new Exception(pht('Column move transaction specifies object "%s" as "%s" in ' . 'column "%s", but this object is not in that column!', $nearby_phid, $where, $column_phid)); } } if ($object_phid) { $old_columns = $layout_engine->getObjectColumns($board_phid, $object_phid); $old_column_phids = mpull($old_columns, 'getPHID'); } else { $old_column_phids = array(); } $spec += array('boardPHID' => $board_phid, 'fromColumnPHIDs' => $old_column_phids); // Check if the object is already in this column, and isn't being moved. // We can just drop this column change if it has no effect. $from_map = array_fuse($spec['fromColumnPHIDs']); $already_here = isset($from_map[$column_phid]); $is_reordering = (bool) $nearby; if ($already_here && !$is_reordering) { unset($new[$key]); } else { $new[$key] = $spec; } } $new = array_values($new); $xaction->setNewValue($new); $more = array(); // If we're moving the object into a column and it does not already belong // in the column, add the appropriate board. For normal columns, this // is the board PHID. For proxy columns, it is the proxy PHID, unless the // object is already a member of some descendant of the proxy PHID. // The major case where this can happen is moves via the API, but it also // happens when a user drags a task from the "Backlog" to a milestone // column. if ($object_phid) { $current_phids = PhabricatorEdgeQuery::loadDestinationPHIDs($object_phid, PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); $current_phids = array_fuse($current_phids); } else { $current_phids = array(); } $add_boards = array(); foreach ($new as $move) { $column_phid = $move['columnPHID']; $board_phid = $move['boardPHID']; $column = $columns[$column_phid]; $proxy_phid = $column->getProxyPHID(); // If this is a normal column, add the board if the object isn't already // associated. if (!$proxy_phid) { if (!isset($current_phids[$board_phid])) { $add_boards[] = $board_phid; } continue; } // If this is a proxy column but the object is already associated with // the proxy board, we don't need to do anything. if (isset($current_phids[$proxy_phid])) { continue; } // If this a proxy column and the object is already associated with some // descendant of the proxy board, we also don't need to do anything. $descendants = id(new PhabricatorProjectQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withAncestorProjectPHIDs(array($proxy_phid))->execute(); $found_descendant = false; foreach ($descendants as $descendant) { if (isset($current_phids[$descendant->getPHID()])) { $found_descendant = true; break; } } if ($found_descendant) { continue; } // Otherwise, we're moving the object to a proxy column which it is not // a member of yet, so add an association to the column's proxy board. $add_boards[] = $proxy_phid; } if ($add_boards) { $more[] = id(new ManiphestTransaction())->setTransactionType(PhabricatorTransactions::TYPE_EDGE)->setMetadataValue('edge:type', PhabricatorProjectObjectHasProjectEdgeType::EDGECONST)->setIgnoreOnNoEffect(true)->setNewValue(array('+' => array_fuse($add_boards))); } return $more; }