2011-02-08 19:53:59 +01:00
|
|
|
<?php
|
|
|
|
|
2011-07-04 22:04:22 +02:00
|
|
|
/**
|
|
|
|
* @group maniphest
|
|
|
|
*/
|
2012-07-11 20:40:10 +02:00
|
|
|
final class ManiphestTask extends ManiphestDAO
|
2013-02-15 16:47:14 +01:00
|
|
|
implements
|
|
|
|
PhabricatorMarkupInterface,
|
|
|
|
PhabricatorPolicyInterface,
|
2013-03-30 17:32:29 +01:00
|
|
|
PhabricatorTokenReceiverInterface,
|
|
|
|
PhrequentTrackableInterface {
|
2012-07-11 20:40:10 +02:00
|
|
|
|
|
|
|
const MARKUP_FIELD_DESCRIPTION = 'markup:desc';
|
2011-02-08 19:53:59 +01:00
|
|
|
|
|
|
|
protected $phid;
|
|
|
|
protected $authorPHID;
|
|
|
|
protected $ownerPHID;
|
2011-02-21 05:08:16 +01:00
|
|
|
protected $ccPHIDs = array();
|
2011-02-08 19:53:59 +01:00
|
|
|
|
|
|
|
protected $status;
|
|
|
|
protected $priority;
|
2012-04-02 21:12:04 +02:00
|
|
|
protected $subpriority;
|
2011-02-08 19:53:59 +01:00
|
|
|
|
|
|
|
protected $title;
|
2012-06-14 05:52:10 +02:00
|
|
|
protected $originalTitle;
|
2011-02-08 19:53:59 +01:00
|
|
|
protected $description;
|
2011-10-14 22:11:58 +02:00
|
|
|
protected $originalEmailSource;
|
2011-05-05 08:09:42 +02:00
|
|
|
protected $mailKey;
|
|
|
|
|
2011-02-17 23:32:01 +01:00
|
|
|
protected $attached = array();
|
2011-02-21 05:08:16 +01:00
|
|
|
protected $projectPHIDs = array();
|
2011-06-30 01:16:33 +02:00
|
|
|
private $projectsNeedUpdate;
|
2011-07-07 19:24:49 +02:00
|
|
|
private $subscribersNeedUpdate;
|
2011-02-08 19:53:59 +01:00
|
|
|
|
Allow Maniphest to scale to a massive size
Summary:
Maniphest is missing some keys and some query strategy which will make it
cumbersome to manage more than a few tens of thousands of tasks.
Test Plan:
Handily manipulated 100k-scale task groups. Maniphest takes about 250ms to
select and render pages of 1,000 tasks and has no problem paging and filtering
them, etc. We should be good to scale to multiple millions of tasks with these
changes.
Reviewed By: gc3
Reviewers: fratrik, jungejason, aran, tuomaspelkonen, gc3
Commenters: jungejason
CC: anjali, aran, epriestley, gc3, jungejason
Differential Revision: 534
2011-06-27 03:50:17 +02:00
|
|
|
protected $ownerOrdering;
|
|
|
|
|
2013-09-03 15:02:14 +02:00
|
|
|
private $auxiliaryAttributes = self::ATTACHABLE;
|
2011-12-24 04:03:28 +01:00
|
|
|
private $auxiliaryDirty = array();
|
|
|
|
|
2011-02-08 19:53:59 +01:00
|
|
|
public function getConfiguration() {
|
|
|
|
return array(
|
|
|
|
self::CONFIG_AUX_PHID => true,
|
|
|
|
self::CONFIG_SERIALIZATION => array(
|
|
|
|
'ccPHIDs' => self::SERIALIZATION_JSON,
|
2011-02-17 23:32:01 +01:00
|
|
|
'attached' => self::SERIALIZATION_JSON,
|
2011-02-21 05:08:16 +01:00
|
|
|
'projectPHIDs' => self::SERIALIZATION_JSON,
|
2011-02-08 19:53:59 +01:00
|
|
|
),
|
|
|
|
) + parent::getConfiguration();
|
|
|
|
}
|
|
|
|
|
2012-07-19 05:41:42 +02:00
|
|
|
public function loadDependsOnTaskPHIDs() {
|
|
|
|
return PhabricatorEdgeQuery::loadDestinationPHIDs(
|
|
|
|
$this->getPHID(),
|
|
|
|
PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function loadDependedOnByTaskPHIDs() {
|
|
|
|
return PhabricatorEdgeQuery::loadDestinationPHIDs(
|
|
|
|
$this->getPHID(),
|
|
|
|
PhabricatorEdgeConfig::TYPE_TASK_DEPENDED_ON_BY_TASK);
|
|
|
|
}
|
|
|
|
|
2011-02-17 23:32:01 +01:00
|
|
|
public function getAttachedPHIDs($type) {
|
|
|
|
return array_keys(idx($this->attached, $type, array()));
|
|
|
|
}
|
|
|
|
|
2011-02-08 19:53:59 +01:00
|
|
|
public function generatePHID() {
|
2013-07-21 21:05:28 +02:00
|
|
|
return PhabricatorPHID::generateNewPHID(ManiphestPHIDTypeTask::TYPECONST);
|
2011-02-08 19:53:59 +01:00
|
|
|
}
|
|
|
|
|
2011-02-09 21:47:24 +01:00
|
|
|
public function getCCPHIDs() {
|
2011-11-16 18:21:11 +01:00
|
|
|
return array_values(nonempty($this->ccPHIDs, array()));
|
2011-02-09 21:47:24 +01:00
|
|
|
}
|
|
|
|
|
2011-06-30 01:16:33 +02:00
|
|
|
public function setProjectPHIDs(array $phids) {
|
2011-11-16 18:21:11 +01:00
|
|
|
$this->projectPHIDs = array_values($phids);
|
2011-06-30 01:16:33 +02:00
|
|
|
$this->projectsNeedUpdate = true;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-11-16 18:21:11 +01:00
|
|
|
public function getProjectPHIDs() {
|
|
|
|
return array_values(nonempty($this->projectPHIDs, array()));
|
|
|
|
}
|
|
|
|
|
2011-07-07 19:24:49 +02:00
|
|
|
public function setCCPHIDs(array $phids) {
|
2011-11-16 18:21:11 +01:00
|
|
|
$this->ccPHIDs = array_values($phids);
|
2011-07-07 19:24:49 +02:00
|
|
|
$this->subscribersNeedUpdate = true;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setOwnerPHID($phid) {
|
2013-05-23 18:22:18 +02:00
|
|
|
$this->ownerPHID = nonempty($phid, null);
|
2011-07-07 19:24:49 +02:00
|
|
|
$this->subscribersNeedUpdate = true;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-12-24 04:03:28 +01:00
|
|
|
public function getAuxiliaryAttribute($key, $default = null) {
|
2013-09-03 15:02:14 +02:00
|
|
|
$this->assertAttached($this->auxiliaryAttributes);
|
2011-12-24 04:03:28 +01:00
|
|
|
return idx($this->auxiliaryAttributes, $key, $default);
|
2011-07-26 00:34:56 +02:00
|
|
|
}
|
|
|
|
|
2011-12-24 04:03:28 +01:00
|
|
|
public function setAuxiliaryAttribute($key, $val) {
|
2013-09-03 15:02:14 +02:00
|
|
|
$this->assertAttached($this->auxiliaryAttributes);
|
|
|
|
|
2011-12-24 04:03:28 +01:00
|
|
|
$this->auxiliaryAttributes[$key] = $val;
|
|
|
|
$this->auxiliaryDirty[$key] = true;
|
|
|
|
return $this;
|
2011-07-26 00:34:56 +02:00
|
|
|
}
|
|
|
|
|
2012-06-14 05:52:10 +02:00
|
|
|
public function setTitle($title) {
|
|
|
|
$this->title = $title;
|
|
|
|
if (!$this->getID()) {
|
|
|
|
$this->originalTitle = $title;
|
|
|
|
}
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-12-24 04:03:28 +01:00
|
|
|
public function attachAuxiliaryAttributes(array $attrs) {
|
|
|
|
if ($this->auxiliaryDirty) {
|
|
|
|
throw new Exception(
|
|
|
|
"This object has dirty attributes, you can not attach new attributes ".
|
|
|
|
"without writing or discarding the dirty attributes.");
|
2011-07-26 00:34:56 +02:00
|
|
|
}
|
2011-12-24 04:03:28 +01:00
|
|
|
$this->auxiliaryAttributes = $attrs;
|
|
|
|
return $this;
|
2011-07-26 00:34:56 +02:00
|
|
|
}
|
|
|
|
|
2011-12-24 04:03:28 +01:00
|
|
|
public function loadAndAttachAuxiliaryAttributes() {
|
|
|
|
if (!$this->getPHID()) {
|
|
|
|
$this->auxiliaryAttributes = array();
|
2012-04-03 21:11:51 +02:00
|
|
|
return $this;
|
2011-12-24 04:03:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$storage = id(new ManiphestTaskAuxiliaryStorage())->loadAllWhere(
|
2011-07-26 00:34:56 +02:00
|
|
|
'taskPHID = %s',
|
|
|
|
$this->getPHID());
|
|
|
|
|
2011-12-24 04:03:28 +01:00
|
|
|
$this->auxiliaryAttributes = mpull($storage, 'getValue', 'getName');
|
|
|
|
|
|
|
|
return $this;
|
2011-07-26 00:34:56 +02:00
|
|
|
}
|
|
|
|
|
2011-05-05 08:09:42 +02:00
|
|
|
public function save() {
|
|
|
|
if (!$this->mailKey) {
|
Replace callsites to sha1() that use it to asciify entropy with
Filesystem::readRandomCharacters()
Summary: See T547. To improve auditability of use of crypto-sensitive hash
functions, use Filesystem::readRandomCharacters() in place of
sha1(Filesystem::readRandomBytes()) when we're just generating random ASCII
strings.
Test Plan:
- Generated a new PHID.
- Logged out and logged back in (to test sessions).
- Regenerated Conduit certificate.
- Created a new task, verified mail key generated sensibly.
- Created a new revision, verified mail key generated sensibly.
- Ran "arc list", got blocked, installed new certificate, ran "arc list"
again.
Reviewers: jungejason, nh, tuomaspelkonen, aran, benmathews
Reviewed By: jungejason
CC: aran, epriestley, jungejason
Differential Revision: 1000
2011-10-11 04:22:30 +02:00
|
|
|
$this->mailKey = Filesystem::readRandomCharacters(20);
|
2011-05-05 08:09:42 +02:00
|
|
|
}
|
2011-06-30 01:16:33 +02:00
|
|
|
|
|
|
|
$result = parent::save();
|
|
|
|
|
|
|
|
if ($this->projectsNeedUpdate) {
|
|
|
|
// If we've changed the project PHIDs for this task, update the link
|
|
|
|
// table.
|
|
|
|
ManiphestTaskProject::updateTaskProjects($this);
|
|
|
|
$this->projectsNeedUpdate = false;
|
|
|
|
}
|
|
|
|
|
2011-07-07 19:24:49 +02:00
|
|
|
if ($this->subscribersNeedUpdate) {
|
|
|
|
// If we've changed the subscriber PHIDs for this task, update the link
|
|
|
|
// table.
|
|
|
|
ManiphestTaskSubscriber::updateTaskSubscribers($this);
|
|
|
|
$this->subscribersNeedUpdate = false;
|
|
|
|
}
|
|
|
|
|
2011-12-24 04:03:28 +01:00
|
|
|
if ($this->auxiliaryDirty) {
|
|
|
|
$this->writeAuxiliaryUpdates();
|
|
|
|
$this->auxiliaryDirty = array();
|
|
|
|
}
|
|
|
|
|
2011-06-30 01:16:33 +02:00
|
|
|
return $result;
|
2011-05-05 08:09:42 +02:00
|
|
|
}
|
|
|
|
|
2011-12-24 04:03:28 +01:00
|
|
|
private function writeAuxiliaryUpdates() {
|
|
|
|
$table = new ManiphestTaskAuxiliaryStorage();
|
|
|
|
$conn_w = $table->establishConnection('w');
|
|
|
|
$update = array();
|
|
|
|
$remove = array();
|
|
|
|
|
|
|
|
foreach ($this->auxiliaryDirty as $key => $dirty) {
|
|
|
|
$value = $this->getAuxiliaryAttribute($key);
|
|
|
|
if ($value === null) {
|
|
|
|
$remove[$key] = true;
|
|
|
|
} else {
|
|
|
|
$update[$key] = $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($remove) {
|
|
|
|
queryfx(
|
|
|
|
$conn_w,
|
|
|
|
'DELETE FROM %T WHERE taskPHID = %s AND name IN (%Ls)',
|
|
|
|
$table->getTableName(),
|
|
|
|
$this->getPHID(),
|
|
|
|
array_keys($remove));
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($update) {
|
|
|
|
$sql = array();
|
|
|
|
foreach ($update as $key => $val) {
|
|
|
|
$sql[] = qsprintf(
|
|
|
|
$conn_w,
|
2013-02-27 19:51:34 +01:00
|
|
|
'(%s, %s, %s, %d, %d)',
|
2011-12-24 04:03:28 +01:00
|
|
|
$this->getPHID(),
|
|
|
|
$key,
|
2013-02-27 19:51:34 +01:00
|
|
|
$val,
|
|
|
|
time(),
|
|
|
|
time());
|
2011-12-24 04:03:28 +01:00
|
|
|
}
|
|
|
|
queryfx(
|
|
|
|
$conn_w,
|
2013-02-27 19:51:34 +01:00
|
|
|
'INSERT INTO %T (taskPHID, name, value, dateCreated, dateModified)
|
|
|
|
VALUES %Q ON DUPLICATE KEY
|
|
|
|
UPDATE value = VALUES(value), dateModified = VALUES(dateModified)',
|
2011-12-24 04:03:28 +01:00
|
|
|
$table->getTableName(),
|
|
|
|
implode(', ', $sql));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-11 20:40:10 +02:00
|
|
|
|
|
|
|
/* -( Markup Interface )--------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task markup
|
|
|
|
*/
|
|
|
|
public function getMarkupFieldKey($field) {
|
|
|
|
$hash = PhabricatorHash::digest($this->getMarkupText($field));
|
|
|
|
$id = $this->getID();
|
|
|
|
return "maniphest:T{$id}:{$field}:{$hash}";
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task markup
|
|
|
|
*/
|
|
|
|
public function getMarkupText($field) {
|
|
|
|
return $this->getDescription();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task markup
|
|
|
|
*/
|
|
|
|
public function newMarkupEngine($field) {
|
|
|
|
return PhabricatorMarkupEngine::newManiphestMarkupEngine();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task markup
|
|
|
|
*/
|
|
|
|
public function didMarkupText(
|
|
|
|
$field,
|
|
|
|
$output,
|
|
|
|
PhutilMarkupEngine $engine) {
|
|
|
|
return $output;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task markup
|
|
|
|
*/
|
|
|
|
public function shouldUseMarkupCache($field) {
|
|
|
|
return (bool)$this->getID();
|
|
|
|
}
|
|
|
|
|
2013-02-15 16:47:14 +01:00
|
|
|
|
|
|
|
/* -( Policy Interface )--------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
public function getCapabilities() {
|
|
|
|
return array(
|
|
|
|
PhabricatorPolicyCapability::CAN_VIEW,
|
|
|
|
PhabricatorPolicyCapability::CAN_EDIT,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getPolicy($capability) {
|
|
|
|
return PhabricatorPolicies::POLICY_USER;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function hasAutomaticCapability($capability, PhabricatorUser $user) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-02-19 02:44:45 +01:00
|
|
|
|
|
|
|
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
|
|
|
|
|
|
|
|
public function getUsersToNotifyOfTokenGiven() {
|
|
|
|
// Sort of ambiguous who this was intended for; just let them both know.
|
|
|
|
return array_filter(
|
|
|
|
array_unique(
|
|
|
|
array(
|
|
|
|
$this->getAuthorPHID(),
|
|
|
|
$this->getOwnerPHID(),
|
|
|
|
)));
|
|
|
|
}
|
|
|
|
|
2011-02-08 19:53:59 +01:00
|
|
|
}
|