@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
at recaptime-dev/main 381 lines 10 kB view raw
1<?php 2 3final class PhabricatorProjectColumn 4 extends PhabricatorProjectDAO 5 implements 6 PhabricatorApplicationTransactionInterface, 7 PhabricatorPolicyInterface, 8 PhabricatorDestructibleInterface, 9 PhabricatorExtendedPolicyInterface, 10 PhabricatorConduitResultInterface { 11 12 const STATUS_ACTIVE = 0; 13 const STATUS_HIDDEN = 1; 14 15 protected $name; 16 protected $status; 17 protected $projectPHID; 18 protected $proxyPHID; 19 protected $sequence; 20 protected $properties = array(); 21 protected $triggerPHID; 22 23 private $project = self::ATTACHABLE; 24 private $proxy = self::ATTACHABLE; 25 private $trigger = self::ATTACHABLE; 26 27 public static function initializeNewColumn(PhabricatorUser $user) { 28 return id(new PhabricatorProjectColumn()) 29 ->setName('') 30 ->setStatus(self::STATUS_ACTIVE) 31 ->attachProxy(null); 32 } 33 34 protected function getConfiguration() { 35 return array( 36 self::CONFIG_AUX_PHID => true, 37 self::CONFIG_SERIALIZATION => array( 38 'properties' => self::SERIALIZATION_JSON, 39 ), 40 self::CONFIG_COLUMN_SCHEMA => array( 41 'name' => 'text255', 42 'status' => 'uint32', 43 'sequence' => 'uint32', 44 'proxyPHID' => 'phid?', 45 'triggerPHID' => 'phid?', 46 ), 47 self::CONFIG_KEY_SCHEMA => array( 48 'key_status' => array( 49 'columns' => array('projectPHID', 'status', 'sequence'), 50 ), 51 'key_sequence' => array( 52 'columns' => array('projectPHID', 'sequence'), 53 ), 54 'key_proxy' => array( 55 'columns' => array('projectPHID', 'proxyPHID'), 56 'unique' => true, 57 ), 58 'key_trigger' => array( 59 'columns' => array('triggerPHID'), 60 ), 61 ), 62 ) + parent::getConfiguration(); 63 } 64 65 public function generatePHID() { 66 return PhabricatorPHID::generateNewPHID( 67 PhabricatorProjectColumnPHIDType::TYPECONST); 68 } 69 70 public function attachProject(PhabricatorProject $project) { 71 $this->project = $project; 72 return $this; 73 } 74 75 public function getProject() { 76 return $this->assertAttached($this->project); 77 } 78 79 public function attachProxy($proxy) { 80 $this->proxy = $proxy; 81 return $this; 82 } 83 84 public function getProxy() { 85 return $this->assertAttached($this->proxy); 86 } 87 88 public function isDefaultColumn() { 89 return (bool)$this->getProperty('isDefault'); 90 } 91 92 public function isHidden() { 93 $proxy = $this->getProxy(); 94 if ($proxy) { 95 return $proxy->isArchived(); 96 } 97 98 return ($this->getStatus() == self::STATUS_HIDDEN); 99 } 100 101 public function getDisplayName() { 102 $proxy = $this->getProxy(); 103 if ($proxy) { 104 return $proxy->getProxyColumnName(); 105 } 106 107 $name = $this->getName(); 108 if (strlen($name)) { 109 return $name; 110 } 111 112 if ($this->isDefaultColumn()) { 113 return pht('Backlog'); 114 } 115 116 return pht('Unnamed Column'); 117 } 118 119 public function getDisplayType() { 120 if ($this->isDefaultColumn()) { 121 return pht('(Default)'); 122 } 123 if ($this->isHidden()) { 124 return pht('(Hidden)'); 125 } 126 127 return null; 128 } 129 130 public function getDisplayClass() { 131 $proxy = $this->getProxy(); 132 if ($proxy) { 133 return $proxy->getProxyColumnClass(); 134 } 135 136 return null; 137 } 138 139 public function getHeaderIcon() { 140 $proxy = $this->getProxy(); 141 if ($proxy) { 142 return $proxy->getProxyColumnIcon(); 143 } 144 145 if ($this->isHidden()) { 146 return 'fa-eye-slash'; 147 } 148 149 return null; 150 } 151 152 public function getProperty($key, $default = null) { 153 return idx($this->properties, $key, $default); 154 } 155 156 public function setProperty($key, $value) { 157 $this->properties[$key] = $value; 158 return $this; 159 } 160 161 public function getPointLimit() { 162 return $this->getProperty('pointLimit'); 163 } 164 165 public function setPointLimit($limit) { 166 $this->setProperty('pointLimit', $limit); 167 return $this; 168 } 169 170 public function getOrderingKey() { 171 $proxy = $this->getProxy(); 172 173 // Normal columns and subproject columns go first, in a user-controlled 174 // order. 175 176 // All the milestone columns go last, in their sequential order. 177 178 if (!$proxy || !$proxy->isMilestone()) { 179 $group = 'A'; 180 $sequence = $this->getSequence(); 181 } else { 182 $group = 'B'; 183 $sequence = $proxy->getMilestoneNumber(); 184 } 185 186 return sprintf('%s%012d', $group, $sequence); 187 } 188 189 public function attachTrigger(?PhabricatorProjectTrigger $trigger = null) { 190 $this->trigger = $trigger; 191 return $this; 192 } 193 194 /** 195 * @return PhabricatorProjectTrigger|null 196 */ 197 public function getTrigger() { 198 return $this->assertAttached($this->trigger); 199 } 200 201 public function canHaveTrigger() { 202 // Backlog columns and proxy (subproject / milestone) columns can't have 203 // triggers because cards routinely end up in these columns through tag 204 // edits rather than drag-and-drop and it would likely be confusing to 205 // have these triggers act only a small fraction of the time. 206 207 if ($this->isDefaultColumn()) { 208 return false; 209 } 210 211 if ($this->getProxy()) { 212 return false; 213 } 214 215 return true; 216 } 217 218 public function getWorkboardURI() { 219 return $this->getProject()->getWorkboardURI(); 220 } 221 222 public function getDropEffects() { 223 $effects = array(); 224 225 $proxy = $this->getProxy(); 226 if ($proxy && $proxy->isMilestone()) { 227 $effects[] = id(new PhabricatorProjectDropEffect()) 228 ->setIcon($proxy->getProxyColumnIcon()) 229 ->setColor('violet') 230 ->setContent( 231 pht( 232 'Move to milestone %s.', 233 phutil_tag('strong', array(), $this->getDisplayName()))); 234 } else { 235 $effects[] = id(new PhabricatorProjectDropEffect()) 236 ->setIcon('fa-columns') 237 ->setColor('blue') 238 ->setContent( 239 pht( 240 'Move to column %s.', 241 phutil_tag('strong', array(), $this->getDisplayName()))); 242 } 243 244 245 if ($this->canHaveTrigger()) { 246 $trigger = $this->getTrigger(); 247 if ($trigger) { 248 foreach ($trigger->getDropEffects() as $trigger_effect) { 249 $effects[] = $trigger_effect; 250 } 251 } 252 } 253 254 return $effects; 255 } 256 257 258/* -( PhabricatorConduitResultInterface )---------------------------------- */ 259 260 public function getFieldSpecificationsForConduit() { 261 return array( 262 id(new PhabricatorConduitSearchFieldSpecification()) 263 ->setKey('name') 264 ->setType('string') 265 ->setDescription(pht('The display name of the column.')), 266 id(new PhabricatorConduitSearchFieldSpecification()) 267 ->setKey('project') 268 ->setType('map<string, wild>') 269 ->setDescription(pht('The project the column belongs to.')), 270 id(new PhabricatorConduitSearchFieldSpecification()) 271 ->setKey('proxyPHID') 272 ->setType('phid?') 273 ->setDescription( 274 pht( 275 'For columns that proxy another object (like a subproject or '. 276 'milestone), the PHID of the object they proxy.')), 277 id(new PhabricatorConduitSearchFieldSpecification()) 278 ->setKey('isHidden') 279 ->setType('bool') 280 ->setDescription(pht('True if this column is hidden.')), 281 id(new PhabricatorConduitSearchFieldSpecification()) 282 ->setKey('isDefaultColumn') 283 ->setType('bool') 284 ->setDescription(pht('True if this is the default column.')), 285 id(new PhabricatorConduitSearchFieldSpecification()) 286 ->setKey('sequence') 287 ->setType('int') 288 ->setDescription( 289 pht('The sequence in which this column appears on the workboard.')), 290 ); 291 } 292 293 public function getFieldValuesForConduit() { 294 return array( 295 'name' => $this->getDisplayName(), 296 'proxyPHID' => $this->getProxyPHID(), 297 'project' => $this->getProject()->getRefForConduit(), 298 'isHidden' => $this->isHidden(), 299 'isDefaultColumn' => $this->isDefaultColumn(), 300 'sequence' => (int)$this->getSequence(), 301 ); 302 } 303 304 public function getConduitSearchAttachments() { 305 return array(); 306 } 307 308 public function getRefForConduit() { 309 return array( 310 'id' => (int)$this->getID(), 311 'phid' => $this->getPHID(), 312 'name' => $this->getDisplayName(), 313 ); 314 } 315 316 317/* -( PhabricatorApplicationTransactionInterface )------------------------- */ 318 319 320 public function getApplicationTransactionEditor() { 321 return new PhabricatorProjectColumnTransactionEditor(); 322 } 323 324 public function getApplicationTransactionTemplate() { 325 return new PhabricatorProjectColumnTransaction(); 326 } 327 328 329/* -( PhabricatorPolicyInterface )----------------------------------------- */ 330 331 332 public function getCapabilities() { 333 return array( 334 PhabricatorPolicyCapability::CAN_VIEW, 335 PhabricatorPolicyCapability::CAN_EDIT, 336 ); 337 } 338 339 public function getPolicy($capability) { 340 // NOTE: Column policies are enforced as an extended policy which makes 341 // them the same as the project's policies. 342 switch ($capability) { 343 case PhabricatorPolicyCapability::CAN_VIEW: 344 return PhabricatorPolicies::getMostOpenPolicy(); 345 case PhabricatorPolicyCapability::CAN_EDIT: 346 return PhabricatorPolicies::POLICY_USER; 347 } 348 } 349 350 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 351 return $this->getProject()->hasAutomaticCapability( 352 $capability, 353 $viewer); 354 } 355 356 public function describeAutomaticCapability($capability) { 357 return pht('Users must be able to see a project to see its board.'); 358 } 359 360 361/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ 362 363 364 public function getExtendedPolicy($capability, PhabricatorUser $viewer) { 365 return array( 366 array($this->getProject(), $capability), 367 ); 368 } 369 370 371/* -( PhabricatorDestructibleInterface )----------------------------------- */ 372 373 public function destroyObjectPermanently( 374 PhabricatorDestructionEngine $engine) { 375 376 $this->openTransaction(); 377 $this->delete(); 378 $this->saveTransaction(); 379 } 380 381}