Page MenuHomestyx hydra

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/src/applications/base/storage/configuration/DatabaseConfigurationProvider.php b/src/applications/base/storage/configuration/DatabaseConfigurationProvider.php
index cdc9652890..4e6f5837dd 100644
--- a/src/applications/base/storage/configuration/DatabaseConfigurationProvider.php
+++ b/src/applications/base/storage/configuration/DatabaseConfigurationProvider.php
@@ -1,62 +1,65 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+/**
+ * TODO: Can we final this?
+ */
class DatabaseConfigurationProvider {
private $dao;
private $mode;
public function __construct(LiskDAO $dao, $mode) {
$this->dao = $dao;
$this->mode = $mode;
}
public function getUser() {
return PhabricatorEnv::getEnvConfig('mysql.user');
}
public function getPassword() {
return PhabricatorEnv::getEnvConfig('mysql.pass');
}
public function getHost() {
return PhabricatorEnv::getEnvConfig('mysql.host');
}
public function getDatabase() {
return 'phabricator_'.$this->getDao()->getApplicationName();
}
final protected function getDao() {
return $this->dao;
}
final protected function getMode() {
return $this->mode;
}
public static function getConfiguration() {
// Get DB info. Note that we are using a dummy PhabricatorUser object in
// creating the DatabaseConfigurationProvider, which is not used at all.
$conf_provider = PhabricatorEnv::getEnvConfig(
'mysql.configuration_provider', 'DatabaseConfigurationProvider');
PhutilSymbolLoader::loadClass($conf_provider);
$conf = newv($conf_provider, array(new PhabricatorUser(), 'r'));
return $conf;
}
}
diff --git a/src/applications/conduit/storage/methodcalllog/PhabricatorConduitMethodCallLog.php b/src/applications/conduit/storage/methodcalllog/PhabricatorConduitMethodCallLog.php
index a44aa9c522..3a8d6d64ca 100644
--- a/src/applications/conduit/storage/methodcalllog/PhabricatorConduitMethodCallLog.php
+++ b/src/applications/conduit/storage/methodcalllog/PhabricatorConduitMethodCallLog.php
@@ -1,29 +1,29 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group conduit
*/
-class PhabricatorConduitMethodCallLog extends PhabricatorConduitDAO {
+final class PhabricatorConduitMethodCallLog extends PhabricatorConduitDAO {
protected $connectionID;
protected $method;
protected $error;
protected $duration;
}
diff --git a/src/applications/herald/adapter/commit/HeraldCommitAdapter.php b/src/applications/herald/adapter/commit/HeraldCommitAdapter.php
index 3a84d4d8eb..4ee4ec2bd3 100644
--- a/src/applications/herald/adapter/commit/HeraldCommitAdapter.php
+++ b/src/applications/herald/adapter/commit/HeraldCommitAdapter.php
@@ -1,227 +1,227 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldCommitAdapter extends HeraldObjectAdapter {
+final class HeraldCommitAdapter extends HeraldObjectAdapter {
protected $diff;
protected $revision;
protected $repository;
protected $commit;
protected $commitData;
protected $emailPHIDs = array();
protected $auditMap = array();
protected $affectedPaths;
protected $affectedRevision;
protected $affectedPackages;
protected $auditNeededPackages;
public function __construct(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit,
PhabricatorRepositoryCommitData $commit_data) {
$this->repository = $repository;
$this->commit = $commit;
$this->commitData = $commit_data;
}
public function getPHID() {
return $this->commit->getPHID();
}
public function getEmailPHIDs() {
return array_keys($this->emailPHIDs);
}
public function getAuditMap() {
return $this->auditMap;
}
public function getHeraldName() {
return
'r'.
$this->repository->getCallsign().
$this->commit->getCommitIdentifier();
}
public function getHeraldTypeName() {
return HeraldContentTypeConfig::CONTENT_TYPE_COMMIT;
}
public function loadAffectedPaths() {
if ($this->affectedPaths === null) {
$result = PhabricatorOwnerPathQuery::loadAffectedPaths(
$this->repository, $this->commit);
$this->affectedPaths = $result;
}
return $this->affectedPaths;
}
public function loadAffectedPackages() {
if ($this->affectedPackages === null) {
$packages = PhabricatorOwnersPackage::loadAffectedPackages(
$this->repository,
$this->loadAffectedPaths());
$this->affectedPackages = $packages;
}
return $this->affectedPackages;
}
public function loadAuditNeededPackage() {
if ($this->auditNeededPackages === null) {
$status_arr = array(
PhabricatorAuditStatusConstants::AUDIT_REQUIRED,
PhabricatorAuditStatusConstants::CONCERNED,
);
$requests = id(new PhabricatorRepositoryAuditRequest())
->loadAllWhere(
"commitPHID = %s AND auditStatus IN (%Ls)",
$this->commit->getPHID(),
$status_arr);
$packages = mpull($requests, 'getAuditorPHID');
$this->auditNeededPackages = $packages;
}
return $this->auditNeededPackages;
}
public function loadDifferentialRevision() {
if ($this->affectedRevision === null) {
$this->affectedRevision = false;
$data = $this->commitData;
$revision_id = $data->getCommitDetail('differential.revisionID');
if ($revision_id) {
$revision = id(new DifferentialRevision())->load($revision_id);
if ($revision) {
$revision->loadRelationships();
$this->affectedRevision = $revision;
}
}
}
return $this->affectedRevision;
}
public function getHeraldField($field) {
$data = $this->commitData;
switch ($field) {
case HeraldFieldConfig::FIELD_BODY:
return $data->getCommitMessage();
case HeraldFieldConfig::FIELD_AUTHOR:
return $data->getCommitDetail('authorPHID');
case HeraldFieldConfig::FIELD_REVIEWER:
return $data->getCommitDetail('reviewerPHID');
case HeraldFieldConfig::FIELD_DIFF_FILE:
return $this->loadAffectedPaths();
case HeraldFieldConfig::FIELD_REPOSITORY:
return $this->repository->getPHID();
case HeraldFieldConfig::FIELD_DIFF_CONTENT:
// TODO!
return null;
/*
try {
$diff = $this->loadDiff();
} catch (Exception $ex) {
// See rE280053 for an example.
return array(
'<<< Failed to load diff, this usually means the change committed '.
'a binary file as text. >>>',
);
}
$dict = array();
$changes = $diff->getChangesets();
$lines = array();
foreach ($changes as $change) {
$lines = array();
foreach ($change->getHunks() as $hunk) {
$lines[] = $hunk->makeChanges();
}
$dict[$change->getTrueFilename()] = implode("\n", $lines);
}
return $dict;
*/
case HeraldFieldConfig::FIELD_AFFECTED_PACKAGE:
$packages = $this->loadAffectedPackages();
return mpull($packages, 'getPHID');
case HeraldFieldConfig::FIELD_AFFECTED_PACKAGE_OWNER:
$packages = $this->loadAffectedPackages();
$owners = PhabricatorOwnersOwner::loadAllForPackages($packages);
return mpull($owners, 'getUserPHID');
case HeraldFieldConfig::FIELD_NEED_AUDIT_FOR_PACKAGE:
return $this->loadAuditNeededPackage();
case HeraldFieldConfig::FIELD_DIFFERENTIAL_REVISION:
$revision = $this->loadDifferentialRevision();
if (!$revision) {
return null;
}
return $revision->getID();
case HeraldFieldConfig::FIELD_DIFFERENTIAL_REVIEWERS:
$revision = $this->loadDifferentialRevision();
if (!$revision) {
return null;
}
return $revision->getReviewers();
case HeraldFieldConfig::FIELD_DIFFERENTIAL_CCS:
$revision = $this->loadDifferentialRevision();
if (!$revision) {
return null;
}
return $revision->getCCPHIDs();
default:
throw new Exception("Invalid field '{$field}'.");
}
}
public function applyHeraldEffects(array $effects) {
$result = array();
foreach ($effects as $effect) {
$action = $effect->getAction();
switch ($action) {
case HeraldActionConfig::ACTION_NOTHING:
$result[] = new HeraldApplyTranscript(
$effect,
true,
'Great success at doing nothing.');
break;
case HeraldActionConfig::ACTION_EMAIL:
foreach ($effect->getTarget() as $phid) {
$this->emailPHIDs[$phid] = true;
}
$result[] = new HeraldApplyTranscript(
$effect,
true,
'Added address to email targets.');
break;
case HeraldActionConfig::ACTION_AUDIT:
foreach ($effect->getTarget() as $phid) {
if (empty($this->auditMap[$phid])) {
$this->auditMap[$phid] = array();
}
$this->auditMap[$phid][] = $effect->getRuleID();
}
break;
default:
throw new Exception("No rules to handle action '{$action}'.");
}
}
return $result;
}
}
diff --git a/src/applications/herald/adapter/differential/HeraldDifferentialRevisionAdapter.php b/src/applications/herald/adapter/differential/HeraldDifferentialRevisionAdapter.php
index 11668483f6..a4fb6135e7 100644
--- a/src/applications/herald/adapter/differential/HeraldDifferentialRevisionAdapter.php
+++ b/src/applications/herald/adapter/differential/HeraldDifferentialRevisionAdapter.php
@@ -1,310 +1,310 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldDifferentialRevisionAdapter extends HeraldObjectAdapter {
+final class HeraldDifferentialRevisionAdapter extends HeraldObjectAdapter {
protected $revision;
protected $diff;
protected $explicitCCs;
protected $explicitReviewers;
protected $forbiddenCCs;
protected $newCCs = array();
protected $remCCs = array();
protected $emailPHIDs = array();
protected $repository;
protected $affectedPackages;
protected $changesets;
public function __construct(
DifferentialRevision $revision,
DifferentialDiff $diff) {
$revision->loadRelationships();
$this->revision = $revision;
$this->diff = $diff;
}
public function setExplicitCCs($explicit_ccs) {
$this->explicitCCs = $explicit_ccs;
return $this;
}
public function setExplicitReviewers($explicit_reviewers) {
$this->explicitReviewers = $explicit_reviewers;
return $this;
}
public function setForbiddenCCs($forbidden_ccs) {
$this->forbiddenCCs = $forbidden_ccs;
return $this;
}
public function getCCsAddedByHerald() {
return array_diff_key($this->newCCs, $this->remCCs);
}
public function getCCsRemovedByHerald() {
return $this->remCCs;
}
public function getEmailPHIDsAddedByHerald() {
return $this->emailPHIDs;
}
public function getPHID() {
return $this->revision->getPHID();
}
public function getHeraldName() {
return $this->revision->getTitle();
}
public function getHeraldTypeName() {
return HeraldContentTypeConfig::CONTENT_TYPE_DIFFERENTIAL;
}
public function loadRepository() {
if ($this->repository === null) {
$diff = $this->diff;
$repository = false;
if ($diff->getRepositoryUUID()) {
$repository = id(new PhabricatorRepository())->loadOneWhere(
'uuid = %s',
$diff->getRepositoryUUID());
}
if (!$repository && $diff->getArcanistProjectPHID()) {
$project = id(new PhabricatorRepositoryArcanistProject())->loadOneWhere(
'phid = %s',
$diff->getArcanistProjectPHID());
if ($project && $project->getRepositoryID()) {
$repository = id(new PhabricatorRepository())->load(
$project->getRepositoryID());
}
}
$this->repository = $repository;
}
return $this->repository;
}
protected function loadChangesets() {
if ($this->changesets === null) {
$this->changesets = $this->diff->loadChangesets();
}
return $this->changesets;
}
protected function loadAffectedPaths() {
$changesets = $this->loadChangesets();
$paths = array();
foreach ($changesets as $changeset) {
$paths[] = $this->getAbsoluteRepositoryPathForChangeset($changeset);
}
return $paths;
}
protected function getAbsoluteRepositoryPathForChangeset(
DifferentialChangeset $changeset) {
$repository = $this->loadRepository();
if (!$repository) {
return '/'.ltrim($changeset->getFilename(), '/');
}
$diff = $this->diff;
return $changeset->getAbsoluteRepositoryPath($diff, $repository);
}
protected function loadContentDictionary() {
$changesets = $this->loadChangesets();
$hunks = array();
if ($changesets) {
$hunks = id(new DifferentialHunk())->loadAllWhere(
'changesetID in (%Ld)',
mpull($changesets, 'getID'));
}
$dict = array();
$hunks = mgroup($hunks, 'getChangesetID');
$changesets = mpull($changesets, null, 'getID');
foreach ($changesets as $id => $changeset) {
$path = $this->getAbsoluteRepositoryPathForChangeset($changeset);
$content = array();
foreach (idx($hunks, $id, array()) as $hunk) {
$content[] = $hunk->makeChanges();
}
$dict[$path] = implode("\n", $content);
}
return $dict;
}
public function loadAffectedPackages() {
if ($this->affectedPackages === null) {
$this->affectedPackages = array();
$repository = $this->loadRepository();
if ($repository) {
$packages = PhabricatorOwnersPackage::loadAffectedPackages(
$repository,
$this->loadAffectedPaths());
$this->affectedPackages = $packages;
}
}
return $this->affectedPackages;
}
public function getHeraldField($field) {
switch ($field) {
case HeraldFieldConfig::FIELD_TITLE:
return $this->revision->getTitle();
break;
case HeraldFieldConfig::FIELD_BODY:
return $this->revision->getSummary()."\n".
$this->revision->getTestPlan();
break;
case HeraldFieldConfig::FIELD_AUTHOR:
return $this->revision->getAuthorPHID();
break;
case HeraldFieldConfig::FIELD_DIFF_FILE:
return $this->loadAffectedPaths();
case HeraldFieldConfig::FIELD_CC:
if (isset($this->explicitCCs)) {
return array_keys($this->explicitCCs);
} else {
return $this->revision->getCCPHIDs();
}
case HeraldFieldConfig::FIELD_REVIEWERS:
if (isset($this->explicitReviewers)) {
return array_keys($this->explicitReviewers);
} else {
return $this->revision->getReviewers();
}
case HeraldFieldConfig::FIELD_REPOSITORY:
$repository = $this->loadRepository();
if (!$repository) {
return null;
}
return $repository->getPHID();
case HeraldFieldConfig::FIELD_DIFF_CONTENT:
return $this->loadContentDictionary();
case HeraldFieldConfig::FIELD_AFFECTED_PACKAGE:
$packages = $this->loadAffectedPackages();
return mpull($packages, 'getPHID');
case HeraldFieldConfig::FIELD_AFFECTED_PACKAGE_OWNER:
$packages = $this->loadAffectedPackages();
$owners = PhabricatorOwnersOwner::loadAllForPackages($packages);
return mpull($owners, 'getUserPHID');
default:
throw new Exception("Invalid field '{$field}'.");
}
}
public function applyHeraldEffects(array $effects) {
$result = array();
if ($this->explicitCCs) {
$effect = new HeraldEffect();
$effect->setAction(HeraldActionConfig::ACTION_ADD_CC);
$effect->setTarget(array_keys($this->explicitCCs));
$effect->setReason(
'CCs provided explicitly by revision author or carried over from a '.
'previous version of the revision.');
$result[] = new HeraldApplyTranscript(
$effect,
true,
'Added addresses to CC list.');
}
$forbidden_ccs = array_fill_keys(
nonempty($this->forbiddenCCs, array()),
true);
foreach ($effects as $effect) {
$action = $effect->getAction();
switch ($action) {
case HeraldActionConfig::ACTION_NOTHING:
$result[] = new HeraldApplyTranscript(
$effect,
true,
'OK, did nothing.');
break;
case HeraldActionConfig::ACTION_EMAIL:
case HeraldActionConfig::ACTION_ADD_CC:
$op = ($action == HeraldActionConfig::ACTION_EMAIL) ? 'email' : 'CC';
$base_target = $effect->getTarget();
$forbidden = array();
foreach ($base_target as $key => $fbid) {
if (isset($forbidden_ccs[$fbid])) {
$forbidden[] = $fbid;
unset($base_target[$key]);
} else {
if ($action == HeraldActionConfig::ACTION_EMAIL) {
$this->emailPHIDs[$fbid] = true;
} else {
$this->newCCs[$fbid] = true;
}
}
}
if ($forbidden) {
$failed = clone $effect;
$failed->setTarget($forbidden);
if ($base_target) {
$effect->setTarget($base_target);
$result[] = new HeraldApplyTranscript(
$effect,
true,
'Added these addresses to '.$op.' list. '.
'Others could not be added.');
}
$result[] = new HeraldApplyTranscript(
$failed,
false,
$op.' forbidden, these addresses have unsubscribed.');
} else {
$result[] = new HeraldApplyTranscript(
$effect,
true,
'Added addresses to '.$op.' list.');
}
break;
case HeraldActionConfig::ACTION_REMOVE_CC:
foreach ($effect->getTarget() as $fbid) {
$this->remCCs[$fbid] = true;
}
$result[] = new HeraldApplyTranscript(
$effect,
true,
'Removed addresses from CC list.');
break;
default:
throw new Exception("No rules to handle action '{$action}'.");
}
}
return $result;
}
}
diff --git a/src/applications/herald/adapter/dryrun/HeraldDryRunAdapter.php b/src/applications/herald/adapter/dryrun/HeraldDryRunAdapter.php
index cc03364625..9755eef022 100644
--- a/src/applications/herald/adapter/dryrun/HeraldDryRunAdapter.php
+++ b/src/applications/herald/adapter/dryrun/HeraldDryRunAdapter.php
@@ -1,47 +1,47 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldDryRunAdapter extends HeraldObjectAdapter {
+final class HeraldDryRunAdapter extends HeraldObjectAdapter {
public function getPHID() {
return 0;
}
public function getHeraldName() {
return 'Dry Run';
}
public function getHeraldTypeName() {
return null;
}
public function getHeraldField($field) {
return null;
}
public function applyHeraldEffects(array $effects) {
$results = array();
foreach ($effects as $effect) {
$results[] = new HeraldApplyTranscript(
$effect,
false,
'This was a dry run, so no actions were actually taken.');
}
return $results;
}
}
diff --git a/src/applications/herald/config/action/HeraldActionConfig.php b/src/applications/herald/config/action/HeraldActionConfig.php
index c98c877aff..a2ac6aa7a6 100644
--- a/src/applications/herald/config/action/HeraldActionConfig.php
+++ b/src/applications/herald/config/action/HeraldActionConfig.php
@@ -1,139 +1,139 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldActionConfig {
+final class HeraldActionConfig {
const ACTION_ADD_CC = 'addcc';
const ACTION_REMOVE_CC = 'remcc';
const ACTION_EMAIL = 'email';
const ACTION_NOTHING = 'nothing';
const ACTION_AUDIT = 'audit';
public static function getActionMessageMapForRuleType($rule_type) {
$generic_mappings =
array(
self::ACTION_NOTHING => 'Do nothing',
);
switch ($rule_type) {
case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL:
$specific_mappings =
array(
self::ACTION_ADD_CC => 'Add emails to CC',
self::ACTION_REMOVE_CC => 'Remove emails from CC',
self::ACTION_EMAIL => 'Send an email to',
self::ACTION_AUDIT => 'Trigger an Audit for project',
);
break;
case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
$specific_mappings =
array(
self::ACTION_ADD_CC => 'CC me',
self::ACTION_REMOVE_CC => 'Remove me from CC',
self::ACTION_EMAIL => 'Email me',
self::ACTION_AUDIT => 'Trigger an Audit by me',
);
break;
default:
throw new Exception("Unknown rule type '${rule_type}'");
}
return $generic_mappings + $specific_mappings;
}
public static function getActionMessageMap($content_type,
$rule_type) {
$map = self::getActionMessageMapForRuleType($rule_type);
switch ($content_type) {
case HeraldContentTypeConfig::CONTENT_TYPE_DIFFERENTIAL:
return array_select_keys(
$map,
array(
self::ACTION_ADD_CC,
self::ACTION_REMOVE_CC,
self::ACTION_EMAIL,
self::ACTION_NOTHING,
));
case HeraldContentTypeConfig::CONTENT_TYPE_COMMIT:
return array_select_keys(
$map,
array(
self::ACTION_EMAIL,
self::ACTION_AUDIT,
self::ACTION_NOTHING,
));
case HeraldContentTypeConfig::CONTENT_TYPE_MERGE:
return array_select_keys(
$map,
array(
self::ACTION_EMAIL,
self::ACTION_NOTHING,
));
case HeraldContentTypeConfig::CONTENT_TYPE_OWNERS:
return array_select_keys(
$map,
array(
self::ACTION_EMAIL,
self::ACTION_NOTHING,
));
default:
throw new Exception("Unknown content type '{$type}'.");
}
}
/**
* Create a HeraldAction to save from data.
*
* $data is of the form:
* array(
* 0 => <action type>
* 1 => array(<targets>)
* )
*/
public static function willSaveAction($rule_type,
$author_phid,
$data) {
$obj = new HeraldAction();
$obj->setAction($data[0]);
// for personal rule types, set the target to be the owner of the rule
if ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) {
switch ($obj->getAction()) {
case HeraldActionConfig::ACTION_EMAIL:
case HeraldActionConfig::ACTION_ADD_CC:
case HeraldActionConfig::ACTION_REMOVE_CC:
case HeraldActionConfig::ACTION_AUDIT:
$data[1] = array($author_phid => $author_phid);
break;
case HeraldActionConfig::ACTION_NOTHING:
break;
default:
throw new Exception('Unrecognized action type: ' .
$obj->getAction());
}
}
if (is_array($data[1])) {
$obj->setTarget(array_keys($data[1]));
} else {
$obj->setTarget($data[1]);
}
return $obj;
}
}
diff --git a/src/applications/herald/config/condition/HeraldConditionConfig.php b/src/applications/herald/config/condition/HeraldConditionConfig.php
index 60d683b126..9f6729bd6c 100644
--- a/src/applications/herald/config/condition/HeraldConditionConfig.php
+++ b/src/applications/herald/config/condition/HeraldConditionConfig.php
@@ -1,142 +1,142 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldConditionConfig {
+final class HeraldConditionConfig {
const CONDITION_CONTAINS = 'contains';
const CONDITION_NOT_CONTAINS = '!contains';
const CONDITION_IS = 'is';
const CONDITION_IS_NOT = '!is';
const CONDITION_IS_ANY = 'isany';
const CONDITION_IS_NOT_ANY = '!isany';
const CONDITION_INCLUDE_ALL = 'all';
const CONDITION_INCLUDE_ANY = 'any';
const CONDITION_INCLUDE_NONE = 'none';
const CONDITION_IS_ME = 'me';
const CONDITION_IS_NOT_ME = '!me';
const CONDITION_REGEXP = 'regexp';
const CONDITION_RULE = 'conditions';
const CONDITION_NOT_RULE = '!conditions';
const CONDITION_EXISTS = 'exists';
const CONDITION_NOT_EXISTS = '!exists';
const CONDITION_REGEXP_PAIR = 'regexp-pair';
public static function getConditionMap() {
static $map = array(
self::CONDITION_CONTAINS => 'contains',
self::CONDITION_NOT_CONTAINS => 'does not contain',
self::CONDITION_IS => 'is',
self::CONDITION_IS_NOT => 'is not',
self::CONDITION_IS_ANY => 'is any of',
self::CONDITION_IS_NOT_ANY => 'is not any of',
self::CONDITION_INCLUDE_ALL => 'include all of',
self::CONDITION_INCLUDE_ANY => 'include any of',
self::CONDITION_INCLUDE_NONE => 'include none of',
self::CONDITION_IS_ME => 'is myself',
self::CONDITION_IS_NOT_ME => 'is not myself',
self::CONDITION_REGEXP => 'matches regexp',
self::CONDITION_RULE => 'matches:',
self::CONDITION_NOT_RULE => 'does not match:',
self::CONDITION_EXISTS => 'exists',
self::CONDITION_NOT_EXISTS => 'does not exist',
self::CONDITION_REGEXP_PAIR => 'matches regexp pair',
);
return $map;
}
public static function getConditionMapForField($field) {
$map = self::getConditionMap();
switch ($field) {
case HeraldFieldConfig::FIELD_TITLE:
case HeraldFieldConfig::FIELD_BODY:
return array_select_keys(
$map,
array(
self::CONDITION_CONTAINS,
self::CONDITION_NOT_CONTAINS,
self::CONDITION_IS,
self::CONDITION_IS_NOT,
self::CONDITION_REGEXP,
));
case HeraldFieldConfig::FIELD_AUTHOR:
case HeraldFieldConfig::FIELD_REPOSITORY:
case HeraldFieldConfig::FIELD_REVIEWER:
case HeraldFieldConfig::FIELD_MERGE_REQUESTER:
return array_select_keys(
$map,
array(
self::CONDITION_IS_ANY,
self::CONDITION_IS_NOT_ANY,
));
case HeraldFieldConfig::FIELD_TAGS:
case HeraldFieldConfig::FIELD_REVIEWERS:
case HeraldFieldConfig::FIELD_CC:
case HeraldFieldConfig::FIELD_DIFFERENTIAL_REVIEWERS:
case HeraldFieldConfig::FIELD_DIFFERENTIAL_CCS:
return array_select_keys(
$map,
array(
self::CONDITION_INCLUDE_ALL,
self::CONDITION_INCLUDE_ANY,
self::CONDITION_INCLUDE_NONE,
));
case HeraldFieldConfig::FIELD_DIFF_FILE:
return array_select_keys(
$map,
array(
self::CONDITION_CONTAINS,
self::CONDITION_REGEXP,
));
case HeraldFieldConfig::FIELD_DIFF_CONTENT:
return array_select_keys(
$map,
array(
self::CONDITION_CONTAINS,
self::CONDITION_REGEXP,
self::CONDITION_REGEXP_PAIR,
));
case HeraldFieldConfig::FIELD_RULE:
return array_select_keys(
$map,
array(
self::CONDITION_RULE,
self::CONDITION_NOT_RULE,
));
case HeraldFieldConfig::FIELD_AFFECTED_PACKAGE:
case HeraldFieldConfig::FIELD_AFFECTED_PACKAGE_OWNER:
case HeraldFieldConfig::FIELD_NEED_AUDIT_FOR_PACKAGE:
return array_select_keys(
$map,
array(
self::CONDITION_INCLUDE_ANY,
self::CONDITION_INCLUDE_NONE,
));
case HeraldFieldConfig::FIELD_DIFFERENTIAL_REVISION:
return array_select_keys(
$map,
array(
self::CONDITION_EXISTS,
self::CONDITION_NOT_EXISTS,
));
default:
throw new Exception("Unknown field type '{$field}'.");
}
}
}
diff --git a/src/applications/herald/config/contenttype/HeraldContentTypeConfig.php b/src/applications/herald/config/contenttype/HeraldContentTypeConfig.php
index 938d5ffb3a..3def39c2b4 100644
--- a/src/applications/herald/config/contenttype/HeraldContentTypeConfig.php
+++ b/src/applications/herald/config/contenttype/HeraldContentTypeConfig.php
@@ -1,37 +1,37 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldContentTypeConfig {
+final class HeraldContentTypeConfig {
const CONTENT_TYPE_DIFFERENTIAL = 'differential';
const CONTENT_TYPE_COMMIT = 'commit';
const CONTENT_TYPE_MERGE = 'merge';
const CONTENT_TYPE_OWNERS = 'owners';
public static function getContentTypeMap() {
static $map = array(
self::CONTENT_TYPE_DIFFERENTIAL => 'Differential Revisions',
self::CONTENT_TYPE_COMMIT => 'Commits',
/* TODO: Deal with this
self::CONTENT_TYPE_MERGE => 'Merge Requests',
self::CONTENT_TYPE_OWNERS => 'Owners Changes',
*/
);
return $map;
}
}
diff --git a/src/applications/herald/config/field/HeraldFieldConfig.php b/src/applications/herald/config/field/HeraldFieldConfig.php
index e55200b633..8f2abb5b99 100644
--- a/src/applications/herald/config/field/HeraldFieldConfig.php
+++ b/src/applications/herald/config/field/HeraldFieldConfig.php
@@ -1,134 +1,134 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldFieldConfig {
+final class HeraldFieldConfig {
const FIELD_TITLE = 'title';
const FIELD_BODY = 'body';
const FIELD_AUTHOR = 'author';
const FIELD_REVIEWER = 'reviewer';
const FIELD_REVIEWERS = 'reviewers';
const FIELD_CC = 'cc';
const FIELD_TAGS = 'tags';
const FIELD_DIFF_FILE = 'diff-file';
const FIELD_DIFF_CONTENT = 'diff-content';
const FIELD_REPOSITORY = 'repository';
const FIELD_RULE = 'rule';
const FIELD_AFFECTED_PACKAGE = 'affected-package';
const FIELD_AFFECTED_PACKAGE_OWNER = 'affected-package-owner';
const FIELD_NEED_AUDIT_FOR_PACKAGE = 'need-audit-for-package';
const FIELD_DIFFERENTIAL_REVISION = 'differential-revision';
const FIELD_DIFFERENTIAL_REVIEWERS = 'differential-reviewers';
const FIELD_DIFFERENTIAL_CCS = 'differential-ccs';
const FIELD_MERGE_REQUESTER = 'merge-requester';
public static function getFieldMap() {
static $map = array(
self::FIELD_TITLE => 'Title',
self::FIELD_BODY => 'Body',
self::FIELD_AUTHOR => 'Author',
self::FIELD_REVIEWER => 'Reviewer',
self::FIELD_REVIEWERS => 'Reviewers',
self::FIELD_CC => 'CCs',
self::FIELD_TAGS => 'Tags',
self::FIELD_DIFF_FILE => 'Any changed filename',
self::FIELD_DIFF_CONTENT => 'Any changed file content',
self::FIELD_REPOSITORY => 'Repository',
self::FIELD_RULE => 'Another Herald rule',
self::FIELD_AFFECTED_PACKAGE => 'Any affected package',
self::FIELD_AFFECTED_PACKAGE_OWNER => "Any affected package's owner",
self::FIELD_NEED_AUDIT_FOR_PACKAGE =>
'Affected packages that need audit',
self::FIELD_DIFFERENTIAL_REVISION => 'Differential revision',
self::FIELD_DIFFERENTIAL_REVIEWERS => 'Differential reviewers',
self::FIELD_DIFFERENTIAL_CCS => 'Differential CCs',
self::FIELD_MERGE_REQUESTER => 'Merge requester'
);
return $map;
}
public static function getFieldMapForContentType($type) {
$map = self::getFieldMap();
switch ($type) {
case HeraldContentTypeConfig::CONTENT_TYPE_DIFFERENTIAL:
return array_select_keys(
$map,
array(
self::FIELD_TITLE,
self::FIELD_BODY,
self::FIELD_AUTHOR,
self::FIELD_REVIEWERS,
self::FIELD_CC,
self::FIELD_REPOSITORY,
self::FIELD_DIFF_FILE,
self::FIELD_DIFF_CONTENT,
self::FIELD_RULE,
self::FIELD_AFFECTED_PACKAGE,
self::FIELD_AFFECTED_PACKAGE_OWNER,
));
case HeraldContentTypeConfig::CONTENT_TYPE_COMMIT:
return array_select_keys(
$map,
array(
self::FIELD_BODY,
self::FIELD_AUTHOR,
self::FIELD_REVIEWER,
self::FIELD_REPOSITORY,
self::FIELD_DIFF_FILE,
self::FIELD_DIFF_CONTENT,
self::FIELD_RULE,
self::FIELD_AFFECTED_PACKAGE,
self::FIELD_AFFECTED_PACKAGE_OWNER,
self::FIELD_NEED_AUDIT_FOR_PACKAGE,
self::FIELD_DIFFERENTIAL_REVISION,
self::FIELD_DIFFERENTIAL_REVIEWERS,
self::FIELD_DIFFERENTIAL_CCS,
));
case HeraldContentTypeConfig::CONTENT_TYPE_MERGE:
return array_select_keys(
$map,
array(
self::FIELD_BODY,
self::FIELD_AUTHOR,
self::FIELD_REVIEWER,
self::FIELD_REPOSITORY,
self::FIELD_DIFF_FILE,
self::FIELD_DIFF_CONTENT,
self::FIELD_RULE,
self::FIELD_AFFECTED_PACKAGE,
self::FIELD_AFFECTED_PACKAGE_OWNER,
self::FIELD_DIFFERENTIAL_REVISION,
self::FIELD_DIFFERENTIAL_REVIEWERS,
self::FIELD_DIFFERENTIAL_CCS,
self::FIELD_MERGE_REQUESTER,
));
case HeraldContentTypeConfig::CONTENT_TYPE_OWNERS:
return array_select_keys(
$map,
array(
self::FIELD_AFFECTED_PACKAGE,
self::FIELD_AFFECTED_PACKAGE_OWNER,
));
default:
throw new Exception("Unknown content type.");
}
}
}
diff --git a/src/applications/herald/config/repetitionpolicy/HeraldRepetitionPolicyConfig.php b/src/applications/herald/config/repetitionpolicy/HeraldRepetitionPolicyConfig.php
index 1c37c906a7..5d241d9a6e 100644
--- a/src/applications/herald/config/repetitionpolicy/HeraldRepetitionPolicyConfig.php
+++ b/src/applications/herald/config/repetitionpolicy/HeraldRepetitionPolicyConfig.php
@@ -1,64 +1,64 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldRepetitionPolicyConfig {
+final class HeraldRepetitionPolicyConfig {
const FIRST = 'first'; // only execute the first time (no repeating)
const EVERY = 'every'; // repeat every time
private static $policyIntMap = array(
self::FIRST => 0,
self::EVERY => 1,
);
private static $policyMap = array(
self::FIRST => 'only the first time',
self::EVERY => 'every time',
);
public static function getMap() {
return self::$policyMap;
}
public static function getMapForContentType($type) {
switch ($type) {
case HeraldContentTypeConfig::CONTENT_TYPE_DIFFERENTIAL:
return array_select_keys(
self::$policyMap,
array(
self::EVERY,
self::FIRST,
));
case HeraldContentTypeConfig::CONTENT_TYPE_COMMIT:
case HeraldContentTypeConfig::CONTENT_TYPE_MERGE:
case HeraldContentTypeConfig::CONTENT_TYPE_OWNERS:
return array();
default:
throw new Exception("Unknown content type '{$type}'.");
}
}
public static function toInt($str) {
return idx(self::$policyIntMap, $str, self::$policyIntMap[self::EVERY]);
}
public static function toString($int) {
return idx(array_flip(self::$policyIntMap), $int, self::EVERY);
}
}
diff --git a/src/applications/herald/config/ruletype/HeraldRuleTypeConfig.php b/src/applications/herald/config/ruletype/HeraldRuleTypeConfig.php
index 143f29c031..9e5091d164 100644
--- a/src/applications/herald/config/ruletype/HeraldRuleTypeConfig.php
+++ b/src/applications/herald/config/ruletype/HeraldRuleTypeConfig.php
@@ -1,31 +1,31 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldRuleTypeConfig {
+final class HeraldRuleTypeConfig {
const RULE_TYPE_GLOBAL = 'global';
const RULE_TYPE_PERSONAL = 'personal';
public static function getRuleTypeMap() {
static $map = array(
self::RULE_TYPE_GLOBAL => 'Global',
self::RULE_TYPE_PERSONAL => 'Personal',
);
return $map;
}
}
diff --git a/src/applications/herald/config/valuetype/HeraldValueTypeConfig.php b/src/applications/herald/config/valuetype/HeraldValueTypeConfig.php
index af41c1a123..86fbcfc9b6 100644
--- a/src/applications/herald/config/valuetype/HeraldValueTypeConfig.php
+++ b/src/applications/herald/config/valuetype/HeraldValueTypeConfig.php
@@ -1,99 +1,99 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldValueTypeConfig {
+final class HeraldValueTypeConfig {
const VALUE_TEXT = 'text';
const VALUE_NONE = 'none';
const VALUE_EMAIL = 'email';
const VALUE_USER = 'user';
const VALUE_TAG = 'tag';
const VALUE_RULE = 'rule';
const VALUE_REPOSITORY = 'repository';
const VALUE_OWNERS_PACKAGE = 'package';
const VALUE_PROJECT = 'project';
public static function getValueTypeForFieldAndCondition($field, $condition) {
switch ($condition) {
case HeraldConditionConfig::CONDITION_CONTAINS:
case HeraldConditionConfig::CONDITION_NOT_CONTAINS:
case HeraldConditionConfig::CONDITION_IS:
case HeraldConditionConfig::CONDITION_IS_NOT:
case HeraldConditionConfig::CONDITION_REGEXP:
case HeraldConditionConfig::CONDITION_REGEXP_PAIR:
return self::VALUE_TEXT;
case HeraldConditionConfig::CONDITION_IS_ANY:
case HeraldConditionConfig::CONDITION_IS_NOT_ANY:
switch ($field) {
case HeraldFieldConfig::FIELD_REPOSITORY:
return self::VALUE_REPOSITORY;
default:
return self::VALUE_USER;
}
break;
case HeraldConditionConfig::CONDITION_INCLUDE_ALL:
case HeraldConditionConfig::CONDITION_INCLUDE_ANY:
case HeraldConditionConfig::CONDITION_INCLUDE_NONE:
switch ($field) {
case HeraldFieldConfig::FIELD_REPOSITORY:
return self::VALUE_REPOSITORY;
case HeraldFieldConfig::FIELD_CC:
case HeraldFieldConfig::FIELD_DIFFERENTIAL_CCS:
return self::VALUE_EMAIL;
case HeraldFieldConfig::FIELD_TAGS:
return self::VALUE_TAG;
case HeraldFieldConfig::FIELD_AFFECTED_PACKAGE:
case HeraldFieldConfig::FIELD_NEED_AUDIT_FOR_PACKAGE:
return self::VALUE_OWNERS_PACKAGE;
default:
return self::VALUE_USER;
}
break;
case HeraldConditionConfig::CONDITION_IS_ME:
case HeraldConditionConfig::CONDITION_IS_NOT_ME:
case HeraldConditionConfig::CONDITION_EXISTS:
case HeraldConditionConfig::CONDITION_NOT_EXISTS:
return self::VALUE_NONE;
case HeraldConditionConfig::CONDITION_RULE:
case HeraldConditionConfig::CONDITION_NOT_RULE:
return self::VALUE_RULE;
default:
throw new Exception("Unknown condition.");
}
}
public static function getValueTypeForAction($action, $rule_type) {
// users can't change targets for personal rule actions
if ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) {
return self::VALUE_NONE;
}
switch ($action) {
case HeraldActionConfig::ACTION_ADD_CC:
case HeraldActionConfig::ACTION_REMOVE_CC:
case HeraldActionConfig::ACTION_EMAIL:
return self::VALUE_EMAIL;
case HeraldActionConfig::ACTION_NOTHING:
return self::VALUE_NONE;
case HeraldActionConfig::ACTION_AUDIT:
return self::VALUE_PROJECT;
default:
throw new Exception("Unknown action '{$action}'.");
}
}
}
diff --git a/src/applications/herald/engine/effect/HeraldEffect.php b/src/applications/herald/engine/effect/HeraldEffect.php
index 510e5f3b8a..a00617774a 100644
--- a/src/applications/herald/engine/effect/HeraldEffect.php
+++ b/src/applications/herald/engine/effect/HeraldEffect.php
@@ -1,85 +1,85 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldEffect {
+final class HeraldEffect {
protected $objectPHID;
protected $action;
protected $target;
protected $ruleID;
protected $effector;
protected $reason;
public function setObjectPHID($object_phid) {
$this->objectPHID = $object_phid;
return $this;
}
public function getObjectPHID() {
return $this->objectPHID;
}
public function setAction($action) {
$this->action = $action;
return $this;
}
public function getAction() {
return $this->action;
}
public function setTarget($target) {
$this->target = $target;
return $this;
}
public function getTarget() {
return $this->target;
}
public function setRuleID($rule_id) {
$this->ruleID = $rule_id;
return $this;
}
public function getRuleID() {
return $this->ruleID;
}
public function setEffector($effector) {
$this->effector = $effector;
return $this;
}
public function getEffector() {
return $this->effector;
}
public function setReason($reason) {
$this->reason = $reason;
return $this;
}
public function getReason() {
return $this->reason;
}
}
diff --git a/src/applications/herald/engine/engine/HeraldEngine.php b/src/applications/herald/engine/engine/HeraldEngine.php
index 9547a4e486..a9e4f6c3af 100644
--- a/src/applications/herald/engine/engine/HeraldEngine.php
+++ b/src/applications/herald/engine/engine/HeraldEngine.php
@@ -1,518 +1,518 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldEngine {
+final class HeraldEngine {
protected $rules = array();
protected $results = array();
protected $stack = array();
protected $activeRule = null;
protected $fieldCache = array();
protected $object = null;
public static function loadAndApplyRules(HeraldObjectAdapter $object) {
$content_type = $object->getHeraldTypeName();
$rules = HeraldRule::loadAllByContentTypeWithFullData(
$content_type,
$object->getPHID());
$engine = new HeraldEngine();
$effects = $engine->applyRules($rules, $object);
$engine->applyEffects($effects, $object, $rules);
return $engine->getTranscript();
}
public function applyRules(array $rules, HeraldObjectAdapter $object) {
$t_start = microtime(true);
$rules = mpull($rules, null, 'getID');
$this->transcript = new HeraldTranscript();
$this->transcript->setObjectPHID((string)$object->getPHID());
$this->fieldCache = array();
$this->results = array();
$this->rules = $rules;
$this->object = $object;
$effects = array();
foreach ($rules as $id => $rule) {
$this->stack = array();
try {
if (($rule->getRepetitionPolicy() ==
HeraldRepetitionPolicyConfig::FIRST) &&
$rule->getRuleApplied($object->getPHID())) {
// This rule is only supposed to be applied a single time, and it's
// aleady been applied, so this is an automatic failure.
$xscript = id(new HeraldRuleTranscript())
->setRuleID($id)
->setResult(false)
->setRuleName($rule->getName())
->setRuleOwner($rule->getAuthorPHID())
->setReason(
"This rule is only supposed to be repeated a single time, ".
"and it has already been applied."
);
$this->transcript->addRuleTranscript($xscript);
$rule_matches = false;
} else {
$rule_matches = $this->doesRuleMatch($rule, $object);
}
} catch (HeraldRecursiveConditionsException $ex) {
$names = array();
foreach ($this->stack as $rule_id => $ignored) {
$names[] = '"'.$rules[$rule_id]->getName().'"';
}
$names = implode(', ', $names);
foreach ($this->stack as $rule_id => $ignored) {
$xscript = new HeraldRuleTranscript();
$xscript->setRuleID($rule_id);
$xscript->setResult(false);
$xscript->setReason(
"Rules {$names} are recursively dependent upon one another! ".
"Don't do this! You have formed an unresolvable cycle in the ".
"dependency graph!");
$xscript->setRuleName($rules[$rule_id]->getName());
$xscript->setRuleOwner($rules[$rule_id]->getAuthorPHID());
$this->transcript->addRuleTranscript($xscript);
}
$rule_matches = false;
}
$this->results[$id] = $rule_matches;
if ($rule_matches) {
foreach ($this->getRuleEffects($rule, $object) as $effect) {
$effects[] = $effect;
}
}
}
$object_transcript = new HeraldObjectTranscript();
$object_transcript->setPHID($object->getPHID());
$object_transcript->setName($object->getHeraldName());
$object_transcript->setType($object->getHeraldTypeName());
$object_transcript->setFields($this->fieldCache);
$this->transcript->setObjectTranscript($object_transcript);
$t_end = microtime(true);
$this->transcript->setDuration($t_end - $t_start);
return $effects;
}
public function applyEffects(
array $effects,
HeraldObjectAdapter $object,
array $rules) {
$this->transcript->setDryRun($object instanceof HeraldDryRunAdapter);
$xscripts = $object->applyHeraldEffects($effects);
foreach ($xscripts as $apply_xscript) {
if (!($apply_xscript instanceof HeraldApplyTranscript)) {
throw new Exception(
"Heraldable must return HeraldApplyTranscripts from ".
"applyHeraldEffect().");
}
$this->transcript->addApplyTranscript($apply_xscript);
}
if (!$this->transcript->getDryRun()) {
$rules = mpull($rules, null, 'getID');
$applied_ids = array();
$first_policy = HeraldRepetitionPolicyConfig::toInt(
HeraldRepetitionPolicyConfig::FIRST);
// Mark all the rules that have had their effects applied as having been
// executed for the current object.
$rule_ids = mpull($xscripts, 'getRuleID');
foreach ($rule_ids as $rule_id) {
if (!$rule_id) {
// Some apply transcripts are purely informational and not associated
// with a rule, e.g. carryover emails from earlier revisions.
continue;
}
$rule = idx($rules, $rule_id);
if (!$rule) {
continue;
}
if ($rule->getRepetitionPolicy() == $first_policy) {
$applied_ids[] = $rule_id;
}
}
if ($applied_ids) {
$conn_w = id(new HeraldRule())->establishConnection('w');
$sql = array();
foreach ($applied_ids as $id) {
$sql[] = qsprintf(
$conn_w,
'(%s, %d)',
$object->getPHID(),
$id);
}
queryfx(
$conn_w,
'INSERT IGNORE INTO %T (phid, ruleID) VALUES %Q',
HeraldRule::TABLE_RULE_APPLIED,
implode(', ', $sql));
}
}
}
public function getTranscript() {
$this->transcript->save();
return $this->transcript;
}
protected function doesRuleMatch(
HeraldRule $rule,
HeraldObjectAdapter $object) {
$id = $rule->getID();
if (isset($this->results[$id])) {
// If we've already evaluated this rule because another rule depends
// on it, we don't need to reevaluate it.
return $this->results[$id];
}
if (isset($this->stack[$id])) {
// We've recursed, fail all of the rules on the stack. This happens when
// there's a dependency cycle with "Rule conditions match for rule ..."
// conditions.
foreach ($this->stack as $rule_id => $ignored) {
$this->results[$rule_id] = false;
}
throw new HeraldRecursiveConditionsException();
}
$this->stack[$id] = true;
$all = $rule->getMustMatchAll();
$conditions = $rule->getConditions();
$result = null;
$local_version = id(new HeraldRule())->getConfigVersion();
if ($rule->getConfigVersion() > $local_version) {
$reason = "Rule could not be processed, it was created with a newer ".
"version of Herald.";
$result = false;
} else if (!$conditions) {
$reason = "Rule failed automatically because it has no conditions.";
$result = false;
} else if ($rule->hasInvalidOwner()) {
$reason = "Rule failed automatically because its owner is invalid ".
"or disabled.";
$result = false;
} else {
foreach ($conditions as $condition) {
$match = $this->doesConditionMatch($rule, $condition, $object);
if (!$all && $match) {
$reason = "Any condition matched.";
$result = true;
break;
}
if ($all && !$match) {
$reason = "Not all conditions matched.";
$result = false;
break;
}
}
if ($result === null) {
if ($all) {
$reason = "All conditions matched.";
$result = true;
} else {
$reason = "No conditions matched.";
$result = false;
}
}
}
$rule_transcript = new HeraldRuleTranscript();
$rule_transcript->setRuleID($rule->getID());
$rule_transcript->setResult($result);
$rule_transcript->setReason($reason);
$rule_transcript->setRuleName($rule->getName());
$rule_transcript->setRuleOwner($rule->getAuthorPHID());
$this->transcript->addRuleTranscript($rule_transcript);
return $result;
}
protected function doesConditionMatch(
HeraldRule $rule,
HeraldCondition $condition,
HeraldObjectAdapter $object) {
$object_value = $this->getConditionObjectValue($condition, $object);
$test_value = $condition->getValue();
$cond = $condition->getFieldCondition();
$transcript = new HeraldConditionTranscript();
$transcript->setRuleID($rule->getID());
$transcript->setConditionID($condition->getID());
$transcript->setFieldName($condition->getFieldName());
$transcript->setCondition($cond);
$transcript->setTestValue($test_value);
$result = null;
switch ($cond) {
case HeraldConditionConfig::CONDITION_CONTAINS:
// "Contains" can take an array of strings, as in "Any changed
// filename" for diffs.
foreach ((array)$object_value as $value) {
$result = (stripos($value, $test_value) !== false);
if ($result) {
break;
}
}
break;
case HeraldConditionConfig::CONDITION_NOT_CONTAINS:
$result = (stripos($object_value, $test_value) === false);
break;
case HeraldConditionConfig::CONDITION_IS:
$result = ($object_value == $test_value);
break;
case HeraldConditionConfig::CONDITION_IS_NOT:
$result = ($object_value != $test_value);
break;
case HeraldConditionConfig::CONDITION_IS_ME:
$result = ($object_value == $rule->getAuthorPHID());
break;
case HeraldConditionConfig::CONDITION_IS_NOT_ME:
$result = ($object_value != $rule->getAuthorPHID());
break;
case HeraldConditionConfig::CONDITION_IS_ANY:
$test_value = array_flip($test_value);
$result = isset($test_value[$object_value]);
break;
case HeraldConditionConfig::CONDITION_IS_NOT_ANY:
$test_value = array_flip($test_value);
$result = !isset($test_value[$object_value]);
break;
case HeraldConditionConfig::CONDITION_INCLUDE_ALL:
if (!is_array($object_value)) {
$transcript->setNote('Object produced bad value!');
$result = false;
} else {
$have = array_select_keys(array_flip($object_value),
$test_value);
$result = (count($have) == count($test_value));
}
break;
case HeraldConditionConfig::CONDITION_INCLUDE_ANY:
$result = (bool)array_select_keys(array_flip($object_value),
$test_value);
break;
case HeraldConditionConfig::CONDITION_INCLUDE_NONE:
$result = !array_select_keys(array_flip($object_value),
$test_value);
break;
case HeraldConditionConfig::CONDITION_EXISTS:
$result = (bool)$object_value;
break;
case HeraldConditionConfig::CONDITION_NOT_EXISTS:
$result = !$object_value;
break;
case HeraldConditionConfig::CONDITION_REGEXP:
foreach ((array)$object_value as $value) {
$result = @preg_match($test_value, $value);
if ($result === false) {
$transcript->setNote(
"Regular expression is not valid!");
break;
}
if ($result) {
break;
}
}
$result = (bool)$result;
break;
case HeraldConditionConfig::CONDITION_REGEXP_PAIR:
// Match a JSON-encoded pair of regular expressions against a
// dictionary. The first regexp must match the dictionary key, and the
// second regexp must match the dictionary value. If any key/value pair
// in the dictionary matches both regexps, the condition is satisfied.
$regexp_pair = json_decode($test_value, true);
if (!is_array($regexp_pair)) {
$result = false;
$transcript->setNote("Regular expression pair is not valid JSON!");
break;
}
if (count($regexp_pair) != 2) {
$result = false;
$transcript->setNote("Regular expression pair is not a pair!");
break;
}
$key_regexp = array_shift($regexp_pair);
$value_regexp = array_shift($regexp_pair);
foreach ((array)$object_value as $key => $value) {
$key_matches = @preg_match($key_regexp, $key);
if ($key_matches === false) {
$result = false;
$transcript->setNote("First regular expression is invalid!");
break 2;
}
if ($key_matches) {
$value_matches = @preg_match($value_regexp, $value);
if ($value_matches === false) {
$result = false;
$transcript->setNote("Second regular expression is invalid!");
break 2;
}
if ($value_matches) {
$result = true;
break 2;
}
}
}
$result = false;
break;
case HeraldConditionConfig::CONDITION_RULE:
case HeraldConditionConfig::CONDITION_NOT_RULE:
$rule = idx($this->rules, $test_value);
if (!$rule) {
$transcript->setNote(
"Condition references a rule which does not exist!");
$result = false;
} else {
$is_not = ($cond == HeraldConditionConfig::CONDITION_NOT_RULE);
$result = $this->doesRuleMatch($rule, $object);
if ($is_not) {
$result = !$result;
}
}
break;
default:
throw new HeraldInvalidConditionException(
"Unknown condition '{$cond}'.");
}
$transcript->setResult($result);
$this->transcript->addConditionTranscript($transcript);
return $result;
}
protected function getConditionObjectValue(
HeraldCondition $condition,
HeraldObjectAdapter $object) {
$field = $condition->getFieldName();
return $this->getObjectFieldValue($field);
}
public function getObjectFieldValue($field) {
if (isset($this->fieldCache[$field])) {
return $this->fieldCache[$field];
}
$result = null;
switch ($field) {
case HeraldFieldConfig::FIELD_RULE:
$result = null;
break;
case HeraldFieldConfig::FIELD_TITLE:
case HeraldFieldConfig::FIELD_BODY:
case HeraldFieldConfig::FIELD_DIFF_FILE:
case HeraldFieldConfig::FIELD_DIFF_CONTENT:
// TODO: Type should be string.
$result = $this->object->getHeraldField($field);
break;
case HeraldFieldConfig::FIELD_AUTHOR:
case HeraldFieldConfig::FIELD_REPOSITORY:
case HeraldFieldConfig::FIELD_MERGE_REQUESTER:
// TODO: Type should be PHID.
$result = $this->object->getHeraldField($field);
break;
case HeraldFieldConfig::FIELD_TAGS:
case HeraldFieldConfig::FIELD_REVIEWER:
case HeraldFieldConfig::FIELD_REVIEWERS:
case HeraldFieldConfig::FIELD_CC:
case HeraldFieldConfig::FIELD_DIFFERENTIAL_REVIEWERS:
case HeraldFieldConfig::FIELD_DIFFERENTIAL_CCS:
// TODO: Type should be list.
$result = $this->object->getHeraldField($field);
break;
case HeraldFieldConfig::FIELD_AFFECTED_PACKAGE:
case HeraldFieldConfig::FIELD_AFFECTED_PACKAGE_OWNER:
case HeraldFieldConfig::FIELD_NEED_AUDIT_FOR_PACKAGE:
$result = $this->object->getHeraldField($field);
if (!is_array($result)) {
throw new HeraldInvalidFieldException(
"Value of field type {$field} is not an array!");
}
break;
case HeraldFieldConfig::FIELD_DIFFERENTIAL_REVISION:
// TODO: Type should be boolean I guess.
$result = $this->object->getHeraldField($field);
break;
default:
throw new HeraldInvalidConditionException(
"Unknown field type '{$field}'!");
}
$this->fieldCache[$field] = $result;
return $result;
}
protected function getRuleEffects(
HeraldRule $rule,
HeraldObjectAdapter $object) {
$effects = array();
foreach ($rule->getActions() as $action) {
$effect = new HeraldEffect();
$effect->setObjectPHID($object->getPHID());
$effect->setAction($action->getAction());
$effect->setTarget($action->getTarget());
$effect->setRuleID($rule->getID());
$name = $rule->getName();
$id = $rule->getID();
$effect->setReason(
'Conditions were met for Herald rule "'.$name.'" (#'.$id.').');
$effects[] = $effect;
}
return $effects;
}
}
diff --git a/src/applications/herald/engine/engine/exception/HeraldInvalidConditionException.php b/src/applications/herald/engine/engine/exception/HeraldInvalidConditionException.php
index 44f316109e..4c5b6f0b6b 100644
--- a/src/applications/herald/engine/engine/exception/HeraldInvalidConditionException.php
+++ b/src/applications/herald/engine/engine/exception/HeraldInvalidConditionException.php
@@ -1,19 +1,19 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldInvalidConditionException extends Exception {}
+final class HeraldInvalidConditionException extends Exception {}
diff --git a/src/applications/herald/engine/engine/exception/HeraldInvalidFieldException.php b/src/applications/herald/engine/engine/exception/HeraldInvalidFieldException.php
index 636084e90f..9ef352f46b 100644
--- a/src/applications/herald/engine/engine/exception/HeraldInvalidFieldException.php
+++ b/src/applications/herald/engine/engine/exception/HeraldInvalidFieldException.php
@@ -1,19 +1,19 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldInvalidFieldException extends Exception {}
+final class HeraldInvalidFieldException extends Exception {}
diff --git a/src/applications/herald/engine/engine/exception/HeraldRecursiveConditionsException.php b/src/applications/herald/engine/engine/exception/HeraldRecursiveConditionsException.php
index 20c732da8d..d65d68a4ae 100644
--- a/src/applications/herald/engine/engine/exception/HeraldRecursiveConditionsException.php
+++ b/src/applications/herald/engine/engine/exception/HeraldRecursiveConditionsException.php
@@ -1,19 +1,19 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldRecursiveConditionsException extends Exception {}
+final class HeraldRecursiveConditionsException extends Exception {}
diff --git a/src/applications/herald/storage/action/HeraldAction.php b/src/applications/herald/storage/action/HeraldAction.php
index ed63a2216c..8215be08db 100644
--- a/src/applications/herald/storage/action/HeraldAction.php
+++ b/src/applications/herald/storage/action/HeraldAction.php
@@ -1,36 +1,36 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldAction extends HeraldDAO {
+final class HeraldAction extends HeraldDAO {
protected $ruleID;
protected $action;
protected $target;
protected function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'target' => self::SERIALIZATION_JSON,
),
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
}
diff --git a/src/applications/herald/storage/condition/HeraldCondition.php b/src/applications/herald/storage/condition/HeraldCondition.php
index 3a60d29124..98c8d93c36 100644
--- a/src/applications/herald/storage/condition/HeraldCondition.php
+++ b/src/applications/herald/storage/condition/HeraldCondition.php
@@ -1,36 +1,36 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldCondition extends HeraldDAO {
+final class HeraldCondition extends HeraldDAO {
protected $ruleID;
protected $fieldName;
protected $fieldCondition;
protected $value;
protected function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'value' => self::SERIALIZATION_JSON,
),
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
}
diff --git a/src/applications/herald/storage/edithistory/HeraldRuleEdit.php b/src/applications/herald/storage/edithistory/HeraldRuleEdit.php
index 7e1b8746c1..b7abeb3a1c 100644
--- a/src/applications/herald/storage/edithistory/HeraldRuleEdit.php
+++ b/src/applications/herald/storage/edithistory/HeraldRuleEdit.php
@@ -1,24 +1,24 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldRuleEdit extends HeraldDAO {
+final class HeraldRuleEdit extends HeraldDAO {
protected $editorPHID;
protected $ruleID;
}
diff --git a/src/applications/herald/storage/rule/HeraldRule.php b/src/applications/herald/storage/rule/HeraldRule.php
index 0756ca2dbc..a01769d904 100644
--- a/src/applications/herald/storage/rule/HeraldRule.php
+++ b/src/applications/herald/storage/rule/HeraldRule.php
@@ -1,241 +1,241 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldRule extends HeraldDAO {
+final class HeraldRule extends HeraldDAO {
const TABLE_RULE_APPLIED = 'herald_ruleapplied';
protected $name;
protected $authorPHID;
protected $contentType;
protected $mustMatchAll;
protected $repetitionPolicy;
protected $ruleType;
protected $configVersion = 9;
private $ruleApplied = array(); // phids for which this rule has been applied
private $invalidOwner = false;
public static function loadAllByContentTypeWithFullData(
$content_type,
$object_phid) {
$rules = id(new HeraldRule())->loadAllWhere(
'contentType = %s',
$content_type);
if (!$rules) {
return array();
}
self::flagDisabledUserRules($rules);
$rule_ids = mpull($rules, 'getID');
$conditions = id(new HeraldCondition())->loadAllWhere(
'ruleID in (%Ld)',
$rule_ids);
$actions = id(new HeraldAction())->loadAllWhere(
'ruleID in (%Ld)',
$rule_ids);
$applied = queryfx_all(
id(new HeraldRule())->establishConnection('r'),
'SELECT * FROM %T WHERE phid = %s',
self::TABLE_RULE_APPLIED,
$object_phid);
$applied = ipull($applied, null, 'ruleID');
$conditions = mgroup($conditions, 'getRuleID');
$actions = mgroup($actions, 'getRuleID');
$applied = igroup($applied, 'ruleID');
foreach ($rules as $rule) {
$rule->setRuleApplied($object_phid, isset($applied[$rule->getID()]));
$rule->attachConditions(idx($conditions, $rule->getID(), array()));
$rule->attachActions(idx($actions, $rule->getID(), array()));
}
return $rules;
}
private static function flagDisabledUserRules(array $rules) {
$users = array();
foreach ($rules as $rule) {
if ($rule->getRuleType() != HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) {
continue;
}
$users[$rule->getAuthorPHID()] = true;
}
$handles = id(new PhabricatorObjectHandleData(array_keys($users)))
->loadHandles();
foreach ($rules as $key => $rule) {
if ($rule->getRuleType() != HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) {
continue;
}
$handle = $handles[$rule->getAuthorPHID()];
if (!$handle->isComplete() || $handle->isDisabled()) {
$rule->invalidOwner = true;
}
}
}
public function getRuleApplied($phid) {
if (idx($this->ruleApplied, $phid) === null) {
throw new Exception("Call setRuleApplied() before getRuleApplied()!");
}
return $this->ruleApplied[$phid];
}
public function setRuleApplied($phid, $applied) {
$this->ruleApplied[$phid] = $applied;
return $this;
}
public function loadConditions() {
if (!$this->getID()) {
return array();
}
return id(new HeraldCondition())->loadAllWhere(
'ruleID = %d',
$this->getID());
}
public function attachConditions(array $conditions) {
$this->conditions = $conditions;
return $this;
}
public function getConditions() {
// TODO: validate conditions have been attached.
return $this->conditions;
}
public function loadActions() {
if (!$this->getID()) {
return array();
}
return id(new HeraldAction())->loadAllWhere(
'ruleID = %d',
$this->getID());
}
public function attachActions(array $actions) {
// TODO: validate actions have been attached.
$this->actions = $actions;
return $this;
}
public function getActions() {
return $this->actions;
}
public function loadEdits() {
if (!$this->getID()) {
return array();
}
$edits = id(new HeraldRuleEdit())->loadAllWhere(
'ruleID = %d ORDER BY dateCreated DESC',
$this->getID());
return $edits;
}
public function attachEdits(array $edits) {
$this->edits = $edits;
return $this;
}
public function getEdits() {
if ($this->edits === null) {
throw new Exception("Attach edits before accessing them!");
}
return $this->edits;
}
public function saveEdit($editor_phid) {
$edit = new HeraldRuleEdit();
$edit->setRuleID($this->getID());
$edit->setEditorPHID($editor_phid);
$edit->save();
}
public function saveConditions(array $conditions) {
return $this->saveChildren(
id(new HeraldCondition())->getTableName(),
$conditions);
}
public function saveActions(array $actions) {
return $this->saveChildren(
id(new HeraldAction())->getTableName(),
$actions);
}
protected function saveChildren($table_name, array $children) {
if (!$this->getID()) {
throw new Exception("Save rule before saving children.");
}
foreach ($children as $child) {
$child->setRuleID($this->getID());
}
// TODO:
// $this->openTransaction();
queryfx(
$this->establishConnection('w'),
'DELETE FROM %T WHERE ruleID = %d',
$table_name,
$this->getID());
foreach ($children as $child) {
$child->save();
}
// $this->saveTransaction();
}
public function delete() {
// TODO:
// $this->openTransaction();
queryfx(
$this->establishConnection('w'),
'DELETE FROM %T WHERE ruleID = %d',
id(new HeraldCondition())->getTableName(),
$this->getID());
queryfx(
$this->establishConnection('w'),
'DELETE FROM %T WHERE ruleID = %d',
id(new HeraldAction())->getTableName(),
$this->getID());
parent::delete();
// $this->saveTransaction();
}
public function hasInvalidOwner() {
return $this->invalidOwner;
}
}
diff --git a/src/applications/herald/storage/transcript/apply/HeraldApplyTranscript.php b/src/applications/herald/storage/transcript/apply/HeraldApplyTranscript.php
index 8e3146241a..0069525cbf 100644
--- a/src/applications/herald/storage/transcript/apply/HeraldApplyTranscript.php
+++ b/src/applications/herald/storage/transcript/apply/HeraldApplyTranscript.php
@@ -1,80 +1,80 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldApplyTranscript extends HeraldDAO {
+final class HeraldApplyTranscript extends HeraldDAO {
protected $action;
protected $target;
protected $ruleID;
protected $effector;
protected $reason;
protected $applied;
protected $appliedReason;
public function __construct(
HeraldEffect $effect,
$applied,
$reason = null) {
$this->setAction($effect->getAction());
$this->setTarget($effect->getTarget());
$this->setRuleID($effect->getRuleID());
$this->setEffector($effect->getEffector());
$this->setReason($effect->getReason());
$this->setApplied($applied);
$this->setAppliedReason($reason);
}
public function getAction() {
return $this->action;
}
public function getTarget() {
return $this->target;
}
public function getRuleID() {
return $this->ruleID;
}
public function getEffector() {
return $this->effector;
}
public function getReason() {
return $this->reason;
}
public function getApplied() {
return $this->applied;
}
public function setAppliedReason($applied_reason) {
$this->appliedReason = $applied_reason;
return $this;
}
public function getAppliedReason() {
return $this->appliedReason;
}
}
diff --git a/src/applications/herald/storage/transcript/base/HeraldTranscript.php b/src/applications/herald/storage/transcript/base/HeraldTranscript.php
index 3706f770dd..1a94539fe7 100644
--- a/src/applications/herald/storage/transcript/base/HeraldTranscript.php
+++ b/src/applications/herald/storage/transcript/base/HeraldTranscript.php
@@ -1,185 +1,185 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldTranscript extends HeraldDAO {
+final class HeraldTranscript extends HeraldDAO {
protected $id;
protected $phid;
protected $objectTranscript;
protected $ruleTranscripts = array();
protected $conditionTranscripts = array();
protected $applyTranscripts = array();
protected $time;
protected $host;
protected $duration;
protected $objectPHID;
protected $dryRun;
const TABLE_SAVED_HEADER = 'herald_savedheader';
public function getXHeraldRulesHeader() {
$ids = array();
foreach ($this->applyTranscripts as $xscript) {
if ($xscript->getApplied()) {
if ($xscript->getRuleID()) {
$ids[] = $xscript->getRuleID();
}
}
}
if (!$ids) {
return 'none';
}
// A rule may have multiple effects, which will cause it to be listed
// multiple times.
$ids = array_unique($ids);
foreach ($ids as $k => $id) {
$ids[$k] = '<'.$id.'>';
}
return implode(', ', $ids);
}
public static function saveXHeraldRulesHeader($phid, $header) {
// Combine any existing header with the new header, listing all rules
// which have ever triggered for this object.
$header = self::combineXHeraldRulesHeaders(
self::loadXHeraldRulesHeader($phid),
$header);
queryfx(
id(new HeraldTranscript())->establishConnection('w'),
'INSERT INTO %T (phid, header) VALUES (%s, %s)
ON DUPLICATE KEY UPDATE header = VALUES(header)',
self::TABLE_SAVED_HEADER,
$phid,
$header);
return $header;
}
private static function combineXHeraldRulesHeaders($u, $v) {
$u = preg_split('/[, ]+/', $u);
$v = preg_split('/[, ]+/', $v);
$combined = array_unique(array_filter(array_merge($u, $v)));
return implode(', ', $combined);
}
public static function loadXHeraldRulesHeader($phid) {
$header = queryfx_one(
id(new HeraldTranscript())->establishConnection('r'),
'SELECT * FROM %T WHERE phid = %s',
self::TABLE_SAVED_HEADER,
$phid);
if ($header) {
return idx($header, 'header');
}
return null;
}
protected function getConfiguration() {
// Ugh. Too much of a mess to deal with.
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_SERIALIZATION => array(
'objectTranscript' => self::SERIALIZATION_PHP,
'ruleTranscripts' => self::SERIALIZATION_PHP,
'conditionTranscripts' => self::SERIALIZATION_PHP,
'applyTranscripts' => self::SERIALIZATION_PHP,
),
) + parent::getConfiguration();
}
public function __construct() {
$this->time = time();
$this->host = php_uname('n');
}
public function addApplyTranscript(HeraldApplyTranscript $transcript) {
$this->applyTranscripts[] = $transcript;
return $this;
}
public function getApplyTranscripts() {
return nonempty($this->applyTranscripts, array());
}
public function setDuration($duration) {
$this->duration = $duration;
return $this;
}
public function setObjectTranscript(HeraldObjectTranscript $transcript) {
$this->objectTranscript = $transcript;
return $this;
}
public function getObjectTranscript() {
return $this->objectTranscript;
}
public function addRuleTranscript(HeraldRuleTranscript $transcript) {
$this->ruleTranscripts[$transcript->getRuleID()] = $transcript;
return $this;
}
public function discardDetails() {
$this->applyTranscripts = null;
$this->ruleTranscripts = null;
$this->objectTranscript = null;
$this->conditionTranscripts = null;
}
public function getRuleTranscripts() {
return nonempty($this->ruleTranscripts, array());
}
public function addConditionTranscript(
HeraldConditionTranscript $transcript) {
$rule_id = $transcript->getRuleID();
$cond_id = $transcript->getConditionID();
$this->conditionTranscripts[$rule_id][$cond_id] = $transcript;
return $this;
}
public function getConditionTranscriptsForRule($rule_id) {
return idx($this->conditionTranscripts, $rule_id, array());
}
public function getMetadataMap() {
return array(
'Run At Epoch' => date('F jS, g:i:s A', $this->time),
'Run On Host' => $this->host,
'Run Duration' => (int)(1000 * $this->duration).' ms',
);
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID('HLXS');
}
}
diff --git a/src/applications/herald/storage/transcript/condition/HeraldConditionTranscript.php b/src/applications/herald/storage/transcript/condition/HeraldConditionTranscript.php
index 130edf22a5..ea85fd32c6 100644
--- a/src/applications/herald/storage/transcript/condition/HeraldConditionTranscript.php
+++ b/src/applications/herald/storage/transcript/condition/HeraldConditionTranscript.php
@@ -1,91 +1,91 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldConditionTranscript {
+final class HeraldConditionTranscript {
protected $ruleID;
protected $conditionID;
protected $fieldName;
protected $condition;
protected $testValue;
protected $note;
protected $result;
public function setRuleID($rule_id) {
$this->ruleID = $rule_id;
return $this;
}
public function getRuleID() {
return $this->ruleID;
}
public function setConditionID($condition_id) {
$this->conditionID = $condition_id;
return $this;
}
public function getConditionID() {
return $this->conditionID;
}
public function setFieldName($field_name) {
$this->fieldName = $field_name;
return $this;
}
public function getFieldName() {
return $this->fieldName;
}
public function setCondition($condition) {
$this->condition = $condition;
return $this;
}
public function getCondition() {
return $this->condition;
}
public function setTestValue($test_value) {
$this->testValue = $test_value;
return $this;
}
public function getTestValue() {
return $this->testValue;
}
public function setNote($note) {
$this->note = $note;
return $this;
}
public function getNote() {
return $this->note;
}
public function setResult($result) {
$this->result = $result;
return $this;
}
public function getResult() {
return $this->result;
}
}
diff --git a/src/applications/herald/storage/transcript/object/HeraldObjectTranscript.php b/src/applications/herald/storage/transcript/object/HeraldObjectTranscript.php
index 432921eefb..2289ec5f2c 100644
--- a/src/applications/herald/storage/transcript/object/HeraldObjectTranscript.php
+++ b/src/applications/herald/storage/transcript/object/HeraldObjectTranscript.php
@@ -1,61 +1,61 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldObjectTranscript {
+final class HeraldObjectTranscript {
protected $phid;
protected $type;
protected $name;
protected $fields;
public function setPHID($phid) {
$this->phid = $phid;
return $this;
}
public function getPHID() {
return $this->phid;
}
public function setType($type) {
$this->type = $type;
return $this;
}
public function getType() {
return $this->type;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function setFields(array $fields) {
$this->fields = $fields;
return $this;
}
public function getFields() {
return $this->fields;
}
}
diff --git a/src/applications/herald/storage/transcript/rule/HeraldRuleTranscript.php b/src/applications/herald/storage/transcript/rule/HeraldRuleTranscript.php
index 5728ac7657..3169aa71ca 100644
--- a/src/applications/herald/storage/transcript/rule/HeraldRuleTranscript.php
+++ b/src/applications/herald/storage/transcript/rule/HeraldRuleTranscript.php
@@ -1,72 +1,72 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldRuleTranscript {
+final class HeraldRuleTranscript {
protected $ruleID;
protected $result;
protected $reason;
protected $ruleName;
protected $ruleOwner;
public function setResult($result) {
$this->result = $result;
return $this;
}
public function getResult() {
return $this->result;
}
public function setReason($reason) {
$this->reason = $reason;
return $this;
}
public function getReason() {
return $this->reason;
}
public function setRuleID($rule_id) {
$this->ruleID = $rule_id;
return $this;
}
public function getRuleID() {
return $this->ruleID;
}
public function setRuleName($rule_name) {
$this->ruleName = $rule_name;
return $this;
}
public function getRuleName() {
return $this->ruleName;
}
public function setRuleOwner($rule_owner) {
$this->ruleOwner = $rule_owner;
return $this;
}
public function getRuleOwner() {
return $this->ruleOwner;
}
}
diff --git a/src/applications/maniphest/auxiliaryfield/typeexception/ManiphestAuxiliaryFieldTypeException.php b/src/applications/maniphest/auxiliaryfield/typeexception/ManiphestAuxiliaryFieldTypeException.php
index 160824e9ad..b148a65225 100644
--- a/src/applications/maniphest/auxiliaryfield/typeexception/ManiphestAuxiliaryFieldTypeException.php
+++ b/src/applications/maniphest/auxiliaryfield/typeexception/ManiphestAuxiliaryFieldTypeException.php
@@ -1,24 +1,24 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
-class ManiphestAuxiliaryFieldTypeException extends Exception {
+final class ManiphestAuxiliaryFieldTypeException extends Exception {
}
diff --git a/src/applications/maniphest/auxiliaryfield/validationexception/ManiphestAuxiliaryFieldValidationException.php b/src/applications/maniphest/auxiliaryfield/validationexception/ManiphestAuxiliaryFieldValidationException.php
index 83257261c7..13a20504d4 100644
--- a/src/applications/maniphest/auxiliaryfield/validationexception/ManiphestAuxiliaryFieldValidationException.php
+++ b/src/applications/maniphest/auxiliaryfield/validationexception/ManiphestAuxiliaryFieldValidationException.php
@@ -1,24 +1,24 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
-class ManiphestAuxiliaryFieldValidationException extends Exception {
+final class ManiphestAuxiliaryFieldValidationException extends Exception {
}
diff --git a/src/applications/maniphest/constants/action/ManiphestAction.php b/src/applications/maniphest/constants/action/ManiphestAction.php
index 356aa40767..a58c1e61c2 100644
--- a/src/applications/maniphest/constants/action/ManiphestAction.php
+++ b/src/applications/maniphest/constants/action/ManiphestAction.php
@@ -1,65 +1,65 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
-class ManiphestAction extends PhrictionConstants {
+final class ManiphestAction extends PhrictionConstants {
const ACTION_CREATE = 'create';
const ACTION_CLOSE = 'close';
const ACTION_UPDATE = 'update';
const ACTION_ASSIGN = 'assign';
public static function getActionPastTenseVerb($action) {
static $map = array(
self::ACTION_CREATE => 'created',
self::ACTION_CLOSE => 'closed',
self::ACTION_UPDATE => 'updated',
self::ACTION_ASSIGN => 'assigned',
);
return idx($map, $action, "brazenly {$action}'d");
}
/**
* If a group of transactions contain several actions, select the "strongest"
* action. For instance, a close is stronger than an update, because we want
* to render "User U closed task T" instead of "User U updated task T" when
* a user closes a task.
*/
public static function selectStrongestAction(array $actions) {
static $strengths = array(
self::ACTION_UPDATE => 0,
self::ACTION_ASSIGN => 1,
self::ACTION_CREATE => 2,
self::ACTION_CLOSE => 3,
);
$strongest = null;
$strength = -1;
foreach ($actions as $action) {
if ($strengths[$action] > $strength) {
$strength = $strengths[$action];
$strongest = $action;
}
}
return $strongest;
}
}
diff --git a/src/applications/maniphest/constants/base/ManiphestConstants.php b/src/applications/maniphest/constants/base/ManiphestConstants.php
index 7e12478621..e91bf08237 100644
--- a/src/applications/maniphest/constants/base/ManiphestConstants.php
+++ b/src/applications/maniphest/constants/base/ManiphestConstants.php
@@ -1,24 +1,24 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
-class ManiphestConstants {
+abstract class ManiphestConstants {
}
diff --git a/src/applications/maniphest/editor/transaction/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/transaction/ManiphestTransactionEditor.php
index 3b5d5602d3..daf6227a4e 100644
--- a/src/applications/maniphest/editor/transaction/ManiphestTransactionEditor.php
+++ b/src/applications/maniphest/editor/transaction/ManiphestTransactionEditor.php
@@ -1,388 +1,388 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
-class ManiphestTransactionEditor {
+final class ManiphestTransactionEditor {
private $parentMessageID;
private $auxiliaryFields = array();
public function setAuxiliaryFields(array $fields) {
$this->auxiliaryFields = $fields;
return $this;
}
public function setParentMessageID($parent_message_id) {
$this->parentMessageID = $parent_message_id;
return $this;
}
public function applyTransactions($task, array $transactions) {
$email_cc = $task->getCCPHIDs();
$email_to = array();
$email_to[] = $task->getOwnerPHID();
foreach ($transactions as $key => $transaction) {
$type = $transaction->getTransactionType();
$new = $transaction->getNewValue();
$email_to[] = $transaction->getAuthorPHID();
$value_is_phid_set = false;
switch ($type) {
case ManiphestTransactionType::TYPE_NONE:
$old = null;
break;
case ManiphestTransactionType::TYPE_STATUS:
$old = $task->getStatus();
break;
case ManiphestTransactionType::TYPE_OWNER:
$old = $task->getOwnerPHID();
break;
case ManiphestTransactionType::TYPE_CCS:
$old = $task->getCCPHIDs();
$value_is_phid_set = true;
break;
case ManiphestTransactionType::TYPE_PRIORITY:
$old = $task->getPriority();
break;
case ManiphestTransactionType::TYPE_ATTACH:
$old = $task->getAttached();
break;
case ManiphestTransactionType::TYPE_TITLE:
$old = $task->getTitle();
break;
case ManiphestTransactionType::TYPE_DESCRIPTION:
$old = $task->getDescription();
break;
case ManiphestTransactionType::TYPE_PROJECTS:
$old = $task->getProjectPHIDs();
$value_is_phid_set = true;
break;
case ManiphestTransactionType::TYPE_AUXILIARY:
$aux_key = $transaction->getMetadataValue('aux:key');
if (!$aux_key) {
throw new Exception(
"Expected 'aux:key' metadata on TYPE_AUXILIARY transaction.");
}
$old = $task->getAuxiliaryAttribute($aux_key);
break;
default:
throw new Exception('Unknown action type.');
}
$old_cmp = $old;
$new_cmp = $new;
if ($value_is_phid_set) {
// Normalize the old and new values if they are PHID sets so we don't
// get any no-op transactions where the values differ only by keys,
// order, duplicates, etc.
if (is_array($old)) {
$old = array_filter($old);
$old = array_unique($old);
sort($old);
$old = array_values($old);
$old_cmp = $old;
}
if (is_array($new)) {
$new = array_filter($new);
$new = array_unique($new);
$transaction->setNewValue($new);
$new_cmp = $new;
sort($new_cmp);
$new_cmp = array_values($new_cmp);
}
}
if (($old !== null) && ($old_cmp == $new_cmp)) {
if (count($transactions) > 1 && !$transaction->hasComments()) {
// If we have at least one other transaction and this one isn't
// doing anything and doesn't have any comments, just throw it
// away.
unset($transactions[$key]);
continue;
} else {
$transaction->setOldValue(null);
$transaction->setNewValue(null);
$transaction->setTransactionType(ManiphestTransactionType::TYPE_NONE);
}
} else {
switch ($type) {
case ManiphestTransactionType::TYPE_NONE:
break;
case ManiphestTransactionType::TYPE_STATUS:
$task->setStatus($new);
break;
case ManiphestTransactionType::TYPE_OWNER:
if ($new) {
$handles = id(new PhabricatorObjectHandleData(array($new)))
->loadHandles();
$task->setOwnerOrdering($handles[$new]->getName());
} else {
$task->setOwnerOrdering(null);
}
$task->setOwnerPHID($new);
break;
case ManiphestTransactionType::TYPE_CCS:
$task->setCCPHIDs($new);
break;
case ManiphestTransactionType::TYPE_PRIORITY:
$task->setPriority($new);
break;
case ManiphestTransactionType::TYPE_ATTACH:
$task->setAttached($new);
break;
case ManiphestTransactionType::TYPE_TITLE:
$task->setTitle($new);
break;
case ManiphestTransactionType::TYPE_DESCRIPTION:
$task->setDescription($new);
break;
case ManiphestTransactionType::TYPE_PROJECTS:
$task->setProjectPHIDs($new);
break;
case ManiphestTransactionType::TYPE_AUXILIARY:
$aux_key = $transaction->getMetadataValue('aux:key');
$task->setAuxiliaryAttribute($aux_key, $new);
break;
default:
throw new Exception('Unknown action type.');
}
$transaction->setOldValue($old);
$transaction->setNewValue($new);
}
}
$task->save();
foreach ($transactions as $transaction) {
$transaction->setTaskID($task->getID());
$transaction->save();
}
$email_to[] = $task->getOwnerPHID();
$email_cc = array_merge(
$email_cc,
$task->getCCPHIDs());
$this->publishFeedStory($task, $transactions);
// TODO: Do this offline via timeline
PhabricatorSearchManiphestIndexer::indexTask($task);
$this->sendEmail($task, $transactions, $email_to, $email_cc);
}
protected function getSubjectPrefix() {
return PhabricatorEnv::getEnvConfig('metamta.maniphest.subject-prefix');
}
private function sendEmail($task, $transactions, $email_to, $email_cc) {
$email_to = array_filter(array_unique($email_to));
$email_cc = array_filter(array_unique($email_cc));
$phids = array();
foreach ($transactions as $transaction) {
foreach ($transaction->extractPHIDs() as $phid) {
$phids[$phid] = true;
}
}
foreach ($email_to as $phid) {
$phids[$phid] = true;
}
foreach ($email_cc as $phid) {
$phids[$phid] = true;
}
$phids = array_keys($phids);
$handles = id(new PhabricatorObjectHandleData($phids))
->loadHandles();
$view = new ManiphestTransactionDetailView();
$view->setTransactionGroup($transactions);
$view->setHandles($handles);
$view->setAuxiliaryFields($this->auxiliaryFields);
list($action, $body) = $view->renderForEmail($with_date = false);
$is_create = $this->isCreate($transactions);
$task_uri = PhabricatorEnv::getURI('/T'.$task->getID());
$reply_handler = $this->buildReplyHandler($task);
if ($is_create) {
$body .=
"\n\n".
"TASK DESCRIPTION\n".
" ".$task->getDescription();
}
$body .=
"\n\n".
"TASK DETAIL\n".
" ".$task_uri."\n";
$reply_instructions = $reply_handler->getReplyHandlerInstructions();
if ($reply_instructions) {
$body .=
"\n".
"REPLY HANDLER ACTIONS\n".
" ".$reply_instructions."\n";
}
$thread_id = '<maniphest-task-'.$task->getPHID().'>';
$task_id = $task->getID();
$title = $task->getTitle();
$prefix = $this->getSubjectPrefix();
$subject = trim("{$prefix} [{$action}] T{$task_id}: {$title}");
$mailtags = $this->getMailTags($transactions);
$template = id(new PhabricatorMetaMTAMail())
->setSubject($subject)
->setFrom($transaction->getAuthorPHID())
->setParentMessageID($this->parentMessageID)
->addHeader('Thread-Topic', 'Maniphest Task '.$task->getID())
->setThreadID($thread_id, $is_create)
->setRelatedPHID($task->getPHID())
->setIsBulk(true)
->setMailTags($mailtags)
->setBody($body);
$mails = $reply_handler->multiplexMail(
$template,
array_select_keys($handles, $email_to),
array_select_keys($handles, $email_cc));
foreach ($mails as $mail) {
$mail->saveAndSend();
}
}
public function buildReplyHandler(ManiphestTask $task) {
$handler_class = PhabricatorEnv::getEnvConfig(
'metamta.maniphest.reply-handler');
$handler_object = newv($handler_class, array());
$handler_object->setMailReceiver($task);
return $handler_object;
}
private function publishFeedStory(ManiphestTask $task, array $transactions) {
$actions = array(ManiphestAction::ACTION_UPDATE);
$comments = null;
foreach ($transactions as $transaction) {
if ($transaction->hasComments()) {
$comments = $transaction->getComments();
}
switch ($transaction->getTransactionType()) {
case ManiphestTransactionType::TYPE_OWNER:
$actions[] = ManiphestAction::ACTION_ASSIGN;
break;
case ManiphestTransactionType::TYPE_STATUS:
if ($task->getStatus() != ManiphestTaskStatus::STATUS_OPEN) {
$actions[] = ManiphestAction::ACTION_CLOSE;
} else if ($this->isCreate($transactions)) {
$actions[] = ManiphestAction::ACTION_CREATE;
}
break;
default:
break;
}
}
$action_type = ManiphestAction::selectStrongestAction($actions);
$owner_phid = $task->getOwnerPHID();
$actor_phid = head($transactions)->getAuthorPHID();
$author_phid = $task->getAuthorPHID();
id(new PhabricatorFeedStoryPublisher())
->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_MANIPHEST)
->setStoryData(array(
'taskPHID' => $task->getPHID(),
'transactionIDs' => mpull($transactions, 'getID'),
'ownerPHID' => $owner_phid,
'action' => $action_type,
'comments' => $comments,
'description' => $task->getDescription(),
))
->setStoryTime(time())
->setStoryAuthorPHID($actor_phid)
->setRelatedPHIDs(
array_merge(
array_filter(
array(
$task->getPHID(),
$author_phid,
$actor_phid,
$owner_phid,
)),
$task->getProjectPHIDs()))
->publish();
}
private function isCreate(array $transactions) {
$is_create = false;
foreach ($transactions as $transaction) {
$type = $transaction->getTransactionType();
if (($type == ManiphestTransactionType::TYPE_STATUS) &&
($transaction->getOldValue() === null) &&
($transaction->getNewValue() == ManiphestTaskStatus::STATUS_OPEN)) {
$is_create = true;
}
}
return $is_create;
}
private function getMailTags(array $transactions) {
$tags = array();
foreach ($transactions as $xaction) {
switch ($xaction->getTransactionType()) {
case ManiphestTransactionType::TYPE_CCS:
$tags[] = MetaMTANotificationType::TYPE_MANIPHEST_CC;
break;
case ManiphestTransactionType::TYPE_PROJECTS:
$tags[] = MetaMTANotificationType::TYPE_MANIPHEST_PROJECTS;
break;
case ManiphestTransactionType::TYPE_PRIORITY:
$tags[] = MetaMTANotificationType::TYPE_MANIPHEST_PRIORITY;
break;
default:
$tags[] = MetaMTANotificationType::TYPE_MANIPHEST_OTHER;
break;
}
if ($xaction->hasComments()) {
$tags[] = MetaMTANotificationType::TYPE_MANIPHEST_COMMENT;
}
}
return array_unique($tags);
}
}
diff --git a/src/applications/maniphest/replyhandler/ManiphestReplyHandler.php b/src/applications/maniphest/replyhandler/ManiphestReplyHandler.php
index c643266041..f042007dad 100644
--- a/src/applications/maniphest/replyhandler/ManiphestReplyHandler.php
+++ b/src/applications/maniphest/replyhandler/ManiphestReplyHandler.php
@@ -1,173 +1,173 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
-class ManiphestReplyHandler extends PhabricatorMailReplyHandler {
+final class ManiphestReplyHandler extends PhabricatorMailReplyHandler {
public function validateMailReceiver($mail_receiver) {
if (!($mail_receiver instanceof ManiphestTask)) {
throw new Exception("Mail receiver is not a ManiphestTask!");
}
}
public function getPrivateReplyHandlerEmailAddress(
PhabricatorObjectHandle $handle) {
return $this->getDefaultPrivateReplyHandlerEmailAddress($handle, 'T');
}
public function getPublicReplyHandlerEmailAddress() {
return $this->getDefaultPublicReplyHandlerEmailAddress('T');
}
public function getReplyHandlerDomain() {
return PhabricatorEnv::getEnvConfig(
'metamta.maniphest.reply-handler-domain');
}
public function getReplyHandlerInstructions() {
if ($this->supportsReplies()) {
return "Reply to comment or attach files, or !close, !claim, or ".
"!unsubscribe.";
} else {
return null;
}
}
public function receiveEmail(PhabricatorMetaMTAReceivedMail $mail) {
// NOTE: We'll drop in here on both the "reply to a task" and "create a
// new task" workflows! Make sure you test both if you make changes!
$task = $this->getMailReceiver();
$is_new_task = !$task->getID();
$user = $this->getActor();
$body = $mail->getCleanTextBody();
$body = trim($body);
$xactions = array();
$content_source = PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_EMAIL,
array(
'id' => $mail->getID(),
));
$template = new ManiphestTransaction();
$template->setContentSource($content_source);
$template->setAuthorPHID($user->getPHID());
if ($is_new_task) {
// If this is a new task, create a "User created this task." transaction
// and then set the title and description.
$xaction = clone $template;
$xaction->setTransactionType(ManiphestTransactionType::TYPE_STATUS);
$xaction->setNewValue(ManiphestTaskStatus::STATUS_OPEN);
$xactions[] = $xaction;
$task->setAuthorPHID($user->getPHID());
$task->setTitle(nonempty($mail->getSubject(), 'Untitled Task'));
$task->setDescription($body);
} else {
$lines = explode("\n", trim($body));
$first_line = head($lines);
$command = null;
$matches = null;
if (preg_match('/^!(\w+)/', $first_line, $matches)) {
$lines = array_slice($lines, 1);
$body = implode("\n", $lines);
$body = trim($body);
$command = $matches[1];
}
$ttype = ManiphestTransactionType::TYPE_NONE;
$new_value = null;
switch ($command) {
case 'close':
$ttype = ManiphestTransactionType::TYPE_STATUS;
$new_value = ManiphestTaskStatus::STATUS_CLOSED_RESOLVED;
break;
case 'claim':
$ttype = ManiphestTransactionType::TYPE_OWNER;
$new_value = $user->getPHID();
break;
case 'unsubscribe':
$ttype = ManiphestTransactionType::TYPE_CCS;
$ccs = $task->getCCPHIDs();
foreach ($ccs as $k => $phid) {
if ($phid == $user->getPHID()) {
unset($ccs[$k]);
}
}
$new_value = array_values($ccs);
break;
}
$xaction = clone $template;
$xaction->setTransactionType($ttype);
$xaction->setNewValue($new_value);
$xaction->setComments($body);
$xactions[] = $xaction;
}
// TODO: We should look at CCs on the mail and add them as CCs.
$files = $mail->getAttachments();
if ($files) {
$file_xaction = clone $template;
$file_xaction->setTransactionType(ManiphestTransactionType::TYPE_ATTACH);
$phid_type = PhabricatorPHIDConstants::PHID_TYPE_FILE;
$new = $task->getAttached();
foreach ($files as $file_phid) {
$new[$phid_type][$file_phid] = array();
}
$file_xaction->setNewValue($new);
$xactions[] = $file_xaction;
}
$event = new PhabricatorEvent(
PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK,
array(
'task' => $task,
'mail' => $mail,
'new' => $is_new_task,
'transactions' => $xactions,
));
$event->setUser($user);
PhutilEventEngine::dispatchEvent($event);
$task = $event->getValue('task');
$xactions = $event->getValue('transactions');
$editor = new ManiphestTransactionEditor();
$editor->setParentMessageID($mail->getMessageID());
$editor->applyTransactions($task, $xactions);
}
}
diff --git a/src/applications/maniphest/storage/auxiliary/ManiphestTaskAuxiliaryStorage.php b/src/applications/maniphest/storage/auxiliary/ManiphestTaskAuxiliaryStorage.php
index d1d9fbdb8a..1a99de76c0 100644
--- a/src/applications/maniphest/storage/auxiliary/ManiphestTaskAuxiliaryStorage.php
+++ b/src/applications/maniphest/storage/auxiliary/ManiphestTaskAuxiliaryStorage.php
@@ -1,28 +1,28 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
-class ManiphestTaskAuxiliaryStorage extends ManiphestDAO {
+final class ManiphestTaskAuxiliaryStorage extends ManiphestDAO {
protected $taskPHID;
protected $name;
protected $value;
}
diff --git a/src/applications/maniphest/storage/base/ManiphestDAO.php b/src/applications/maniphest/storage/base/ManiphestDAO.php
index c7df0c59b0..ff7319d028 100644
--- a/src/applications/maniphest/storage/base/ManiphestDAO.php
+++ b/src/applications/maniphest/storage/base/ManiphestDAO.php
@@ -1,28 +1,28 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
-class ManiphestDAO extends PhabricatorLiskDAO {
+abstract class ManiphestDAO extends PhabricatorLiskDAO {
public function getApplicationName() {
return 'maniphest';
}
}
diff --git a/src/applications/maniphest/storage/task/ManiphestTask.php b/src/applications/maniphest/storage/task/ManiphestTask.php
index 5f7a54cfc4..cec6055f53 100644
--- a/src/applications/maniphest/storage/task/ManiphestTask.php
+++ b/src/applications/maniphest/storage/task/ManiphestTask.php
@@ -1,207 +1,207 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
-class ManiphestTask extends ManiphestDAO {
+final class ManiphestTask extends ManiphestDAO {
protected $phid;
protected $authorPHID;
protected $ownerPHID;
protected $ccPHIDs = array();
protected $status;
protected $priority;
protected $title;
protected $description;
protected $originalEmailSource;
protected $mailKey;
protected $attached = array();
protected $projectPHIDs = array();
private $projectsNeedUpdate;
private $subscribersNeedUpdate;
protected $ownerOrdering;
private $auxiliaryAttributes;
private $auxiliaryDirty = array();
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'ccPHIDs' => self::SERIALIZATION_JSON,
'attached' => self::SERIALIZATION_JSON,
'projectPHIDs' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function getAttachedPHIDs($type) {
return array_keys(idx($this->attached, $type, array()));
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_TASK);
}
public function getCCPHIDs() {
return array_values(nonempty($this->ccPHIDs, array()));
}
public function setProjectPHIDs(array $phids) {
$this->projectPHIDs = array_values($phids);
$this->projectsNeedUpdate = true;
return $this;
}
public function getProjectPHIDs() {
return array_values(nonempty($this->projectPHIDs, array()));
}
public function setCCPHIDs(array $phids) {
$this->ccPHIDs = array_values($phids);
$this->subscribersNeedUpdate = true;
return $this;
}
public function setOwnerPHID($phid) {
$this->ownerPHID = $phid;
$this->subscribersNeedUpdate = true;
return $this;
}
public function getAuxiliaryAttribute($key, $default = null) {
if ($this->auxiliaryAttributes === null) {
throw new Exception("Attach auxiliary attributes before getting them!");
}
return idx($this->auxiliaryAttributes, $key, $default);
}
public function setAuxiliaryAttribute($key, $val) {
if ($this->auxiliaryAttributes === null) {
throw new Exception("Attach auxiliary attributes before setting them!");
}
$this->auxiliaryAttributes[$key] = $val;
$this->auxiliaryDirty[$key] = true;
return $this;
}
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.");
}
$this->auxiliaryAttributes = $attrs;
return $this;
}
public function loadAndAttachAuxiliaryAttributes() {
if (!$this->getPHID()) {
$this->auxiliaryAttributes = array();
return;
}
$storage = id(new ManiphestTaskAuxiliaryStorage())->loadAllWhere(
'taskPHID = %s',
$this->getPHID());
$this->auxiliaryAttributes = mpull($storage, 'getValue', 'getName');
return $this;
}
public function save() {
if (!$this->mailKey) {
$this->mailKey = Filesystem::readRandomCharacters(20);
}
$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;
}
if ($this->subscribersNeedUpdate) {
// If we've changed the subscriber PHIDs for this task, update the link
// table.
ManiphestTaskSubscriber::updateTaskSubscribers($this);
$this->subscribersNeedUpdate = false;
}
if ($this->auxiliaryDirty) {
$this->writeAuxiliaryUpdates();
$this->auxiliaryDirty = array();
}
return $result;
}
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,
'(%s, %s, %s)',
$this->getPHID(),
$key,
$val);
}
queryfx(
$conn_w,
'INSERT INTO %T (taskPHID, name, value) VALUES %Q
ON DUPLICATE KEY UPDATE value = VALUES(value)',
$table->getTableName(),
implode(', ', $sql));
}
}
}
diff --git a/src/applications/maniphest/storage/transaction/ManiphestTransaction.php b/src/applications/maniphest/storage/transaction/ManiphestTransaction.php
index b641a9833b..a82136c2cd 100644
--- a/src/applications/maniphest/storage/transaction/ManiphestTransaction.php
+++ b/src/applications/maniphest/storage/transaction/ManiphestTransaction.php
@@ -1,141 +1,141 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
-class ManiphestTransaction extends ManiphestDAO {
+final class ManiphestTransaction extends ManiphestDAO {
protected $taskID;
protected $authorPHID;
protected $transactionType;
protected $oldValue;
protected $newValue;
protected $comments;
protected $cache;
protected $metadata = array();
protected $contentSource;
public function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'oldValue' => self::SERIALIZATION_JSON,
'newValue' => self::SERIALIZATION_JSON,
'metadata' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function extractPHIDs() {
$phids = array();
switch ($this->getTransactionType()) {
case ManiphestTransactionType::TYPE_CCS:
case ManiphestTransactionType::TYPE_PROJECTS:
foreach ($this->getOldValue() as $phid) {
$phids[] = $phid;
}
foreach ($this->getNewValue() as $phid) {
$phids[] = $phid;
}
break;
case ManiphestTransactionType::TYPE_OWNER:
$phids[] = $this->getOldValue();
$phids[] = $this->getNewValue();
break;
case ManiphestTransactionType::TYPE_ATTACH:
$old = $this->getOldValue();
$new = $this->getNewValue();
if (!is_array($old)) {
$old = array();
}
if (!is_array($new)) {
$new = array();
}
$val = array_merge(array_values($old), array_values($new));
foreach ($val as $stuff) {
foreach ($stuff as $phid => $ignored) {
$phids[] = $phid;
}
}
break;
}
$phids[] = $this->getAuthorPHID();
return $phids;
}
public function getMetadataValue($key, $default = null) {
if (!is_array($this->metadata)) {
return $default;
}
return idx($this->metadata, $key, $default);
}
public function setMetadataValue($key, $value) {
if (!is_array($this->metadata)) {
$this->metadata = array();
}
$this->metadata[$key] = $value;
return $this;
}
public function canGroupWith($target) {
if ($target->getAuthorPHID() != $this->getAuthorPHID()) {
return false;
}
if ($target->hasComments() && $this->hasComments()) {
return false;
}
$ttime = $target->getDateCreated();
$stime = $this->getDateCreated();
if (abs($stime - $ttime) > 60) {
return false;
}
if ($target->getTransactionType() == $this->getTransactionType()) {
$aux_type = ManiphestTransactionType::TYPE_AUXILIARY;
if ($this->getTransactionType() == $aux_type) {
$that_key = $target->getMetadataValue('aux:key');
$this_key = $this->getMetadataValue('aux:key');
if ($that_key == $this_key) {
return false;
}
} else {
return false;
}
}
return true;
}
public function hasComments() {
return (bool)strlen(trim($this->getComments()));
}
public function setContentSource(PhabricatorContentSource $content_source) {
$this->contentSource = $content_source->serialize();
return $this;
}
public function getContentSource() {
return PhabricatorContentSource::newFromSerialized($this->contentSource);
}
}
diff --git a/src/applications/maniphest/view/tasklist/ManiphestTaskListView.php b/src/applications/maniphest/view/tasklist/ManiphestTaskListView.php
index 9928cc7a58..8d8ace72fa 100644
--- a/src/applications/maniphest/view/tasklist/ManiphestTaskListView.php
+++ b/src/applications/maniphest/view/tasklist/ManiphestTaskListView.php
@@ -1,64 +1,64 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
-class ManiphestTaskListView extends ManiphestView {
+final class ManiphestTaskListView extends ManiphestView {
private $tasks;
private $handles;
private $user;
private $showBatchControls;
public function setTasks(array $tasks) {
$this->tasks = $tasks;
return $this;
}
public function setHandles(array $handles) {
$this->handles = $handles;
return $this;
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function setShowBatchControls($show_batch_controls) {
$this->showBatchControls = $show_batch_controls;
return $this;
}
public function render() {
$views = array();
foreach ($this->tasks as $task) {
$view = new ManiphestTaskSummaryView();
$view->setTask($task);
$view->setShowBatchControls($this->showBatchControls);
$view->setUser($this->user);
$view->setHandles($this->handles);
$views[] = $view->render();
}
return implode("\n", $views);
}
}
diff --git a/src/applications/maniphest/view/tasksummary/ManiphestTaskSummaryView.php b/src/applications/maniphest/view/tasksummary/ManiphestTaskSummaryView.php
index f5e47c56ad..ca4ed447eb 100644
--- a/src/applications/maniphest/view/tasksummary/ManiphestTaskSummaryView.php
+++ b/src/applications/maniphest/view/tasksummary/ManiphestTaskSummaryView.php
@@ -1,126 +1,126 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
-class ManiphestTaskSummaryView extends ManiphestView {
+final class ManiphestTaskSummaryView extends ManiphestView {
private $task;
private $handles;
private $user;
private $showBatchControls;
public function setTask(ManiphestTask $task) {
$this->task = $task;
return $this;
}
public function setHandles(array $handles) {
$this->handles = $handles;
return $this;
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function setShowBatchControls($show_batch_controls) {
$this->showBatchControls = $show_batch_controls;
return $this;
}
public function render() {
if (!$this->user) {
throw new Exception("Call setUser() before rendering!");
}
$task = $this->task;
$handles = $this->handles;
require_celerity_resource('maniphest-task-summary-css');
$classes = array(
ManiphestTaskPriority::PRIORITY_UNBREAK_NOW => 'pri-unbreak',
ManiphestTaskPriority::PRIORITY_TRIAGE => 'pri-triage',
ManiphestTaskPriority::PRIORITY_HIGH => 'pri-high',
ManiphestTaskPriority::PRIORITY_NORMAL => 'pri-normal',
ManiphestTaskPriority::PRIORITY_LOW => 'pri-low',
ManiphestTaskPriority::PRIORITY_WISH => 'pri-wish',
);
$pri_class = idx($classes, $task->getPriority());
$status_map = ManiphestTaskStatus::getTaskStatusMap();
$batch = null;
if ($this->showBatchControls) {
$batch =
'<td class="maniphest-task-batch">'.
javelin_render_tag(
'input',
array(
'type' => 'checkbox',
'name' => 'batch[]',
'value' => $task->getID(),
'sigil' => 'maniphest-batch',
),
null).
'</td>';
}
return javelin_render_tag(
'table',
array(
'class' => 'maniphest-task-summary',
'sigil' => 'maniphest-task',
),
'<tr>'.
'<td class="maniphest-task-handle '.$pri_class.'">'.
'</td>'.
$batch.
'<td class="maniphest-task-number">'.
'T'.$task->getID().
'</td>'.
'<td class="maniphest-task-status">'.
idx($status_map, $task->getStatus(), 'Unknown').
'</td>'.
'<td class="maniphest-task-owner">'.
($task->getOwnerPHID()
? $handles[$task->getOwnerPHID()]->renderLink()
: '<em>None</em>').
'</td>'.
'<td class="maniphest-task-name">'.
phutil_render_tag(
'a',
array(
'href' => '/T'.$task->getID(),
),
phutil_escape_html($task->getTitle())).
'</td>'.
'<td class="maniphest-task-priority">'.
ManiphestTaskPriority::getTaskPriorityName($task->getPriority()).
'</td>'.
'<td class="maniphest-task-updated">'.
phabricator_datetime($task->getDateModified(), $this->user).
'</td>'.
'</tr>');
}
}
diff --git a/src/applications/maniphest/view/transactiondetail/ManiphestTransactionDetailView.php b/src/applications/maniphest/view/transactiondetail/ManiphestTransactionDetailView.php
index c4175c0e4c..b497c4a509 100644
--- a/src/applications/maniphest/view/transactiondetail/ManiphestTransactionDetailView.php
+++ b/src/applications/maniphest/view/transactiondetail/ManiphestTransactionDetailView.php
@@ -1,628 +1,628 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
-class ManiphestTransactionDetailView extends ManiphestView {
+final class ManiphestTransactionDetailView extends ManiphestView {
private $transactions;
private $handles;
private $markupEngine;
private $forEmail;
private $preview;
private $commentNumber;
private $rangeSpecification;
private $renderSummaryOnly;
private $renderFullSummary;
private $user;
private $auxiliaryFields;
public function setAuxiliaryFields(array $fields) {
$this->auxiliaryFields = mpull($fields, null, 'getAuxiliaryKey');
return $this;
}
public function getAuxiliaryField($key) {
return idx($this->auxiliaryFields, $key);
}
public function setTransactionGroup(array $transactions) {
$this->transactions = $transactions;
return $this;
}
public function setHandles(array $handles) {
$this->handles = $handles;
return $this;
}
public function setMarkupEngine(PhutilMarkupEngine $engine) {
$this->markupEngine = $engine;
return $this;
}
public function setPreview($preview) {
$this->preview = $preview;
return $this;
}
public function setRenderSummaryOnly($render_summary_only) {
$this->renderSummaryOnly = $render_summary_only;
return $this;
}
public function getRenderSummaryOnly() {
return $this->renderSummaryOnly;
}
public function setRenderFullSummary($render_full_summary) {
$this->renderFullSummary = $render_full_summary;
return $this;
}
public function getRenderFullSummary() {
return $this->renderFullSummary;
}
public function setCommentNumber($comment_number) {
$this->commentNumber = $comment_number;
return $this;
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function setRangeSpecification($range) {
$this->rangeSpecification = $range;
return $this;
}
public function getRangeSpecification() {
return $this->rangeSpecification;
}
public function renderForEmail($with_date) {
$this->forEmail = true;
$transaction = reset($this->transactions);
$author = $this->renderHandles(array($transaction->getAuthorPHID()));
$action = null;
$descs = array();
$comments = null;
foreach ($this->transactions as $transaction) {
list($verb, $desc, $classes) = $this->describeAction($transaction);
if ($desc === null) {
continue;
}
if ($action === null) {
$action = $verb;
}
$desc = $author.' '.$desc.'.';
if ($with_date) {
// NOTE: This is going into a (potentially multi-recipient) email so
// we can't use a single user's timezone preferences. Use the server's
// instead, but make the timezone explicit.
$datetime = date('M jS \a\t g:i A T', $transaction->getDateCreated());
$desc = "On {$datetime}, {$desc}";
}
$descs[] = $desc;
if ($transaction->hasComments()) {
$comments = $transaction->getComments();
}
}
$descs = implode("\n", $descs);
if ($comments) {
$descs .= "\n".$comments;
}
foreach ($this->transactions as $transaction) {
$supplemental = $this->renderSupplementalInfoForEmail($transaction);
if ($supplemental) {
$descs .= "\n\n".$supplemental;
}
}
$this->forEmail = false;
return array($action, $descs);
}
public function render() {
if (!$this->user) {
throw new Exception("Call setUser() before render()!");
}
$handles = $this->handles;
$transactions = $this->transactions;
require_celerity_resource('maniphest-transaction-detail-css');
$comment_transaction = null;
foreach ($this->transactions as $transaction) {
if ($transaction->hasComments()) {
$comment_transaction = $transaction;
break;
}
}
$any_transaction = reset($transactions);
$author = $this->handles[$any_transaction->getAuthorPHID()];
$more_classes = array();
$descs = array();
foreach ($transactions as $transaction) {
list($verb, $desc, $classes) = $this->describeAction($transaction);
if ($desc === null) {
continue;
}
$more_classes = array_merge($more_classes, $classes);
$full_summary = null;
if ($this->getRenderFullSummary()) {
$full_summary = $this->renderFullSummary($transaction);
}
$descs[] = javelin_render_tag(
'div',
array(
'sigil' => 'maniphest-transaction-description',
),
$author->renderLink().' '.$desc.'.'.$full_summary);
}
if ($this->getRenderSummaryOnly()) {
return implode("\n", $descs);
}
if ($comment_transaction && $comment_transaction->hasComments()) {
$comments = $comment_transaction->getCache();
if (!strlen($comments)) {
$comments = $comment_transaction->getComments();
if (strlen($comments)) {
$comments = $this->markupEngine->markupText($comments);
$comment_transaction->setCache($comments);
if ($comment_transaction->getID() && !$this->preview) {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$comment_transaction->save();
unset($unguarded);
}
}
}
$comment_block =
'<div class="maniphest-transaction-comments phabricator-remarkup">'.
$comments.
'</div>';
} else {
$comment_block = null;
}
$source_transaction = nonempty($comment_transaction, $any_transaction);
$xaction_view = id(new PhabricatorTransactionView())
->setUser($this->user)
->setImageURI($author->getImageURI())
->setContentSource($source_transaction->getContentSource())
->setActions($descs);
foreach ($more_classes as $class) {
$xaction_view->addClass($class);
}
if ($this->preview) {
$xaction_view->setIsPreview($this->preview);
} else {
$xaction_view->setEpoch($any_transaction->getDateCreated());
if ($this->commentNumber) {
$anchor_name = 'comment-'.$this->commentNumber;
$anchor_text = 'T'.$any_transaction->getTaskID().'#'.$anchor_name;
$xaction_view->setAnchor($anchor_name, $anchor_text);
}
}
$xaction_view->appendChild($comment_block);
return $xaction_view->render();
}
private function renderSupplementalInfoForEmail($transaction) {
$handles = $this->handles;
$type = $transaction->getTransactionType();
$new = $transaction->getNewValue();
$old = $transaction->getOldValue();
switch ($type) {
case ManiphestTransactionType::TYPE_DESCRIPTION:
return "NEW DESCRIPTION\n ".trim($new)."\n\n".
"PREVIOUS DESCRIPTION\n ".trim($old);
case ManiphestTransactionType::TYPE_ATTACH:
$old_raw = nonempty($old, array());
$new_raw = nonempty($new, array());
$attach_types = array(
PhabricatorPHIDConstants::PHID_TYPE_DREV,
PhabricatorPHIDConstants::PHID_TYPE_FILE,
);
foreach ($attach_types as $type) {
$old = array_keys(idx($old_raw, $type, array()));
$new = array_keys(idx($new_raw, $type, array()));
if ($old != $new) {
break;
}
}
$added = array_diff($new, $old);
if (!$added) {
break;
}
$links = array();
foreach (array_select_keys($handles, $added) as $handle) {
$links[] = ' '.PhabricatorEnv::getProductionURI($handle->getURI());
}
$links = implode("\n", $links);
switch ($type) {
case PhabricatorPHIDConstants::PHID_TYPE_DREV:
$title = 'ATTACHED REVISIONS';
break;
case PhabricatorPHIDConstants::PHID_TYPE_FILE:
$title = 'ATTACHED FILES';
break;
}
return $title."\n".$links;
default:
break;
}
return null;
}
private function describeAction($transaction) {
$verb = null;
$desc = null;
$classes = array();
$handles = $this->handles;
$type = $transaction->getTransactionType();
$author_phid = $transaction->getAuthorPHID();
$new = $transaction->getNewValue();
$old = $transaction->getOldValue();
switch ($type) {
case ManiphestTransactionType::TYPE_TITLE:
$verb = 'Retitled';
$desc = 'changed the title from '.$this->renderString($old).
' to '.$this->renderString($new);
break;
case ManiphestTransactionType::TYPE_DESCRIPTION:
$verb = 'Edited';
if ($this->forEmail || $this->getRenderFullSummary()) {
$desc = 'updated the task description';
} else {
$desc = 'updated the task description; '.
$this->renderExpandLink($transaction);
}
break;
case ManiphestTransactionType::TYPE_NONE:
$verb = 'Commented On';
$desc = 'added a comment';
break;
case ManiphestTransactionType::TYPE_OWNER:
if ($transaction->getAuthorPHID() == $new) {
$verb = 'Claimed';
$desc = 'claimed this task';
$classes[] = 'claimed';
} else if (!$new) {
$verb = 'Up For Grabs';
$desc = 'placed this task up for grabs';
$classes[] = 'upforgrab';
} else if (!$old) {
$verb = 'Assigned';
$desc = 'assigned this task to '.$this->renderHandles(array($new));
$classes[] = 'assigned';
} else {
$verb = 'Reassigned';
$desc = 'reassigned this task from '.
$this->renderHandles(array($old)).
' to '.
$this->renderHandles(array($new));
$classes[] = 'reassigned';
}
break;
case ManiphestTransactionType::TYPE_CCS:
if ($this->preview) {
$verb = 'Changed CC';
$desc = 'changed CCs..';
break;
}
$added = array_diff($new, $old);
$removed = array_diff($old, $new);
if ($added && !$removed) {
$verb = 'Added CC';
if (count($added) == 1) {
$desc = 'added '.$this->renderHandles($added).' to CC';
} else {
$desc = 'added CCs: '.$this->renderHandles($added);
}
} else if ($removed && !$added) {
$verb = 'Removed CC';
if (count($removed) == 1) {
$desc = 'removed '.$this->renderHandles($removed).' from CC';
} else {
$desc = 'removed CCs: '.$this->renderHandles($removed);
}
} else {
$verb = 'Changed CC';
$desc = 'changed CCs, added: '.$this->renderHandles($added).'; '.
'removed: '.$this->renderHandles($removed);
}
break;
case ManiphestTransactionType::TYPE_PROJECTS:
if ($this->preview) {
$verb = 'Changed Projects';
$desc = 'changed projects..';
break;
}
$added = array_diff($new, $old);
$removed = array_diff($old, $new);
if ($added && !$removed) {
$verb = 'Added Project';
if (count($added) == 1) {
$desc = 'added project '.$this->renderHandles($added);
} else {
$desc = 'added projects: '.$this->renderHandles($added);
}
} else if ($removed && !$added) {
$verb = 'Removed Project';
if (count($removed) == 1) {
$desc = 'removed project '.$this->renderHandles($removed);
} else {
$desc = 'removed projectss: '.$this->renderHandles($removed);
}
} else {
$verb = 'Changed Projects';
$desc = 'changed projects, added: '.$this->renderHandles($added).'; '.
'removed: '.$this->renderHandles($removed);
}
break;
case ManiphestTransactionType::TYPE_STATUS:
if ($new == ManiphestTaskStatus::STATUS_OPEN) {
if ($old) {
$verb = 'Reopened';
$desc = 'reopened this task';
$classes[] = 'reopened';
} else {
$verb = 'Created';
$desc = 'created this task';
$classes[] = 'created';
}
} else if ($new == ManiphestTaskStatus::STATUS_CLOSED_SPITE) {
$verb = 'Spited';
$desc = 'closed this task out of spite';
$classes[] = 'spited';
} else if ($new == ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE) {
$verb = 'Merged';
$desc = 'closed this task as a duplicate';
$classes[] = 'duplicate';
} else {
$verb = 'Closed';
$full = idx(ManiphestTaskStatus::getTaskStatusMap(), $new, '???');
$desc = 'closed this task as "'.$full.'"';
$classes[] = 'closed';
}
break;
case ManiphestTransactionType::TYPE_PRIORITY:
$old_name = ManiphestTaskPriority::getTaskPriorityName($old);
$new_name = ManiphestTaskPriority::getTaskPriorityName($new);
if ($old == ManiphestTaskPriority::PRIORITY_TRIAGE) {
$verb = 'Triaged';
$desc = 'triaged this task as "'.$new_name.'" priority';
} else if ($old > $new) {
$verb = 'Lowered Priority';
$desc = 'lowered the priority of this task from "'.$old_name.'" to '.
'"'.$new_name.'"';
} else {
$verb = 'Raised Priority';
$desc = 'raised the priority of this task from "'.$old_name.'" to '.
'"'.$new_name.'"';
}
if ($new == ManiphestTaskPriority::PRIORITY_UNBREAK_NOW) {
$classes[] = 'unbreaknow';
}
break;
case ManiphestTransactionType::TYPE_ATTACH:
if ($this->preview) {
$verb = 'Changed Attached';
$desc = 'changed attachments..';
break;
}
$old_raw = nonempty($old, array());
$new_raw = nonempty($new, array());
foreach (array(
PhabricatorPHIDConstants::PHID_TYPE_DREV,
PhabricatorPHIDConstants::PHID_TYPE_TASK,
PhabricatorPHIDConstants::PHID_TYPE_FILE) as $type) {
$old = array_keys(idx($old_raw, $type, array()));
$new = array_keys(idx($new_raw, $type, array()));
if ($old != $new) {
break;
}
}
$added = array_diff($new, $old);
$removed = array_diff($old, $new);
$add_desc = $this->renderHandles($added);
$rem_desc = $this->renderHandles($removed);
switch ($type) {
case PhabricatorPHIDConstants::PHID_TYPE_DREV:
$singular = 'Differential Revision';
$plural = 'Differential Revisions';
break;
case PhabricatorPHIDConstants::PHID_TYPE_FILE:
$singular = 'file';
$plural = 'files';
break;
case PhabricatorPHIDConstants::PHID_TYPE_TASK:
$singular = 'Maniphest Task';
$plural = 'Maniphest Tasks';
$dependency = true;
break;
}
if ($added && !$removed) {
$verb = 'Attached';
if (count($added) == 1) {
$desc = 'attached '.$singular.': '.$add_desc;
} else {
$desc = 'attached '.$plural.': '.$add_desc;
}
} else if ($removed && !$added) {
$verb = 'Detached';
if (count($removed) == 1) {
$desc = 'detached '.$singular.': '.$rem_desc;
} else {
$desc = 'detached '.$plural.': '.$rem_desc;
}
} else {
$verb = 'Changed Attached';
$desc = 'changed attached '.$plural.', added: '.$add_desc.'; '.
'removed: '.$rem_desc;
}
break;
case ManiphestTransactionType::TYPE_AUXILIARY:
$aux_key = $transaction->getMetadataValue('aux:key');
$aux_field = $this->getAuxiliaryField($aux_key);
$verb = null;
if ($aux_field) {
$verb = $aux_field->renderTransactionEmailVerb($transaction);
}
if ($verb === null) {
if ($old === null) {
$verb = "Set Field";
} else if ($new === null) {
$verb = "Removed Field";
} else {
$verb = "Updated Field";
}
}
$desc = null;
if ($aux_field) {
$use_field = $aux_field;
} else {
$use_field = id(new ManiphestAuxiliaryFieldDefaultSpecification())
->setFieldType(
ManiphestAuxiliaryFieldDefaultSpecification::TYPE_STRING);
}
$desc = $use_field->renderTransactionDescription(
$transaction,
$this->forEmail
? ManiphestAuxiliaryFieldSpecification::RENDER_TARGET_TEXT
: ManiphestAuxiliaryFieldSpecification::RENDER_TARGET_HTML);
break;
default:
return array($type, ' brazenly '.$type."'d", $classes);
}
return array($verb, $desc, $classes);
}
private function renderFullSummary($transaction) {
switch ($transaction->getTransactionType()) {
case ManiphestTransactionType::TYPE_DESCRIPTION:
$id = $transaction->getID();
$old_text = wordwrap($transaction->getOldValue(), 80);
$new_text = wordwrap($transaction->getNewValue(), 80);
$engine = new PhabricatorDifferenceEngine();
$changeset = $engine->generateChangesetFromFileContent($old_text,
$new_text);
$whitespace_mode = DifferentialChangesetParser::WHITESPACE_SHOW_ALL;
$parser = new DifferentialChangesetParser();
$parser->setChangeset($changeset);
$parser->setRenderingReference($id);
$parser->setWhitespaceMode($whitespace_mode);
$spec = $this->getRangeSpecification();
list($range_s, $range_e, $mask) =
DifferentialChangesetParser::parseRangeSpecification($spec);
$output = $parser->render($range_s, $range_e, $mask);
return $output;
}
return null;
}
private function renderExpandLink($transaction) {
$id = $transaction->getID();
Javelin::initBehavior('maniphest-transaction-expand');
return javelin_render_tag(
'a',
array(
'href' => '/maniphest/task/descriptionchange/'.$id.'/',
'sigil' => 'maniphest-expand-transaction',
'mustcapture' => true,
),
'show details');
}
private function renderHandles($phids) {
$links = array();
foreach ($phids as $phid) {
if ($this->forEmail) {
$links[] = $this->handles[$phid]->getName();
} else {
$links[] = $this->handles[$phid]->renderLink();
}
}
return implode(', ', $links);
}
private function renderString($string) {
if ($this->forEmail) {
return '"'.$string.'"';
} else {
return '"'.phutil_escape_html($string).'"';
}
}
}
diff --git a/src/applications/maniphest/view/transactionlist/ManiphestTransactionListView.php b/src/applications/maniphest/view/transactionlist/ManiphestTransactionListView.php
index 417d94f3a6..01b84cbc9d 100644
--- a/src/applications/maniphest/view/transactionlist/ManiphestTransactionListView.php
+++ b/src/applications/maniphest/view/transactionlist/ManiphestTransactionListView.php
@@ -1,123 +1,123 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
-class ManiphestTransactionListView extends ManiphestView {
+final class ManiphestTransactionListView extends ManiphestView {
private $transactions;
private $handles;
private $user;
private $markupEngine;
private $preview;
private $auxiliaryFields;
public function setTransactions(array $transactions) {
$this->transactions = $transactions;
return $this;
}
public function setHandles(array $handles) {
$this->handles = $handles;
return $this;
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function setMarkupEngine(PhutilMarkupEngine $engine) {
$this->markupEngine = $engine;
return $this;
}
public function setPreview($preview) {
$this->preview = $preview;
return $this;
}
public function setAuxiliaryFields(array $fields) {
$this->auxiliaryFields = $fields;
return $this;
}
public function render() {
$views = array();
$last = null;
$group = array();
$groups = array();
$has_description_transaction = false;
foreach ($this->transactions as $transaction) {
if ($transaction->getTransactionType() ==
ManiphestTransactionType::TYPE_DESCRIPTION) {
$has_description_transaction = true;
}
if ($last === null) {
$last = $transaction;
$group[] = $transaction;
continue;
} else if ($last->canGroupWith($transaction)) {
$group[] = $transaction;
if ($transaction->hasComments()) {
$last = $transaction;
}
} else {
$groups[] = $group;
$last = $transaction;
$group = array($transaction);
}
}
if ($group) {
$groups[] = $group;
}
if ($has_description_transaction) {
require_celerity_resource('differential-changeset-view-css');
require_celerity_resource('syntax-highlighting-css');
$whitespace_mode = DifferentialChangesetParser::WHITESPACE_SHOW_ALL;
Javelin::initBehavior('differential-show-more', array(
'uri' => '/maniphest/task/descriptionchange/',
'whitespace' => $whitespace_mode,
));
}
$sequence = 1;
foreach ($groups as $group) {
$view = new ManiphestTransactionDetailView();
$view->setUser($this->user);
$view->setAuxiliaryFields($this->auxiliaryFields);
$view->setTransactionGroup($group);
$view->setHandles($this->handles);
$view->setMarkupEngine($this->markupEngine);
$view->setPreview($this->preview);
$view->setCommentNumber($sequence++);
$views[] = $view->render();
}
return
'<div class="maniphest-transaction-list-view">'.
implode("\n", $views).
'</div>';
}
}
diff --git a/src/applications/metamta/adapter/amazonses/PhabricatorMailImplementationAmazonSESAdapter.php b/src/applications/metamta/adapter/amazonses/PhabricatorMailImplementationAmazonSESAdapter.php
index c4f9c642e7..3602c0df43 100644
--- a/src/applications/metamta/adapter/amazonses/PhabricatorMailImplementationAmazonSESAdapter.php
+++ b/src/applications/metamta/adapter/amazonses/PhabricatorMailImplementationAmazonSESAdapter.php
@@ -1,49 +1,49 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorMailImplementationAmazonSESAdapter
+final class PhabricatorMailImplementationAmazonSESAdapter
extends PhabricatorMailImplementationPHPMailerLiteAdapter {
private $message;
private $isHTML;
public function __construct() {
parent::__construct();
$this->mailer->Mailer = 'amazon-ses';
$this->mailer->customMailer = $this;
}
public function supportsMessageIDHeader() {
// Amazon SES will ignore any Message-ID we provide.
return false;
}
public function executeSend($body) {
$key = PhabricatorEnv::getEnvConfig('amazon-ses.access-key');
$secret = PhabricatorEnv::getEnvConfig('amazon-ses.secret-key');
$root = phutil_get_library_root('phabricator');
$root = dirname($root);
require_once $root.'/externals/amazon-ses/ses.php';
$service = newv('SimpleEmailService', array($key, $secret));
$service->enableUseExceptions(true);
return $service->sendRawEmail($body);
}
}
diff --git a/src/applications/metamta/adapter/phpmailerlite/PhabricatorMailImplementationPHPMailerLiteAdapter.php b/src/applications/metamta/adapter/phpmailerlite/PhabricatorMailImplementationPHPMailerLiteAdapter.php
index e10a23131e..dffc69865f 100644
--- a/src/applications/metamta/adapter/phpmailerlite/PhabricatorMailImplementationPHPMailerLiteAdapter.php
+++ b/src/applications/metamta/adapter/phpmailerlite/PhabricatorMailImplementationPHPMailerLiteAdapter.php
@@ -1,100 +1,103 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+/**
+ * TODO: Should be final, but inherited by SES.
+ */
class PhabricatorMailImplementationPHPMailerLiteAdapter
extends PhabricatorMailImplementationAdapter {
public function __construct() {
$root = phutil_get_library_root('phabricator');
$root = dirname($root);
require_once $root.'/externals/phpmailer/class.phpmailer-lite.php';
$this->mailer = newv('PHPMailerLite', array($use_exceptions = true));
$this->mailer->CharSet = 'utf-8';
}
public function supportsMessageIDHeader() {
return true;
}
public function setFrom($email, $name = '') {
$this->mailer->SetFrom($email, $name, $crazy_side_effects = false);
return $this;
}
public function addReplyTo($email, $name = '') {
$this->mailer->AddReplyTo($email, $name);
return $this;
}
public function addTos(array $emails) {
foreach ($emails as $email) {
$this->mailer->AddAddress($email);
}
return $this;
}
public function addCCs(array $emails) {
foreach ($emails as $email) {
$this->mailer->AddCC($email);
}
return $this;
}
public function addAttachment($data, $filename, $mimetype) {
$this->mailer->AddStringAttachment(
$data,
$filename,
'base64',
$mimetype
);
return $this;
}
public function addHeader($header_name, $header_value) {
if (strtolower($header_name) == 'message-id') {
$this->mailer->MessageID = $header_value;
} else {
$this->mailer->AddCustomHeader($header_name.': '.$header_value);
}
return $this;
}
public function setBody($body) {
$this->mailer->Body = $body;
return $this;
}
public function setSubject($subject) {
$this->mailer->Subject = $subject;
return $this;
}
public function setIsHTML($is_html) {
$this->mailer->IsHTML($is_html);
return $this;
}
public function hasValidRecipients() {
return true;
}
public function send() {
return $this->mailer->Send();
}
}
diff --git a/src/applications/metamta/adapter/sendgrid/PhabricatorMailImplementationSendGridAdapter.php b/src/applications/metamta/adapter/sendgrid/PhabricatorMailImplementationSendGridAdapter.php
index 4a0c3640bf..c563ddef3c 100644
--- a/src/applications/metamta/adapter/sendgrid/PhabricatorMailImplementationSendGridAdapter.php
+++ b/src/applications/metamta/adapter/sendgrid/PhabricatorMailImplementationSendGridAdapter.php
@@ -1,174 +1,174 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Mail adapter that uses SendGrid's web API to deliver email.
*/
-class PhabricatorMailImplementationSendGridAdapter
+final class PhabricatorMailImplementationSendGridAdapter
extends PhabricatorMailImplementationAdapter {
private $params = array();
public function setFrom($email, $name = '') {
$this->params['from'] = $email;
$this->params['from-name'] = $name;
return $this;
}
public function addReplyTo($email, $name = '') {
if (empty($this->params['reply-to'])) {
$this->params['reply-to'] = array();
}
$this->params['reply-to'][] = array(
'email' => $email,
'name' => $name,
);
return $this;
}
public function addTos(array $emails) {
foreach ($emails as $email) {
$this->params['tos'][] = $email;
}
return $this;
}
public function addCCs(array $emails) {
foreach ($emails as $email) {
$this->params['ccs'][] = $email;
}
return $this;
}
public function addAttachment($data, $filename, $mimetype) {
if (empty($this->params['files'])) {
$this->params['files'] = array();
}
$this->params['files'][$filename] = $data;
}
public function addHeader($header_name, $header_value) {
$this->params['headers'][] = array($header_name, $header_value);
return $this;
}
public function setBody($body) {
$this->params['body'] = $body;
return $this;
}
public function setSubject($subject) {
$this->params['subject'] = $subject;
return $this;
}
public function setIsHTML($is_html) {
$this->params['is-html'] = $is_html;
return $this;
}
public function supportsMessageIDHeader() {
return false;
}
public function send() {
$user = PhabricatorEnv::getEnvConfig('sendgrid.api-user');
$key = PhabricatorEnv::getEnvConfig('sendgrid.api-key');
if (!$user || !$key) {
throw new Exception(
"Configure 'sendgrid.api-user' and 'sendgrid.api-key' to use ".
"SendGrid for mail delivery.");
}
$params = array();
$ii = 0;
foreach (idx($this->params, 'tos', array()) as $to) {
$params['to['.($ii++).']'] = $to;
}
$params['subject'] = idx($this->params, 'subject');
if (idx($this->params, 'is-html')) {
$params['html'] = idx($this->params, 'body');
} else {
$params['text'] = idx($this->params, 'body');
}
$params['from'] = idx($this->params, 'from');
if (idx($this->params, 'from-name')) {
$params['fromname'] = $this->params['from-name'];
}
if (idx($this->params, 'reply-to')) {
$replyto = $this->params['reply-to'];
// Pick off the email part, no support for the name part in this API.
$params['replyto'] = $replyto[0]['email'];
}
foreach (idx($this->params, 'files', array()) as $name => $data) {
$params['files['.$name.']'] = $data;
}
$headers = idx($this->params, 'headers', array());
// See SendGrid Support Ticket #29390; there's no explicit REST API support
// for CC right now but it works if you add a generic "Cc" header.
//
// SendGrid said this is supported:
// "You can use CC as you are trying to do there [by adding a generic
// header]. It is supported despite our limited documentation to this
// effect, I am glad you were able to figure it out regardless. ..."
if (idx($this->params, 'ccs')) {
$headers[] = array('Cc', implode(', ', $this->params['ccs']));
}
if ($headers) {
// Convert to dictionary.
$headers = ipull($headers, 1, 0);
$headers = json_encode($headers);
$params['headers'] = $headers;
}
$params['api_user'] = $user;
$params['api_key'] = $key;
$future = new HTTPSFuture(
'https://sendgrid.com/api/mail.send.json',
$params);
$future->setMethod('POST');
list($body) = $future->resolvex();
$response = json_decode($body, true);
if (!is_array($response)) {
throw new Exception("Failed to JSON decode response: {$body}");
}
if ($response['message'] !== 'success') {
$errors = implode(";", $response['errors']);
throw new Exception("Request failed with errors: {$errors}.");
}
return true;
}
}
diff --git a/src/applications/metamta/adapter/test/PhabricatorMailImplementationTestAdapter.php b/src/applications/metamta/adapter/test/PhabricatorMailImplementationTestAdapter.php
index 9890974492..1bd6a53a0c 100644
--- a/src/applications/metamta/adapter/test/PhabricatorMailImplementationTestAdapter.php
+++ b/src/applications/metamta/adapter/test/PhabricatorMailImplementationTestAdapter.php
@@ -1,106 +1,106 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Mail adapter that doesn't actually send any email, for writing unit tests
* against.
*/
-class PhabricatorMailImplementationTestAdapter
+final class PhabricatorMailImplementationTestAdapter
extends PhabricatorMailImplementationAdapter {
private $guts = array();
private $config;
public function __construct(array $config) {
$this->config = $config;
}
public function setFrom($email, $name = '') {
$this->guts['from'] = $email;
$this->guts['from-name'] = $name;
return $this;
}
public function addReplyTo($email, $name = '') {
if (empty($this->guts['reply-to'])) {
$this->guts['reply-to'] = array();
}
$this->guts['reply-to'][] = array(
'email' => $email,
'name' => $name,
);
return $this;
}
public function addTos(array $emails) {
foreach ($emails as $email) {
$this->guts['tos'][] = $email;
}
return $this;
}
public function addCCs(array $emails) {
foreach ($emails as $email) {
$this->guts['ccs'][] = $email;
}
return $this;
}
public function addAttachment($data, $filename, $mimetype) {
$this->guts['attachments'][] = array(
'data' => $data,
'filename' => $filename,
'mimetype' => $mimetype
);
return $this;
}
public function addHeader($header_name, $header_value) {
$this->guts['headers'][] = array($header_name, $header_value);
return $this;
}
public function setBody($body) {
$this->guts['body'] = $body;
return $this;
}
public function setSubject($subject) {
$this->guts['subject'] = $subject;
return $this;
}
public function setIsHTML($is_html) {
$this->guts['is-html'] = $is_html;
return $this;
}
public function supportsMessageIDHeader() {
return $this->config['supportsMessageIDHeader'];
}
public function send() {
$this->guts['did-send'] = true;
return true;
}
public function getGuts() {
return $this->guts;
}
}
diff --git a/src/applications/metamta/storage/mail/PhabricatorMetaMTAAttachment.php b/src/applications/metamta/storage/mail/PhabricatorMetaMTAAttachment.php
index f6023b609a..c9b130d48c 100644
--- a/src/applications/metamta/storage/mail/PhabricatorMetaMTAAttachment.php
+++ b/src/applications/metamta/storage/mail/PhabricatorMetaMTAAttachment.php
@@ -1,56 +1,56 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorMetaMTAAttachment {
+final class PhabricatorMetaMTAAttachment {
protected $data;
protected $filename;
protected $mimetype;
public function __construct($data, $filename, $mimetype) {
$this->setData($data);
$this->setFilename($filename);
$this->setMimeType($mimetype);
}
public function getData() {
return $this->data;
}
public function setData($data) {
$this->data = $data;
return $this;
}
public function getFilename() {
return $this->filename;
}
public function setFilename($filename) {
$this->filename = $filename;
return $this;
}
public function getMimeType() {
return $this->mimetype;
}
public function setMimeType($mimetype) {
$this->mimetype = $mimetype;
return $this;
}
}
diff --git a/src/applications/metamta/storage/mail/PhabricatorMetaMTAMail.php b/src/applications/metamta/storage/mail/PhabricatorMetaMTAMail.php
index ff333bed1d..c38eca13f1 100644
--- a/src/applications/metamta/storage/mail/PhabricatorMetaMTAMail.php
+++ b/src/applications/metamta/storage/mail/PhabricatorMetaMTAMail.php
@@ -1,637 +1,637 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* See #394445 for an explanation of why this thing even exists.
*/
-class PhabricatorMetaMTAMail extends PhabricatorMetaMTADAO {
+final class PhabricatorMetaMTAMail extends PhabricatorMetaMTADAO {
const STATUS_QUEUE = 'queued';
const STATUS_SENT = 'sent';
const STATUS_FAIL = 'fail';
const STATUS_VOID = 'void';
const MAX_RETRIES = 250;
const RETRY_DELAY = 5;
protected $parameters;
protected $status;
protected $message;
protected $retryCount;
protected $nextRetry;
protected $relatedPHID;
public function __construct() {
$this->status = self::STATUS_QUEUE;
$this->retryCount = 0;
$this->nextRetry = time();
$this->parameters = array();
parent::__construct();
}
public function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'parameters' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
protected function setParam($param, $value) {
$this->parameters[$param] = $value;
return $this;
}
protected function getParam($param) {
return idx($this->parameters, $param);
}
/**
* Set tags (@{class:MetaMTANotificationType} constants) which identify the
* content of this mail in a general way. These tags are used to allow users
* to opt out of receiving certain types of mail, like updates when a task's
* projects change.
*
* @param list<const> List of @{class:MetaMTANotificationType} constants.
* @return this
*/
public function setMailTags(array $tags) {
$this->setParam('mailtags', $tags);
return $this;
}
/**
* In Gmail, conversations will be broken if you reply to a thread and the
* server sends back a response without referencing your Message-ID, even if
* it references a Message-ID earlier in the thread. To avoid this, use the
* parent email's message ID explicitly if it's available. This overwrites the
* "In-Reply-To" and "References" headers we would otherwise generate. This
* needs to be set whenever an action is triggered by an email message. See
* T251 for more details.
*
* @param string The "Message-ID" of the email which precedes this one.
* @return this
*/
public function setParentMessageID($id) {
$this->setParam('parent-message-id', $id);
return $this;
}
public function getParentMessageID() {
return $this->getParam('parent-message-id');
}
public function getSubject() {
return $this->getParam('subject');
}
public function addTos(array $phids) {
$phids = array_unique($phids);
$this->setParam('to', $phids);
return $this;
}
public function addCCs(array $phids) {
$phids = array_unique($phids);
$this->setParam('cc', $phids);
return $this;
}
public function addHeader($name, $value) {
$this->parameters['headers'][$name] = $value;
return $this;
}
public function addAttachment(PhabricatorMetaMTAAttachment $attachment) {
$this->parameters['attachments'][] = $attachment;
return $this;
}
public function getAttachments() {
return $this->getParam('attachments');
}
public function setAttachments(array $attachments) {
$this->setParam('attachments', $attachments);
return $this;
}
public function setFrom($from) {
$this->setParam('from', $from);
return $this;
}
public function setReplyTo($reply_to) {
$this->setParam('reply-to', $reply_to);
return $this;
}
public function setSubject($subject) {
$this->setParam('subject', $subject);
return $this;
}
public function setBody($body) {
$this->setParam('body', $body);
return $this;
}
public function getBody() {
return $this->getParam('body');
}
public function setIsHTML($html) {
$this->setParam('is-html', $html);
return $this;
}
public function getSimulatedFailureCount() {
return nonempty($this->getParam('simulated-failures'), 0);
}
public function setSimulatedFailureCount($count) {
$this->setParam('simulated-failures', $count);
return $this;
}
public function getWorkerTaskID() {
return $this->getParam('worker-task');
}
public function setWorkerTaskID($id) {
$this->setParam('worker-task', $id);
return $this;
}
/**
* Flag that this is an auto-generated bulk message and should have bulk
* headers added to it if appropriate. Broadly, this means some flavor of
* "Precedence: bulk" or similar, but is implementation and configuration
* dependent.
*
* @param bool True if the mail is automated bulk mail.
* @return this
*/
public function setIsBulk($is_bulk) {
$this->setParam('is-bulk', $is_bulk);
return $this;
}
/**
* Use this method to set an ID used for message threading. MetaMTA will
* set appropriate headers (Message-ID, In-Reply-To, References and
* Thread-Index) based on the capabilities of the underlying mailer.
*
* @param string Unique identifier, appropriate for use in a Message-ID,
* In-Reply-To or References headers.
* @param bool If true, indicates this is the first message in the thread.
* @return this
*/
public function setThreadID($thread_id, $is_first_message = false) {
$this->setParam('thread-id', $thread_id);
$this->setParam('is-first-message', $is_first_message);
return $this;
}
/**
* Save a newly created mail to the database and attempt to send it
* immediately if the server is configured for immediate sends. When
* applications generate new mail they should generally use this method to
* deliver it. If the server doesn't use immediate sends, this has the same
* effect as calling save(): the mail will eventually be delivered by the
* MetaMTA daemon.
*
* @return this
*/
public function saveAndSend() {
$ret = null;
if (PhabricatorEnv::getEnvConfig('metamta.send-immediately')) {
$ret = $this->sendNow();
} else {
$ret = $this->save();
}
return $ret;
}
protected function didWriteData() {
parent::didWriteData();
if (!$this->getWorkerTaskID()) {
$mailer_task = new PhabricatorWorkerTask();
$mailer_task->setTaskClass('PhabricatorMetaMTAWorker');
$mailer_task->setData($this->getID());
$mailer_task->save();
$this->setWorkerTaskID($mailer_task->getID());
$this->save();
}
}
public function buildDefaultMailer() {
$class_name = PhabricatorEnv::getEnvConfig('metamta.mail-adapter');
PhutilSymbolLoader::loadClass($class_name);
return newv($class_name, array());
}
/**
* Attempt to deliver an email immediately, in this process.
*
* @param bool Try to deliver this email even if it has already been
* delivered or is in backoff after a failed delivery attempt.
* @param PhabricatorMailImplementationAdapter Use a specific mail adapter,
* instead of the default.
*
* @return void
*/
public function sendNow(
$force_send = false,
PhabricatorMailImplementationAdapter $mailer = null) {
if ($mailer === null) {
$mailer = $this->buildDefaultMailer();
}
if (!$force_send) {
if ($this->getStatus() != self::STATUS_QUEUE) {
throw new Exception("Trying to send an already-sent mail!");
}
if (time() < $this->getNextRetry()) {
throw new Exception("Trying to send an email before next retry!");
}
}
try {
$parameters = $this->parameters;
$phids = array();
foreach ($parameters as $key => $value) {
switch ($key) {
case 'from':
case 'to':
case 'cc':
if (!is_array($value)) {
$value = array($value);
}
foreach (array_filter($value) as $phid) {
$phids[] = $phid;
}
break;
}
}
$handles = id(new PhabricatorObjectHandleData($phids))
->loadHandles();
$exclude = array();
$params = $this->parameters;
$default = PhabricatorEnv::getEnvConfig('metamta.default-address');
if (empty($params['from'])) {
$mailer->setFrom($default);
} else {
$from = $params['from'];
// If the user has set their preferences to not send them email about
// things they do, exclude them from being on To or Cc.
$from_user = id(new PhabricatorUser())->loadOneWhere(
'phid = %s',
$from);
if ($from_user) {
$pref_key = PhabricatorUserPreferences::PREFERENCE_NO_SELF_MAIL;
$exclude_self = $from_user
->loadPreferences()
->getPreference($pref_key);
if ($exclude_self) {
$exclude[$from] = true;
}
}
if (!PhabricatorEnv::getEnvConfig('metamta.can-send-as-user')) {
$handle = $handles[$from];
if (empty($params['reply-to'])) {
$params['reply-to'] = $handle->getEmail();
$params['reply-to-name'] = $handle->getFullName();
}
$mailer->setFrom(
$default,
$handle->getFullName());
unset($params['from']);
}
}
$is_first = idx($params, 'is-first-message');
unset($params['is-first-message']);
$is_threaded = (bool)idx($params, 'thread-id');
$reply_to_name = idx($params, 'reply-to-name', '');
unset($params['reply-to-name']);
$add_cc = array();
$add_to = array();
foreach ($params as $key => $value) {
switch ($key) {
case 'from':
$mailer->setFrom($handles[$value]->getEmail());
break;
case 'reply-to':
$mailer->addReplyTo($value, $reply_to_name);
break;
case 'to':
$emails = $this->getDeliverableEmailsFromHandles(
$value,
$handles,
$exclude);
if ($emails) {
$add_to = $emails;
}
break;
case 'cc':
$emails = $this->getDeliverableEmailsFromHandles(
$value,
$handles,
$exclude);
if ($emails) {
$add_cc = $emails;
}
break;
case 'headers':
foreach ($value as $header_key => $header_value) {
// NOTE: If we have \n in a header, SES rejects the email.
$header_value = str_replace("\n", " ", $header_value);
$mailer->addHeader($header_key, $header_value);
}
break;
case 'attachments':
foreach ($value as $attachment) {
$mailer->addAttachment(
$attachment->getData(),
$attachment->getFilename(),
$attachment->getMimeType()
);
}
break;
case 'body':
$mailer->setBody($value);
break;
case 'subject':
if ($is_threaded) {
$add_re = PhabricatorEnv::getEnvConfig('metamta.re-prefix');
// If this message has a single recipient, respect their "Re:"
// preference. Otherwise, use the global setting.
$to = idx($params, 'to', array());
$cc = idx($params, 'cc', array());
if (count($to) == 1 && count($cc) == 0) {
$user = id(new PhabricatorUser())->loadOneWhere(
'phid = %s',
head($to));
if ($user) {
$prefs = $user->loadPreferences();
$pref_key = PhabricatorUserPreferences::PREFERENCE_RE_PREFIX;
$add_re = $prefs->getPreference($pref_key, $add_re);
}
}
if ($add_re) {
$value = 'Re: '.$value;
}
}
$mailer->setSubject($value);
break;
case 'is-html':
if ($value) {
$mailer->setIsHTML(true);
}
break;
case 'is-bulk':
if ($value) {
if (PhabricatorEnv::getEnvConfig('metamta.precedence-bulk')) {
$mailer->addHeader('Precedence', 'bulk');
}
}
break;
case 'thread-id':
if ($is_first && $mailer->supportsMessageIDHeader()) {
$mailer->addHeader('Message-ID', $value);
} else {
$in_reply_to = $value;
$references = array($value);
$parent_id = $this->getParentMessageID();
if ($parent_id) {
$in_reply_to = $parent_id;
// By RFC 2822, the most immediate parent should appear last
// in the "References" header, so this order is intentional.
$references[] = $parent_id;
}
$references = implode(' ', $references);
$mailer->addHeader('In-Reply-To', $in_reply_to);
$mailer->addHeader('References', $references);
}
$thread_index = $this->generateThreadIndex($value, $is_first);
$mailer->addHeader('Thread-Index', $thread_index);
break;
case 'mailtags':
// Handled below.
break;
default:
// Just discard.
}
}
$mailer->addHeader('X-Phabricator-Sent-This-Message', 'Yes');
$mailer->addHeader('X-Mail-Transport-Agent', 'MetaMTA');
// If the message has mailtags, filter out any recipients who don't want
// to receive this type of mail.
$mailtags = $this->getParam('mailtags');
if ($mailtags && ($add_to || $add_cc)) {
$tag_header = array();
foreach ($mailtags as $mailtag) {
$tag_header[] = '<'.$mailtag.'>';
}
$tag_header = implode(', ', $tag_header);
$mailer->addHeader('X-Phabricator-Mail-Tags', $tag_header);
$exclude = array();
$all_recipients = array_merge(
array_keys($add_to),
array_keys($add_cc));
$all_prefs = id(new PhabricatorUserPreferences())->loadAllWhere(
'userPHID in (%Ls)',
$all_recipients);
$all_prefs = mpull($all_prefs, null, 'getUserPHID');
foreach ($all_recipients as $recipient) {
$prefs = idx($all_prefs, $recipient);
if (!$prefs) {
continue;
}
$user_mailtags = $prefs->getPreference(
PhabricatorUserPreferences::PREFERENCE_MAILTAGS,
array());
// The user must have elected to receive mail for at least one
// of the mailtags.
$send = false;
foreach ($mailtags as $tag) {
if (idx($user_mailtags, $tag, true)) {
$send = true;
break;
}
}
if (!$send) {
$exclude[$recipient] = true;
}
}
$add_to = array_diff_key($add_to, $exclude);
$add_cc = array_diff_key($add_cc, $exclude);
}
if ($add_to) {
$mailer->addTos($add_to);
if ($add_cc) {
$mailer->addCCs($add_cc);
}
} else if ($add_cc) {
// If we have CC addresses but no "to" address, promote the CCs to
// "to".
$mailer->addTos($add_cc);
} else {
$this->setStatus(self::STATUS_VOID);
$this->setMessage(
"Message has no valid recipients: all To/CC are disabled or ".
"configured not to receive this mail.");
return $this->save();
}
} catch (Exception $ex) {
$this->setStatus(self::STATUS_FAIL);
$this->setMessage($ex->getMessage());
return $this->save();
}
if ($this->getRetryCount() < $this->getSimulatedFailureCount()) {
$ok = false;
$error = 'Simulated failure.';
} else {
try {
$ok = $mailer->send();
$error = null;
} catch (Exception $ex) {
$ok = false;
$error = $ex->getMessage()."\n".$ex->getTraceAsString();
}
}
if (!$ok) {
$this->setMessage($error);
if ($this->getRetryCount() > self::MAX_RETRIES) {
$this->setStatus(self::STATUS_FAIL);
} else {
$this->setRetryCount($this->getRetryCount() + 1);
$next_retry = time() + ($this->getRetryCount() * self::RETRY_DELAY);
$this->setNextRetry($next_retry);
}
} else {
$this->setStatus(self::STATUS_SENT);
}
return $this->save();
}
public static function getReadableStatus($status_code) {
static $readable = array(
self::STATUS_QUEUE => "Queued for Delivery",
self::STATUS_FAIL => "Delivery Failed",
self::STATUS_SENT => "Sent",
self::STATUS_VOID => "Void",
);
$status_code = coalesce($status_code, '?');
return idx($readable, $status_code, $status_code);
}
private function generateThreadIndex($seed, $is_first_mail) {
// When threading, Outlook ignores the 'References' and 'In-Reply-To'
// headers that most clients use. Instead, it uses a custom 'Thread-Index'
// header. The format of this header is something like this (from
// camel-exchange-folder.c in Evolution Exchange):
/* A new post to a folder gets a 27-byte-long thread index. (The value
* is apparently unique but meaningless.) Each reply to a post gets a
* 32-byte-long thread index whose first 27 bytes are the same as the
* parent's thread index. Each reply to any of those gets a
* 37-byte-long thread index, etc. The Thread-Index header contains a
* base64 representation of this value.
*/
// The specific implementation uses a 27-byte header for the first email
// a recipient receives, and a random 5-byte suffix (32 bytes total)
// thereafter. This means that all the replies are (incorrectly) siblings,
// but it would be very difficult to keep track of the entire tree and this
// gets us reasonable client behavior.
$base = substr(md5($seed), 0, 27);
if (!$is_first_mail) {
// Not totally sure, but it seems like outlook orders replies by
// thread-index rather than timestamp, so to get these to show up in the
// right order we use the time as the last 4 bytes.
$base .= ' '.pack('N', time());
}
return base64_encode($base);
}
private function getDeliverableEmailsFromHandles(
array $phids,
array $handles,
array $exclude) {
$emails = array();
foreach ($phids as $phid) {
if ($handles[$phid]->isDisabled()) {
continue;
}
if (!$handles[$phid]->isComplete()) {
continue;
}
if (isset($exclude[$phid])) {
continue;
}
$emails[$phid] = $handles[$phid]->getEmail();
}
return $emails;
}
}
diff --git a/src/applications/metamta/storage/mail/__tests__/PhabricatorMetaMTAMailTestCase.php b/src/applications/metamta/storage/mail/__tests__/PhabricatorMetaMTAMailTestCase.php
index 54bcc2c625..94681b75e6 100644
--- a/src/applications/metamta/storage/mail/__tests__/PhabricatorMetaMTAMailTestCase.php
+++ b/src/applications/metamta/storage/mail/__tests__/PhabricatorMetaMTAMailTestCase.php
@@ -1,77 +1,77 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorMetaMTAMailTestCase extends PhabricatorTestCase {
+final class PhabricatorMetaMTAMailTestCase extends PhabricatorTestCase {
public function testThreadIDHeaders() {
$this->runThreadIDHeadersWithConfiguration(true, true);
$this->runThreadIDHeadersWithConfiguration(true, false);
$this->runThreadIDHeadersWithConfiguration(false, true);
$this->runThreadIDHeadersWithConfiguration(false, false);
}
private function runThreadIDHeadersWithConfiguration(
$supports_message_id,
$is_first_mail) {
$mailer = new PhabricatorMailImplementationTestAdapter(
array(
'supportsMessageIDHeader' => $supports_message_id,
));
$thread_id = '<somethread-12345@somedomain.tld>';
$mail = new PhabricatorMetaMTAMail();
$mail->setThreadID($thread_id, $is_first_mail);
$mail->sendNow($force = true, $mailer);
$guts = $mailer->getGuts();
$dict = ipull($guts['headers'], 1, 0);
if ($is_first_mail && $supports_message_id) {
$expect_message_id = true;
$expect_in_reply_to = false;
$expect_references = false;
} else {
$expect_message_id = false;
$expect_in_reply_to = true;
$expect_references = true;
}
$case = "<message-id = ".($supports_message_id ? 'Y' : 'N').", ".
"first = ".($is_first_mail ? 'Y' : 'N').">";
$this->assertEqual(
true,
isset($dict['Thread-Index']),
"Expect Thread-Index header for case {$case}.");
$this->assertEqual(
$expect_message_id,
isset($dict['Message-ID']),
"Expectation about existence of Message-ID header for case {$case}.");
$this->assertEqual(
$expect_in_reply_to,
isset($dict['In-Reply-To']),
"Expectation about existence of In-Reply-To header for case {$case}.");
$this->assertEqual(
$expect_references,
isset($dict['References']),
"Expectation about existence of References header for case {$case}.");
}
}
diff --git a/src/applications/metamta/storage/mailinglist/PhabricatorMetaMTAMailingList.php b/src/applications/metamta/storage/mailinglist/PhabricatorMetaMTAMailingList.php
index 49bbff7bfd..c429ccbd0d 100644
--- a/src/applications/metamta/storage/mailinglist/PhabricatorMetaMTAMailingList.php
+++ b/src/applications/metamta/storage/mailinglist/PhabricatorMetaMTAMailingList.php
@@ -1,37 +1,37 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorMetaMTAMailingList extends PhabricatorMetaMTADAO {
+final class PhabricatorMetaMTAMailingList extends PhabricatorMetaMTADAO {
protected $name;
protected $phid;
protected $email;
protected $uri;
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_MLST);
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
) + parent::getConfiguration();
}
}
diff --git a/src/applications/metamta/storage/receivedmail/PhabricatorMetaMTAReceivedMail.php b/src/applications/metamta/storage/receivedmail/PhabricatorMetaMTAReceivedMail.php
index 5cbb63b97e..4c1769546f 100644
--- a/src/applications/metamta/storage/receivedmail/PhabricatorMetaMTAReceivedMail.php
+++ b/src/applications/metamta/storage/receivedmail/PhabricatorMetaMTAReceivedMail.php
@@ -1,299 +1,299 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
+final class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
protected $headers = array();
protected $bodies = array();
protected $attachments = array();
protected $relatedPHID;
protected $authorPHID;
protected $message;
public function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'headers' => self::SERIALIZATION_JSON,
'bodies' => self::SERIALIZATION_JSON,
'attachments' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function setHeaders(array $headers) {
// Normalize headers to lowercase.
$normalized = array();
foreach ($headers as $name => $value) {
$normalized[strtolower($name)] = $value;
}
$this->headers = $normalized;
return $this;
}
public function getMessageID() {
return idx($this->headers, 'message-id');
}
public function getSubject() {
return idx($this->headers, 'subject');
}
public function processReceivedMail() {
$to = idx($this->headers, 'to');
$to = $this->getRawEmailAddress($to);
$from = idx($this->headers, 'from');
$create_task = PhabricatorEnv::getEnvConfig(
'metamta.maniphest.public-create-email');
if ($create_task && $to == $create_task) {
$receiver = new ManiphestTask();
$user = $this->lookupPublicUser();
if ($user) {
$this->setAuthorPHID($user->getPHID());
} else {
$default_author = PhabricatorEnv::getEnvConfig(
'metamta.maniphest.default-public-author');
if ($default_author) {
$user = id(new PhabricatorUser())->loadOneWhere(
'username = %s',
$default_author);
if ($user) {
$receiver->setOriginalEmailSource($from);
} else {
throw new Exception(
"Phabricator is misconfigured, the configuration key ".
"'metamta.maniphest.default-public-author' is set to user ".
"'{$default_author}' but that user does not exist.");
}
} else {
// TODO: We should probably bounce these since from the user's
// perspective their email vanishes into a black hole.
return $this->setMessage("Invalid public user '{$from}'.")->save();
}
}
$receiver->setAuthorPHID($user->getPHID());
$receiver->setPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
$editor = new ManiphestTransactionEditor();
$handler = $editor->buildReplyHandler($receiver);
$handler->setActor($user);
$handler->receiveEmail($this);
$this->setRelatedPHID($receiver->getPHID());
$this->setMessage('OK');
return $this->save();
}
// We've already stripped this, so look for an object address which has
// a format like: D291+291+b0a41ca848d66dcc@example.com
$matches = null;
$single_handle_prefix = PhabricatorEnv::getEnvConfig(
'metamta.single-reply-handler-prefix');
$prefixPattern = ($single_handle_prefix)
? preg_quote($single_handle_prefix, '/') . '\+'
: '';
$pattern = "/^{$prefixPattern}((?:D|T|C)\d+)\+([\w]+)\+([a-f0-9]{16})@/U";
$ok = preg_match(
$pattern,
$to,
$matches);
if (!$ok) {
return $this->setMessage("Unrecognized 'to' format: {$to}")->save();
}
$receiver_name = $matches[1];
$user_id = $matches[2];
$hash = $matches[3];
if ($user_id == 'public') {
if (!PhabricatorEnv::getEnvConfig('metamta.public-replies')) {
return $this->setMessage("Public replies not enabled.")->save();
}
$user = $this->lookupPublicUser();
if (!$user) {
return $this->setMessage("Invalid public user '{$from}'.")->save();
}
$use_user_hash = false;
} else {
$user = id(new PhabricatorUser())->load($user_id);
if (!$user) {
return $this->setMessage("Invalid private user '{$user_id}'.")->save();
}
$use_user_hash = true;
}
if ($user->getIsDisabled()) {
return $this->setMessage("User '{$user_id}' is disabled")->save();
}
$this->setAuthorPHID($user->getPHID());
$receiver = self::loadReceiverObject($receiver_name);
if (!$receiver) {
return $this->setMessage("Invalid object '{$receiver_name}'")->save();
}
$this->setRelatedPHID($receiver->getPHID());
if ($use_user_hash) {
// This is a private reply-to address, check that the user hash is
// correct.
$check_phid = $user->getPHID();
} else {
// This is a public reply-to address, check that the object hash is
// correct.
$check_phid = $receiver->getPHID();
}
$expect_hash = self::computeMailHash($receiver->getMailKey(), $check_phid);
// See note at computeOldMailHash().
$old_hash = self::computeOldMailHash($receiver->getMailKey(), $check_phid);
if ($expect_hash != $hash && $old_hash != $hash) {
return $this->setMessage("Invalid mail hash!")->save();
}
if ($receiver instanceof ManiphestTask) {
$editor = new ManiphestTransactionEditor();
$handler = $editor->buildReplyHandler($receiver);
} else if ($receiver instanceof DifferentialRevision) {
$handler = DifferentialMail::newReplyHandlerForRevision($receiver);
} else if ($receiver instanceof PhabricatorRepositoryCommit) {
$handler = PhabricatorAuditCommentEditor::newReplyHandlerForCommit(
$receiver);
}
$handler->setActor($user);
$handler->receiveEmail($this);
$this->setMessage('OK');
return $this->save();
}
public function getCleanTextBody() {
$body = idx($this->bodies, 'text');
$parser = new PhabricatorMetaMTAEmailBodyParser($body);
return $parser->stripQuotedText();
}
public static function loadReceiverObject($receiver_name) {
if (!$receiver_name) {
return null;
}
$receiver_type = $receiver_name[0];
$receiver_id = substr($receiver_name, 1);
$class_obj = null;
switch ($receiver_type) {
case 'T':
$class_obj = newv('ManiphestTask', array());
break;
case 'D':
$class_obj = newv('DifferentialRevision', array());
break;
case 'C':
$class_obj = newv('PhabricatorRepositoryCommit', array());
break;
default:
return null;
}
return $class_obj->load($receiver_id);
}
public static function computeMailHash($mail_key, $phid) {
$global_mail_key = PhabricatorEnv::getEnvConfig('phabricator.mail-key');
$hash = PhabricatorHash::digest($mail_key.$global_mail_key.$phid);
return substr($hash, 0, 16);
}
public static function computeOldMailHash($mail_key, $phid) {
// TODO: Remove this method entirely in a couple of months. We've moved from
// plain sha1 to sha1+hmac to make the codebase more auditable for good uses
// of hash functions, but still accept the old hashes on email replies to
// avoid breaking things. Once we've been sending only hmac hashes for a
// while, remove this and start rejecting old hashes. See T547.
$global_mail_key = PhabricatorEnv::getEnvConfig('phabricator.mail-key');
$hash = sha1($mail_key.$global_mail_key.$phid);
return substr($hash, 0, 16);
}
/**
* Strip an email address down to the actual user@domain.tld part if
* necessary, since sometimes it will have formatting like
* '"Abraham Lincoln" <alincoln@logcab.in>'.
*/
private function getRawEmailAddress($address) {
$matches = null;
$ok = preg_match('/<(.*)>/', $address, $matches);
if ($ok) {
$address = $matches[1];
}
return $address;
}
private function lookupPublicUser() {
$from = idx($this->headers, 'from');
$from = $this->getRawEmailAddress($from);
$user = id(new PhabricatorUser())->loadOneWhere(
'email = %s',
$from);
// If Phabricator is configured to allow "Reply-To" authentication, try
// the "Reply-To" address if we failed to match the "From" address.
$config_key = 'metamta.insecure-auth-with-reply-to';
$allow_reply_to = PhabricatorEnv::getEnvConfig($config_key);
if (!$user && $allow_reply_to) {
$reply_to = idx($this->headers, 'reply-to');
$reply_to = $this->getRawEmailAddress($reply_to);
if ($reply_to) {
$user = id(new PhabricatorUser())->loadOneWhere(
'email = %s',
$reply_to);
}
}
return $user;
}
}
diff --git a/src/applications/owners/query/path/PhabricatorOwnerPathQuery.php b/src/applications/owners/query/path/PhabricatorOwnerPathQuery.php
index ef2d6b2e79..7551f800c6 100644
--- a/src/applications/owners/query/path/PhabricatorOwnerPathQuery.php
+++ b/src/applications/owners/query/path/PhabricatorOwnerPathQuery.php
@@ -1,52 +1,52 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorOwnerPathQuery {
+final class PhabricatorOwnerPathQuery {
public static function loadAffectedPaths(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {
$drequest = self::buildDiffusionRequest($repository, $commit);
$path_query = DiffusionPathChangeQuery::newFromDiffusionRequest(
$drequest);
$paths = $path_query->loadChanges();
$result = array();
foreach ($paths as $path) {
$basic_path = '/' . $path->getPath();
if ($path->getFileType() == DifferentialChangeType::FILE_DIRECTORY) {
$basic_path = rtrim($basic_path, '/') . '/';
}
$result[] = $basic_path;
}
return $result;
}
private static function buildDiffusionRequest(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {
return DiffusionRequest::newFromAphrontRequestDictionary(
array(
'callsign' => $repository->getCallsign(),
'commit' => $commit->getCommitIdentifier(),
));
}
}
diff --git a/src/applications/owners/storage/owner/PhabricatorOwnersOwner.php b/src/applications/owners/storage/owner/PhabricatorOwnersOwner.php
index dfdcbb7685..4d1cef4ecc 100644
--- a/src/applications/owners/storage/owner/PhabricatorOwnersOwner.php
+++ b/src/applications/owners/storage/owner/PhabricatorOwnersOwner.php
@@ -1,39 +1,39 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorOwnersOwner extends PhabricatorOwnersDAO {
+final class PhabricatorOwnersOwner extends PhabricatorOwnersDAO {
protected $packageID;
protected $userPHID;
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
public static function loadAllForPackages(array $packages) {
if (!$packages) {
return array();
}
return id(new PhabricatorOwnersOwner())->loadAllWhere(
'packageID IN (%Ls)',
mpull($packages, 'getID'));
}
}
diff --git a/src/applications/owners/storage/package/PhabricatorOwnersPackage.php b/src/applications/owners/storage/package/PhabricatorOwnersPackage.php
index b4ada00bef..17d1941b49 100644
--- a/src/applications/owners/storage/package/PhabricatorOwnersPackage.php
+++ b/src/applications/owners/storage/package/PhabricatorOwnersPackage.php
@@ -1,254 +1,254 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorOwnersPackage extends PhabricatorOwnersDAO {
+final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO {
protected $phid;
protected $name;
protected $auditingEnabled;
protected $description;
protected $primaryOwnerPHID;
private $unsavedOwners;
private $unsavedPaths;
public function getConfiguration() {
return array(
// This information is better available from the history table.
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_AUX_PHID => true,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID('OPKG');
}
public function attachUnsavedOwners(array $owners) {
$this->unsavedOwners = $owners;
return $this;
}
public function attachUnsavedPaths(array $paths) {
$this->unsavedPaths = $paths;
return $this;
}
public function loadOwners() {
if (!$this->getID()) {
return array();
}
return id(new PhabricatorOwnersOwner())->loadAllWhere(
'packageID = %d',
$this->getID());
}
public function loadPaths() {
if (!$this->getID()) {
return array();
}
return id(new PhabricatorOwnersPath())->loadAllWhere(
'packageID = %d',
$this->getID());
}
public static function loadAffectedPackages(
PhabricatorRepository $repository,
array $paths) {
if (!$paths) {
return array();
}
$fragments = array(
'/' => true,
);
foreach ($paths as $path) {
$fragments += self::splitPath($path);
}
return self::loadPackagesForPaths($repository, array_keys($fragments));
}
public static function loadOwningPackages($repository, $path) {
if (empty($path)) {
return array();
}
$fragments = self::splitPath($path);
return self::loadPackagesForPaths($repository, array_keys($fragments), 1);
}
private static function loadPackagesForPaths(
PhabricatorRepository $repository,
array $paths,
$limit = 0) {
$package = new PhabricatorOwnersPackage();
$path = new PhabricatorOwnersPath();
$conn = $package->establishConnection('r');
$repository_clause = qsprintf($conn, 'AND p.repositoryPHID = %s',
$repository->getPHID());
$limit_clause = '';
if (!empty($limit)) {
$limit_clause = qsprintf($conn, 'LIMIT %d', $limit);
}
$data = queryfx_all(
$conn,
'SELECT pkg.id FROM %T pkg JOIN %T p ON p.packageID = pkg.id
WHERE p.path IN (%Ls) %Q ORDER BY LENGTH(p.path) DESC %Q',
$package->getTableName(),
$path->getTableName(),
$paths,
$repository_clause,
$limit_clause);
$ids = ipull($data, 'id');
if (empty($ids)) {
return array();
}
$order = array();
foreach ($ids as $id) {
if (empty($order[$id])) {
$order[$id] = true;
}
}
$packages = $package->loadAllWhere('id in (%Ld)', array_keys($order));
$packages = array_select_keys($packages, array_keys($order));
return $packages;
}
public function save() {
// TODO: Transactions!
$ret = parent::save();
if ($this->unsavedOwners) {
$new_owners = array_fill_keys($this->unsavedOwners, true);
$cur_owners = array();
foreach ($this->loadOwners() as $owner) {
if (empty($new_owners[$owner->getUserPHID()])) {
$owner->delete();
continue;
}
$cur_owners[$owner->getUserPHID()] = true;
}
$add_owners = array_diff_key($new_owners, $cur_owners);
foreach ($add_owners as $phid => $ignored) {
$owner = new PhabricatorOwnersOwner();
$owner->setPackageID($this->getID());
$owner->setUserPHID($phid);
$owner->save();
}
unset($this->unsavedOwners);
}
if ($this->unsavedPaths) {
$new_paths = igroup($this->unsavedPaths, 'repositoryPHID', 'path');
$cur_paths = $this->loadPaths();
foreach ($cur_paths as $key => $path) {
if (empty($new_paths[$path->getRepositoryPHID()][$path->getPath()])) {
$path->delete();
unset($cur_paths[$key]);
}
}
$cur_paths = mgroup($cur_paths, 'getRepositoryPHID', 'getPath');
foreach ($new_paths as $repository_phid => $paths) {
// get repository object for path validation
$repository = id(new PhabricatorRepository())->loadOneWhere(
'phid = %s',
$repository_phid);
if (!$repository) {
continue;
}
foreach ($paths as $path => $ignored) {
$path = ltrim($path, '/');
// build query to validate path
$drequest = DiffusionRequest::newFromAphrontRequestDictionary(
array(
'callsign' => $repository->getCallsign(),
'path' => ':/'.$path,
));
$query = DiffusionBrowseQuery::newFromDiffusionRequest($drequest);
$query->needValidityOnly(true);
$valid = $query->loadPaths();
$is_directory = true;
if (!$valid) {
switch ($query->getReasonForEmptyResultSet()) {
case DiffusionBrowseQuery::REASON_IS_FILE:
$valid = true;
$is_directory = false;
break;
case DiffusionBrowseQuery::REASON_IS_EMPTY:
$valid = true;
break;
}
}
if ($is_directory && substr($path, -1) != '/') {
$path .= '/';
}
if (substr($path, 0, 1) != '/') {
$path = '/'.$path;
}
if (empty($cur_paths[$repository_phid][$path]) && $valid) {
$obj = new PhabricatorOwnersPath();
$obj->setPackageID($this->getID());
$obj->setRepositoryPHID($repository_phid);
$obj->setPath($path);
$obj->save();
}
}
}
unset($this->unsavedPaths);
}
return $ret;
}
public function delete() {
foreach ($this->loadOwners() as $owner) {
$owner->delete();
}
foreach ($this->loadPaths() as $path) {
$path->delete();
}
return parent::delete();
}
private static function splitPath($path) {
$result = array();
$trailing_slash = preg_match('@/$@', $path) ? '/' : '';
$path = trim($path, '/');
$parts = explode('/', $path);
while (count($parts)) {
$result['/'.implode('/', $parts).$trailing_slash] = true;
$trailing_slash = '/';
array_pop($parts);
}
return $result;
}
}
diff --git a/src/applications/owners/storage/path/PhabricatorOwnersPath.php b/src/applications/owners/storage/path/PhabricatorOwnersPath.php
index cf8fbca3ee..cc0de81f36 100644
--- a/src/applications/owners/storage/path/PhabricatorOwnersPath.php
+++ b/src/applications/owners/storage/path/PhabricatorOwnersPath.php
@@ -1,31 +1,31 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorOwnersPath extends PhabricatorOwnersDAO {
+final class PhabricatorOwnersPath extends PhabricatorOwnersDAO {
protected $packageID;
protected $repositoryPHID;
protected $path;
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
}
diff --git a/src/applications/paste/storage/paste/PhabricatorPaste.php b/src/applications/paste/storage/paste/PhabricatorPaste.php
index c7a75f0669..38a8d1fbe3 100644
--- a/src/applications/paste/storage/paste/PhabricatorPaste.php
+++ b/src/applications/paste/storage/paste/PhabricatorPaste.php
@@ -1,39 +1,39 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorPaste extends PhabricatorPasteDAO {
+final class PhabricatorPaste extends PhabricatorPasteDAO {
protected $phid;
protected $title;
protected $authorPHID;
protected $filePHID;
protected $language;
protected $parentPHID;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_PSTE);
}
}
diff --git a/src/applications/people/storage/log/PhabricatorUserLog.php b/src/applications/people/storage/log/PhabricatorUserLog.php
index 8d3be1eed8..b76db56ba4 100644
--- a/src/applications/people/storage/log/PhabricatorUserLog.php
+++ b/src/applications/people/storage/log/PhabricatorUserLog.php
@@ -1,109 +1,109 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorUserLog extends PhabricatorUserDAO {
+final class PhabricatorUserLog extends PhabricatorUserDAO {
const ACTION_LOGIN = 'login';
const ACTION_LOGOUT = 'logout';
const ACTION_LOGIN_FAILURE = 'login-fail';
const ACTION_RESET_PASSWORD = 'reset-pass';
const ACTION_CREATE = 'create';
const ACTION_ADMIN = 'admin';
const ACTION_DISABLE = 'disable';
const ACTION_CONDUIT_CERTIFICATE = 'conduit-cert';
const ACTION_CONDUIT_CERTIFICATE_FAILURE = 'conduit-cert-fail';
protected $actorPHID;
protected $userPHID;
protected $action;
protected $oldValue;
protected $newValue;
protected $details = array();
protected $remoteAddr;
protected $session;
public static function newLog(
PhabricatorUser $actor = null,
PhabricatorUser $user = null,
$action) {
$log = new PhabricatorUserLog();
if ($actor) {
$log->setActorPHID($actor->getPHID());
}
if ($user) {
$log->setUserPHID($user->getPHID());
} else {
$log->setUserPHID('');
}
if ($action) {
$log->setAction($action);
}
return $log;
}
public static function loadRecentEventsFromThisIP($action, $timespan) {
return id(new PhabricatorUserLog())->loadAllWhere(
'action = %s AND remoteAddr = %s AND dateCreated > %d
ORDER BY dateCreated DESC',
$action,
idx($_SERVER, 'REMOTE_ADDR'),
time() - $timespan);
}
public function save() {
if (!$this->remoteAddr) {
$this->remoteAddr = idx($_SERVER, 'REMOTE_ADDR');
}
if (!$this->session) {
$this->setSession(idx($_COOKIE, 'phsid'));
}
$this->details['host'] = php_uname('n');
$this->details['user_agent'] = idx($_SERVER, 'HTTP_USER_AGENT');
return parent::save();
}
public function setSession($session) {
// Store the hash of the session, not the actual session key, so that
// seeing the logs doesn't compromise all the sessions which appear in
// them. This just prevents casual leaks, like in a screenshot.
if (strlen($session)) {
$this->session = PhabricatorHash::digest($session);
}
return $this;
}
public function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'oldValue' => self::SERIALIZATION_JSON,
'newValue' => self::SERIALIZATION_JSON,
'details' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
}
diff --git a/src/applications/people/storage/preferences/PhabricatorUserPreferences.php b/src/applications/people/storage/preferences/PhabricatorUserPreferences.php
index 89aa6c9780..95cf66e269 100644
--- a/src/applications/people/storage/preferences/PhabricatorUserPreferences.php
+++ b/src/applications/people/storage/preferences/PhabricatorUserPreferences.php
@@ -1,55 +1,55 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorUserPreferences extends PhabricatorUserDAO {
+final class PhabricatorUserPreferences extends PhabricatorUserDAO {
const PREFERENCE_MONOSPACED = 'monospaced';
const PREFERENCE_EDITOR = 'editor';
const PREFERENCE_TITLES = 'titles';
const PREFERENCE_RE_PREFIX = 're-prefix';
const PREFERENCE_NO_SELF_MAIL = 'self-mail';
const PREFERENCE_MAILTAGS = 'mailtags';
protected $userPHID;
protected $preferences = array();
public function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'preferences' => self::SERIALIZATION_JSON,
),
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
public function getPreference($key, $default = null) {
return idx($this->preferences, $key, $default);
}
public function setPreference($key, $value) {
$this->preferences[$key] = $value;
return $this;
}
public function unsetPreference($key) {
unset($this->preferences[$key]);
return $this;
}
}
diff --git a/src/applications/people/storage/profile/PhabricatorUserProfile.php b/src/applications/people/storage/profile/PhabricatorUserProfile.php
index d269e40fb9..757f0e27ea 100644
--- a/src/applications/people/storage/profile/PhabricatorUserProfile.php
+++ b/src/applications/people/storage/profile/PhabricatorUserProfile.php
@@ -1,26 +1,26 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorUserProfile extends PhabricatorUserDAO {
+final class PhabricatorUserProfile extends PhabricatorUserDAO {
protected $userPHID;
protected $title;
protected $blurb;
protected $profileImagePHID;
}
diff --git a/src/applications/people/storage/user/PhabricatorUser.php b/src/applications/people/storage/user/PhabricatorUser.php
index 0d932d705f..062afc280f 100644
--- a/src/applications/people/storage/user/PhabricatorUser.php
+++ b/src/applications/people/storage/user/PhabricatorUser.php
@@ -1,526 +1,526 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorUser extends PhabricatorUserDAO {
+final class PhabricatorUser extends PhabricatorUserDAO {
const SESSION_TABLE = 'phabricator_session';
const NAMETOKEN_TABLE = 'user_nametoken';
protected $phid;
protected $userName;
protected $realName;
protected $email;
protected $passwordSalt;
protected $passwordHash;
protected $profileImagePHID;
protected $timezoneIdentifier = '';
protected $consoleEnabled = 0;
protected $consoleVisible = 0;
protected $consoleTab = '';
protected $conduitCertificate;
protected $isSystemAgent = 0;
protected $isAdmin = 0;
protected $isDisabled = 0;
private $preferences = null;
protected function readField($field) {
switch ($field) {
case 'profileImagePHID':
return nonempty(
$this->profileImagePHID,
PhabricatorEnv::getEnvConfig('user.default-profile-image-phid'));
case 'timezoneIdentifier':
// If the user hasn't set one, guess the server's time.
return nonempty(
$this->timezoneIdentifier,
date_default_timezone_get());
// Make sure these return booleans.
case 'isAdmin':
return (bool)$this->isAdmin;
case 'isDisabled':
return (bool)$this->isDisabled;
case 'isSystemAgent':
return (bool)$this->isSystemAgent;
default:
return parent::readField($field);
}
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_PARTIAL_OBJECTS => true,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_USER);
}
public function setPassword($password) {
if (!$this->getPHID()) {
throw new Exception(
"You can not set a password for an unsaved user because their PHID ".
"is a salt component in the password hash.");
}
if (!strlen($password)) {
$this->setPasswordHash('');
} else {
$this->setPasswordSalt(md5(mt_rand()));
$hash = $this->hashPassword($password);
$this->setPasswordHash($hash);
}
return $this;
}
public function isLoggedIn() {
return !($this->getPHID() === null);
}
public function save() {
if (!$this->getConduitCertificate()) {
$this->setConduitCertificate($this->generateConduitCertificate());
}
$result = parent::save();
$this->updateNameTokens();
PhabricatorSearchUserIndexer::indexUser($this);
return $result;
}
private function generateConduitCertificate() {
return Filesystem::readRandomCharacters(255);
}
public function comparePassword($password) {
if (!strlen($password)) {
return false;
}
if (!strlen($this->getPasswordHash())) {
return false;
}
$password = $this->hashPassword($password);
return ($password === $this->getPasswordHash());
}
private function hashPassword($password) {
$password = $this->getUsername().
$password.
$this->getPHID().
$this->getPasswordSalt();
for ($ii = 0; $ii < 1000; $ii++) {
$password = md5($password);
}
return $password;
}
const CSRF_CYCLE_FREQUENCY = 3600;
const CSRF_TOKEN_LENGTH = 16;
const EMAIL_CYCLE_FREQUENCY = 86400;
const EMAIL_TOKEN_LENGTH = 24;
public function getCSRFToken($offset = 0) {
return $this->generateToken(
time() + (self::CSRF_CYCLE_FREQUENCY * $offset),
self::CSRF_CYCLE_FREQUENCY,
PhabricatorEnv::getEnvConfig('phabricator.csrf-key'),
self::CSRF_TOKEN_LENGTH);
}
public function validateCSRFToken($token) {
if (!$this->getPHID()) {
return true;
}
// When the user posts a form, we check that it contains a valid CSRF token.
// Tokens cycle each hour (every CSRF_CYLCE_FREQUENCY seconds) and we accept
// either the current token, the next token (users can submit a "future"
// token if you have two web frontends that have some clock skew) or any of
// the last 6 tokens. This means that pages are valid for up to 7 hours.
// There is also some Javascript which periodically refreshes the CSRF
// tokens on each page, so theoretically pages should be valid indefinitely.
// However, this code may fail to run (if the user loses their internet
// connection, or there's a JS problem, or they don't have JS enabled).
// Choosing the size of the window in which we accept old CSRF tokens is
// an issue of balancing concerns between security and usability. We could
// choose a very narrow (e.g., 1-hour) window to reduce vulnerability to
// attacks using captured CSRF tokens, but it's also more likely that real
// users will be affected by this, e.g. if they close their laptop for an
// hour, open it back up, and try to submit a form before the CSRF refresh
// can kick in. Since the user experience of submitting a form with expired
// CSRF is often quite bad (you basically lose data, or it's a big pain to
// recover at least) and I believe we gain little additional protection
// by keeping the window very short (the overwhelming value here is in
// preventing blind attacks, and most attacks which can capture CSRF tokens
// can also just capture authentication information [sniffing networks]
// or act as the user [xss]) the 7 hour default seems like a reasonable
// balance. Other major platforms have much longer CSRF token lifetimes,
// like Rails (session duration) and Django (forever), which suggests this
// is a reasonable analysis.
$csrf_window = 6;
for ($ii = -$csrf_window; $ii <= 1; $ii++) {
$valid = $this->getCSRFToken($ii);
if ($token == $valid) {
return true;
}
}
return false;
}
private function generateToken($epoch, $frequency, $key, $len) {
$time_block = floor($epoch / $frequency);
$vec = $this->getPHID().$this->getPasswordHash().$key.$time_block;
return substr(PhabricatorHash::digest($vec), 0, $len);
}
/**
* Issue a new session key to this user. Phabricator supports different
* types of sessions (like "web" and "conduit") and each session type may
* have multiple concurrent sessions (this allows a user to be logged in on
* multiple browsers at the same time, for instance).
*
* Note that this method is transport-agnostic and does not set cookies or
* issue other types of tokens, it ONLY generates a new session key.
*
* You can configure the maximum number of concurrent sessions for various
* session types in the Phabricator configuration.
*
* @param string Session type, like "web".
* @return string Newly generated session key.
*/
public function establishSession($session_type) {
$conn_w = $this->establishConnection('w');
if (strpos($session_type, '-') !== false) {
throw new Exception("Session type must not contain hyphen ('-')!");
}
// We allow multiple sessions of the same type, so when a caller requests
// a new session of type "web", we give them the first available session in
// "web-1", "web-2", ..., "web-N", up to some configurable limit. If none
// of these sessions is available, we overwrite the oldest session and
// reissue a new one in its place.
$session_limit = 1;
switch ($session_type) {
case 'web':
$session_limit = PhabricatorEnv::getEnvConfig('auth.sessions.web');
break;
case 'conduit':
$session_limit = PhabricatorEnv::getEnvConfig('auth.sessions.conduit');
break;
default:
throw new Exception("Unknown session type '{$session_type}'!");
}
$session_limit = (int)$session_limit;
if ($session_limit <= 0) {
throw new Exception(
"Session limit for '{$session_type}' must be at least 1!");
}
// NOTE: Session establishment is sensitive to race conditions, as when
// piping `arc` to `arc`:
//
// arc export ... | arc paste ...
//
// To avoid this, we overwrite an old session only if it hasn't been
// re-established since we read it.
// Consume entropy to generate a new session key, forestalling the eventual
// heat death of the universe.
$session_key = Filesystem::readRandomCharacters(40);
// Load all the currently active sessions.
$sessions = queryfx_all(
$conn_w,
'SELECT type, sessionKey, sessionStart FROM %T
WHERE userPHID = %s AND type LIKE %>',
PhabricatorUser::SESSION_TABLE,
$this->getPHID(),
$session_type.'-');
$sessions = ipull($sessions, null, 'type');
$sessions = isort($sessions, 'sessionStart');
$existing_sessions = array_keys($sessions);
// UNGUARDED WRITES: Logging-in users don't have CSRF stuff yet.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$retries = 0;
while (true) {
// Choose which 'type' we'll actually establish, i.e. what number we're
// going to append to the basic session type. To do this, just check all
// the numbers sequentially until we find an available session.
$establish_type = null;
for ($ii = 1; $ii <= $session_limit; $ii++) {
$try_type = $session_type.'-'.$ii;
if (!in_array($try_type, $existing_sessions)) {
$establish_type = $try_type;
$expect_key = $session_key;
$existing_sessions[] = $try_type;
// Ensure the row exists so we can issue an update below. We don't
// care if we race here or not.
queryfx(
$conn_w,
'INSERT IGNORE INTO %T (userPHID, type, sessionKey, sessionStart)
VALUES (%s, %s, %s, 0)',
self::SESSION_TABLE,
$this->getPHID(),
$establish_type,
$session_key);
break;
}
}
// If we didn't find an available session, choose the oldest session and
// overwrite it.
if (!$establish_type) {
$oldest = reset($sessions);
$establish_type = $oldest['type'];
$expect_key = $oldest['sessionKey'];
}
// This is so that we'll only overwrite the session if it hasn't been
// refreshed since we read it. If it has, the session key will be
// different and we know we're racing other processes. Whichever one
// won gets the session, we go back and try again.
queryfx(
$conn_w,
'UPDATE %T SET sessionKey = %s, sessionStart = UNIX_TIMESTAMP()
WHERE userPHID = %s AND type = %s AND sessionKey = %s',
self::SESSION_TABLE,
$session_key,
$this->getPHID(),
$establish_type,
$expect_key);
if ($conn_w->getAffectedRows()) {
// The update worked, so the session is valid.
break;
} else {
// We know this just got grabbed, so don't try it again.
unset($sessions[$establish_type]);
}
if (++$retries > $session_limit) {
throw new Exception("Failed to establish a session!");
}
}
$log = PhabricatorUserLog::newLog(
$this,
$this,
PhabricatorUserLog::ACTION_LOGIN);
$log->setDetails(
array(
'session_type' => $session_type,
'session_issued' => $establish_type,
));
$log->setSession($session_key);
$log->save();
return $session_key;
}
public function destroySession($session_key) {
$conn_w = $this->establishConnection('w');
queryfx(
$conn_w,
'DELETE FROM %T WHERE userPHID = %s AND sessionKey = %s',
self::SESSION_TABLE,
$this->getPHID(),
$session_key);
}
private function generateEmailToken($offset = 0) {
return $this->generateToken(
time() + ($offset * self::EMAIL_CYCLE_FREQUENCY),
self::EMAIL_CYCLE_FREQUENCY,
PhabricatorEnv::getEnvConfig('phabricator.csrf-key').$this->getEmail(),
self::EMAIL_TOKEN_LENGTH);
}
public function validateEmailToken($token) {
for ($ii = -1; $ii <= 1; $ii++) {
$valid = $this->generateEmailToken($ii);
if ($token == $valid) {
return true;
}
}
return false;
}
public function getEmailLoginURI() {
$token = $this->generateEmailToken();
$uri = PhabricatorEnv::getProductionURI('/login/etoken/'.$token.'/');
$uri = new PhutilURI($uri);
return $uri->alter('email', $this->getEmail());
}
public function loadPreferences() {
if ($this->preferences) {
return $this->preferences;
}
$preferences = id(new PhabricatorUserPreferences())->loadOneWhere(
'userPHID = %s',
$this->getPHID());
if (!$preferences) {
$preferences = new PhabricatorUserPreferences();
$preferences->setUserPHID($this->getPHID());
$default_dict = array(
PhabricatorUserPreferences::PREFERENCE_TITLES => 'glyph',
PhabricatorUserPreferences::PREFERENCE_EDITOR => '',
PhabricatorUserPreferences::PREFERENCE_MONOSPACED => '');
$preferences->setPreferences($default_dict);
}
$this->preferences = $preferences;
return $preferences;
}
public function loadEditorLink($path,
$line,
PhabricatorRepository $repository) {
$editor = $this->loadPreferences()->getPreference(
PhabricatorUserPreferences::PREFERENCE_EDITOR);
if ($editor) {
return strtr($editor, array(
'%f' => phutil_escape_uri($path),
'%l' => phutil_escape_uri($line),
'%r' => phutil_escape_uri($repository->getCallsign()),
));
}
}
private static function tokenizeName($name) {
if (function_exists('mb_strtolower')) {
$name = mb_strtolower($name, 'UTF-8');
} else {
$name = strtolower($name);
}
$name = trim($name);
if (!strlen($name)) {
return array();
}
return preg_split('/\s+/', $name);
}
/**
* Populate the nametoken table, which used to fetch typeahead results. When
* a user types "linc", we want to match "Abraham Lincoln" from on-demand
* typeahead sources. To do this, we need a separate table of name fragments.
*/
public function updateNameTokens() {
$tokens = array_merge(
self::tokenizeName($this->getRealName()),
self::tokenizeName($this->getUserName()));
$tokens = array_unique($tokens);
$table = self::NAMETOKEN_TABLE;
$conn_w = $this->establishConnection('w');
$sql = array();
foreach ($tokens as $token) {
$sql[] = qsprintf(
$conn_w,
'(%d, %s)',
$this->getID(),
$token);
}
queryfx(
$conn_w,
'DELETE FROM %T WHERE userID = %d',
$table,
$this->getID());
if ($sql) {
queryfx(
$conn_w,
'INSERT INTO %T (userID, token) VALUES %Q',
$table,
implode(', ', $sql));
}
}
public function sendWelcomeEmail(PhabricatorUser $admin) {
$admin_username = $admin->getUserName();
$admin_realname = $admin->getRealName();
$user_username = $this->getUserName();
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$base_uri = PhabricatorEnv::getProductionURI('/');
$uri = $this->getEmailLoginURI();
$body = <<<EOBODY
Welcome to Phabricator!
{$admin_username} ({$admin_realname}) has created an account for you.
Username: {$user_username}
To login to Phabricator, follow this link and set a password:
{$uri}
After you have set a password, you can login in the future by going here:
{$base_uri}
EOBODY;
if (!$is_serious) {
$body .= <<<EOBODY
Love,
Phabricator
EOBODY;
}
$mail = id(new PhabricatorMetaMTAMail())
->addTos(array($this->getPHID()))
->setSubject('[Phabricator] Welcome to Phabricator')
->setBody($body)
->setFrom($admin->getPHID())
->saveAndSend();
}
public static function validateUsername($username) {
return (bool)preg_match('/^[a-zA-Z0-9]+$/', $username);
}
}
diff --git a/src/applications/people/storage/useroauthinfo/PhabricatorUserOAuthInfo.php b/src/applications/people/storage/useroauthinfo/PhabricatorUserOAuthInfo.php
index d5647228f9..6d84f0a703 100644
--- a/src/applications/people/storage/useroauthinfo/PhabricatorUserOAuthInfo.php
+++ b/src/applications/people/storage/useroauthinfo/PhabricatorUserOAuthInfo.php
@@ -1,60 +1,60 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorUserOAuthInfo extends PhabricatorUserDAO {
+final class PhabricatorUserOAuthInfo extends PhabricatorUserDAO {
const TOKEN_STATUS_NONE = 'none';
const TOKEN_STATUS_GOOD = 'good';
const TOKEN_STATUS_FAIL = 'fail';
const TOKEN_STATUS_EXPIRED = 'xpyr';
protected $userID;
protected $oauthProvider;
protected $oauthUID;
protected $accountURI;
protected $accountName;
protected $token;
protected $tokenExpires;
protected $tokenScope;
protected $tokenStatus;
public function getTokenStatus() {
if (!$this->token) {
return self::TOKEN_STATUS_NONE;
}
if ($this->tokenExpires && $this->tokenExpires <= time()) {
return self::TOKEN_STATUS_EXPIRED;
}
return $this->tokenStatus;
}
public static function getReadableTokenStatus($status) {
static $map = array(
self::TOKEN_STATUS_NONE => 'No Token',
self::TOKEN_STATUS_GOOD => 'Token Good',
self::TOKEN_STATUS_FAIL => 'Token Failed',
self::TOKEN_STATUS_EXPIRED => 'Token Expired',
);
return idx($map, $status, 'Unknown');
}
}
diff --git a/src/applications/people/storage/usersshkey/PhabricatorUserSSHKey.php b/src/applications/people/storage/usersshkey/PhabricatorUserSSHKey.php
index f1c2940e4e..ecedfc104b 100644
--- a/src/applications/people/storage/usersshkey/PhabricatorUserSSHKey.php
+++ b/src/applications/people/storage/usersshkey/PhabricatorUserSSHKey.php
@@ -1,37 +1,37 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorUserSSHKey extends PhabricatorUserDAO {
+final class PhabricatorUserSSHKey extends PhabricatorUserDAO {
protected $userPHID;
protected $name;
protected $keyType;
protected $keyBody;
protected $keyHash;
protected $keyComment;
public function getEntireKey() {
$parts = array(
$this->getKeyType(),
$this->getKeyBody(),
$this->getKeyComment(),
);
return trim(implode(' ', $parts));
}
}
diff --git a/src/applications/phid/handle/PhabricatorObjectHandle.php b/src/applications/phid/handle/PhabricatorObjectHandle.php
index 95262f5486..00c1bd50e8 100644
--- a/src/applications/phid/handle/PhabricatorObjectHandle.php
+++ b/src/applications/phid/handle/PhabricatorObjectHandle.php
@@ -1,231 +1,231 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorObjectHandle {
+final class PhabricatorObjectHandle {
private $uri;
private $phid;
private $type;
private $name;
private $email;
private $fullName;
private $imageURI;
private $timestamp;
private $alternateID;
private $status = 'open';
private $complete;
private $disabled;
public function setURI($uri) {
$this->uri = $uri;
return $this;
}
public function getURI() {
return $this->uri;
}
public function setPHID($phid) {
$this->phid = $phid;
return $this;
}
public function getPHID() {
return $this->phid;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function setStatus($status) {
$this->status = $status;
return $this;
}
public function getStatus() {
return $this->status;
}
public function setFullName($full_name) {
$this->fullName = $full_name;
return $this;
}
public function getFullName() {
if ($this->fullName !== null) {
return $this->fullName;
}
return $this->getName();
}
public function setType($type) {
$this->type = $type;
return $this;
}
public function getType() {
return $this->type;
}
public function setEmail($email) {
$this->email = $email;
return $this;
}
public function getEmail() {
return $this->email;
}
public function setImageURI($uri) {
$this->imageURI = $uri;
return $this;
}
public function getImageURI() {
return $this->imageURI;
}
public function setTimestamp($timestamp) {
$this->timestamp = $timestamp;
return $this;
}
public function getTimestamp() {
return $this->timestamp;
}
public function setAlternateID($alternate_id) {
$this->alternateID = $alternate_id;
return $this;
}
public function getAlternateID() {
return $this->alternateID;
}
public function getTypeName() {
static $map = array(
PhabricatorPHIDConstants::PHID_TYPE_USER => 'User',
PhabricatorPHIDConstants::PHID_TYPE_TASK => 'Task',
PhabricatorPHIDConstants::PHID_TYPE_DREV => 'Revision',
PhabricatorPHIDConstants::PHID_TYPE_CMIT => 'Commit',
PhabricatorPHIDConstants::PHID_TYPE_WIKI => 'Phriction',
);
return idx($map, $this->getType());
}
/**
* Set whether or not the underlying object is complete. See
* @{method:getComplete} for an explanation of what it means to be complete.
*
* @param bool True if the handle represents a complete object.
* @return this
*/
public function setComplete($complete) {
$this->complete = $complete;
return $this;
}
/**
* Determine if the handle represents an object which was completely loaded
* (i.e., the underlying object exists) vs an object which could not be
* completely loaded (e.g., the type or data for the PHID could not be
* identified or located).
*
* Basically, @{class:PhabricatorObjectHandleData} gives you back a handle for
* any PHID you give it, but it gives you a complete handle only for valid
* PHIDs.
*
* @return bool True if the handle represents a complete object.
*/
public function isComplete() {
return $this->complete;
}
/**
* Set whether or not the underlying object is disabled. See
* @{method:getDisabled} for an explanation of what it means to be disabled.
*
* @param bool True if the handle represents a disabled object.
* @return this
*/
public function setDisabled($disabled) {
$this->disabled = $disabled;
return $this;
}
/**
* Determine if the handle represents an object which has been disabled --
* for example, disabled users, archived projects, etc. These objects are
* complete and exist, but should be excluded from some system interactions
* (for instance, they usually should not appear in typeaheads, and should
* not have mail/notifications delivered to or about them).
*
* @return bool True if the handle represents a disabled object.
*/
public function isDisabled() {
return $this->disabled;
}
public function renderLink() {
$name = $this->getLinkName();
$class = null;
if ($this->status != PhabricatorObjectHandleStatus::STATUS_OPEN) {
$class .= ' handle-status-'.$this->status;
}
if ($this->disabled) {
$class .= ' handle-disabled';
}
return phutil_render_tag(
'a',
array(
'href' => $this->getURI(),
'class' => $class,
),
phutil_escape_html($name));
}
public function getLinkName() {
switch ($this->getType()) {
case PhabricatorPHIDConstants::PHID_TYPE_USER:
case PhabricatorPHIDConstants::PHID_TYPE_CMIT:
$name = $this->getName();
break;
default:
$name = $this->getFullName();
break;
}
return $name;
}
}
diff --git a/src/applications/phid/handle/const/status/PhabricatorObjectHandleStatus.php b/src/applications/phid/handle/const/status/PhabricatorObjectHandleStatus.php
index a565845a2f..4b9e421e0c 100644
--- a/src/applications/phid/handle/const/status/PhabricatorObjectHandleStatus.php
+++ b/src/applications/phid/handle/const/status/PhabricatorObjectHandleStatus.php
@@ -1,25 +1,25 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorObjectHandleStatus
+final class PhabricatorObjectHandleStatus
extends PhabricatorObjectHandleConstants {
const STATUS_OPEN = 'open';
const STATUS_CLOSED = 'closed';
}
diff --git a/src/applications/phid/handle/data/PhabricatorObjectHandleData.php b/src/applications/phid/handle/data/PhabricatorObjectHandleData.php
index 1aca83d594..fa545780bb 100644
--- a/src/applications/phid/handle/data/PhabricatorObjectHandleData.php
+++ b/src/applications/phid/handle/data/PhabricatorObjectHandleData.php
@@ -1,504 +1,504 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorObjectHandleData {
+final class PhabricatorObjectHandleData {
private $phids;
public function __construct(array $phids) {
$this->phids = array_unique($phids);
}
public function loadObjects() {
$types = array();
foreach ($this->phids as $phid) {
$type = $this->lookupType($phid);
$types[$type][] = $phid;
}
$objects = array_fill_keys($this->phids, null);
foreach ($types as $type => $phids) {
switch ($type) {
case PhabricatorPHIDConstants::PHID_TYPE_USER:
$user_dao = newv('PhabricatorUser', array());
$users = $user_dao->loadAllWhere(
'phid in (%Ls)',
$phids);
foreach ($users as $user) {
$objects[$user->getPHID()] = $user;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_CMIT:
$commit_dao = newv('PhabricatorRepositoryCommit', array());
$commits = $commit_dao->loadAllWhere(
'phid IN (%Ls)',
$phids);
$commit_data = array();
if ($commits) {
$data_dao = newv('PhabricatorRepositoryCommitData', array());
$commit_data = $data_dao->loadAllWhere(
'commitID IN (%Ld)',
mpull($commits, 'getID'));
$commit_data = mpull($commit_data, null, 'getCommitID');
}
foreach ($commits as $commit) {
$data = idx($commit_data, $commit->getID());
if ($data) {
$commit->attachCommitData($data);
$objects[$commit->getPHID()] = $commit;
} else {
// If we couldn't load the commit data, just act as though we
// couldn't load the object at all so we don't load half an object.
}
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_TASK:
$task_dao = newv('ManiphestTask', array());
$tasks = $task_dao->loadAllWhere(
'phid IN (%Ls)',
$phids);
foreach ($tasks as $task) {
$objects[$task->getPHID()] = $task;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_DREV:
$revision_dao = newv('DifferentialRevision', array());
$revisions = $revision_dao->loadAllWhere(
'phid IN (%Ls)',
$phids);
foreach ($revisions as $revision) {
$objects[$revision->getPHID()] = $revision;
}
break;
}
}
return $objects;
}
public function loadHandles() {
$types = array();
foreach ($this->phids as $phid) {
$type = $this->lookupType($phid);
$types[$type][] = $phid;
}
$handles = array();
$external_loaders = PhabricatorEnv::getEnvConfig('phid.external-loaders');
foreach ($types as $type => $phids) {
switch ($type) {
case PhabricatorPHIDConstants::PHID_TYPE_MAGIC:
// Black magic!
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
switch ($phid) {
case ManiphestTaskOwner::OWNER_UP_FOR_GRABS:
$handle->setName('Up For Grabs');
$handle->setFullName('upforgrabs (Up For Grabs)');
$handle->setComplete(true);
break;
case ManiphestTaskOwner::PROJECT_NO_PROJECT:
$handle->setName('No Project');
$handle->setFullName('noproject (No Project)');
$handle->setComplete(true);
break;
default:
$handle->setName('Foul Magicks');
break;
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_USER:
$class = 'PhabricatorUser';
PhutilSymbolLoader::loadClass($class);
$object = newv($class, array());
$users = $object->loadAllWhere('phid IN (%Ls)', $phids);
$users = mpull($users, null, 'getPHID');
$image_phids = mpull($users, 'getProfileImagePHID');
$image_phids = array_unique(array_filter($image_phids));
$images = array();
if ($image_phids) {
$images = id(new PhabricatorFile())->loadAllWhere(
'phid IN (%Ls)',
$image_phids);
$images = mpull($images, 'getBestURI', 'getPHID');
}
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($users[$phid])) {
$handle->setName('Unknown User');
} else {
$user = $users[$phid];
$handle->setName($user->getUsername());
$handle->setURI('/p/'.$user->getUsername().'/');
$handle->setEmail($user->getEmail());
$handle->setFullName(
$user->getUsername().' ('.$user->getRealName().')');
$handle->setAlternateID($user->getID());
$handle->setComplete(true);
$handle->setDisabled($user->getIsDisabled());
$img_uri = idx($images, $user->getProfileImagePHID());
if ($img_uri) {
$handle->setImageURI($img_uri);
}
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_MLST:
$class = 'PhabricatorMetaMTAMailingList';
PhutilSymbolLoader::loadClass($class);
$object = newv($class, array());
$lists = $object->loadAllWhere('phid IN (%Ls)', $phids);
$lists = mpull($lists, null, 'getPHID');
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($lists[$phid])) {
$handle->setName('Unknown Mailing List');
} else {
$list = $lists[$phid];
$handle->setEmail($list->getEmail());
$handle->setName($list->getName());
$handle->setURI($list->getURI());
$handle->setFullName($list->getName());
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_DREV:
$class = 'DifferentialRevision';
PhutilSymbolLoader::loadClass($class);
$object = newv($class, array());
$revs = $object->loadAllWhere('phid in (%Ls)', $phids);
$revs = mpull($revs, null, 'getPHID');
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($revs[$phid])) {
$handle->setName('Unknown Revision');
} else {
$rev = $revs[$phid];
$handle->setName($rev->getTitle());
$handle->setURI('/D'.$rev->getID());
$handle->setFullName('D'.$rev->getID().': '.$rev->getTitle());
$handle->setComplete(true);
$status = $rev->getStatus();
if (($status == ArcanistDifferentialRevisionStatus::COMMITTED) ||
($status == ArcanistDifferentialRevisionStatus::ABANDONED)) {
$closed = PhabricatorObjectHandleStatus::STATUS_CLOSED;
$handle->setStatus($closed);
}
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_CMIT:
$class = 'PhabricatorRepositoryCommit';
PhutilSymbolLoader::loadClass($class);
$object = newv($class, array());
$commits = $object->loadAllWhere('phid in (%Ls)', $phids);
$commits = mpull($commits, null, 'getPHID');
$repository_ids = array();
$callsigns = array();
if ($commits) {
$repository_ids = mpull($commits, 'getRepositoryID');
$repositories = id(new PhabricatorRepository())->loadAllWhere(
'id in (%Ld)', array_unique($repository_ids));
$callsigns = mpull($repositories, 'getCallsign');
}
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($commits[$phid]) ||
!isset($callsigns[$repository_ids[$phid]])) {
$handle->setName('Unknown Commit');
} else {
$commit = $commits[$phid];
$callsign = $callsigns[$repository_ids[$phid]];
$repository = $repositories[$repository_ids[$phid]];
$commit_identifier = $commit->getCommitIdentifier();
// In case where the repository for the commit was deleted,
// we don't have have info about the repository anymore.
if ($repository) {
$vcs = $repository->getVersionControlSystem();
$type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
$type_hg = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL;
$is_git = ($vcs == $type_git);
$is_hg = ($vcs == $type_hg);
if ($is_git || $is_hg) {
$short_identifier = substr($commit_identifier, 0, 12);
} else {
$short_identifier = $commit_identifier;
}
$handle->setName('r'.$callsign.$short_identifier);
} else {
$handle->setName('Commit '.'r'.$callsign.$commit_identifier);
}
$handle->setURI('/r'.$callsign.$commit_identifier);
$handle->setFullName('r'.$callsign.$commit_identifier);
$handle->setTimestamp($commit->getEpoch());
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_TASK:
$class = 'ManiphestTask';
PhutilSymbolLoader::loadClass($class);
$object = newv($class, array());
$tasks = $object->loadAllWhere('phid in (%Ls)', $phids);
$tasks = mpull($tasks, null, 'getPHID');
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($tasks[$phid])) {
$handle->setName('Unknown Revision');
} else {
$task = $tasks[$phid];
$handle->setName($task->getTitle());
$handle->setURI('/T'.$task->getID());
$handle->setFullName('T'.$task->getID().': '.$task->getTitle());
$handle->setComplete(true);
$handle->setAlternateID($task->getID());
if ($task->getStatus() != ManiphestTaskStatus::STATUS_OPEN) {
$closed = PhabricatorObjectHandleStatus::STATUS_CLOSED;
$handle->setStatus($closed);
}
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_FILE:
$class = 'PhabricatorFile';
PhutilSymbolLoader::loadClass($class);
$object = newv($class, array());
$files = $object->loadAllWhere('phid IN (%Ls)', $phids);
$files = mpull($files, null, 'getPHID');
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($files[$phid])) {
$handle->setName('Unknown File');
} else {
$file = $files[$phid];
$handle->setName($file->getName());
$handle->setURI($file->getBestURI());
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_PROJ:
$class = 'PhabricatorProject';
PhutilSymbolLoader::loadClass($class);
$object = newv($class, array());
$projects = $object->loadAllWhere('phid IN (%Ls)', $phids);
$projects = mpull($projects, null, 'getPHID');
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($projects[$phid])) {
$handle->setName('Unknown Project');
} else {
$project = $projects[$phid];
$handle->setName($project->getName());
$handle->setURI('/project/view/'.$project->getID().'/');
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_REPO:
$class = 'PhabricatorRepository';
PhutilSymbolLoader::loadClass($class);
$object = newv($class, array());
$repositories = $object->loadAllWhere('phid in (%Ls)', $phids);
$repositories = mpull($repositories, null, 'getPHID');
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($repositories[$phid])) {
$handle->setName('Unknown Repository');
} else {
$repository = $repositories[$phid];
$handle->setName($repository->getCallsign());
$handle->setURI('/diffusion/'.$repository->getCallsign().'/');
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_OPKG:
$class = 'PhabricatorOwnersPackage';
PhutilSymbolLoader::loadClass($class);
$object = newv($class, array());
$packages = $object->loadAllWhere('phid in (%Ls)', $phids);
$packages = mpull($packages, null, 'getPHID');
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($packages[$phid])) {
$handle->setName('Unknown Package');
} else {
$package = $packages[$phid];
$handle->setName($package->getName());
$handle->setURI('/owners/package/'.$package->getID().'/');
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_APRJ:
$project_dao = newv('PhabricatorRepositoryArcanistProject', array());
$projects = $project_dao->loadAllWhere(
'phid IN (%Ls)',
$phids);
$projects = mpull($projects, null, 'getPHID');
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($projects[$phid])) {
$handle->setName('Unknown Arcanist Project');
} else {
$project = $projects[$phid];
$handle->setName($project->getName());
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_WIKI:
$document_dao = newv('PhrictionDocument', array());
$content_dao = newv('PhrictionContent', array());
$conn = $document_dao->establishConnection('r');
$documents = queryfx_all(
$conn,
'SELECT * FROM %T document JOIN %T content
ON document.contentID = content.id
WHERE document.phid IN (%Ls)',
$document_dao->getTableName(),
$content_dao->getTableName(),
$phids);
$documents = ipull($documents, null, 'phid');
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($documents[$phid])) {
$handle->setName('Unknown Document');
} else {
$info = $documents[$phid];
$handle->setName($info['title']);
$handle->setURI(PhrictionDocument::getSlugURI($info['slug']));
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
default:
$loader = null;
if (isset($external_loaders[$type])) {
$loader = $external_loaders[$type];
} else if (isset($external_loaders['*'])) {
$loader = $external_loaders['*'];
}
if ($loader) {
PhutilSymbolLoader::loadClass($loader);
$object = newv($loader, array());
$handles += $object->loadHandles($phids);
break;
}
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setType($type);
$handle->setPHID($phid);
$handle->setName('Unknown Object');
$handle->setFullName('An Unknown Object');
$handles[$phid] = $handle;
}
break;
}
}
return $handles;
}
private function lookupType($phid) {
$matches = null;
if (preg_match('/^PHID-([^-]{4})-/', $phid, $matches)) {
return $matches[1];
}
return PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN;
}
}
diff --git a/src/applications/phid/handle/view/selector/PhabricatorHandleObjectSelectorDataView.php b/src/applications/phid/handle/view/selector/PhabricatorHandleObjectSelectorDataView.php
index cc69990f63..2c0a14a115 100644
--- a/src/applications/phid/handle/view/selector/PhabricatorHandleObjectSelectorDataView.php
+++ b/src/applications/phid/handle/view/selector/PhabricatorHandleObjectSelectorDataView.php
@@ -1,35 +1,35 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorHandleObjectSelectorDataView {
+final class PhabricatorHandleObjectSelectorDataView {
private $handle;
public function __construct($handle) {
$this->handle = $handle;
}
public function renderData() {
$handle = $this->handle;
return array(
'phid' => $handle->getPHID(),
'name' => $handle->getFullName(),
'uri' => $handle->getURI(),
);
}
}
diff --git a/src/applications/phid/storage/base/PhabricatorPHIDDAO.php b/src/applications/phid/storage/base/PhabricatorPHIDDAO.php
index a133222722..01c88caa52 100644
--- a/src/applications/phid/storage/base/PhabricatorPHIDDAO.php
+++ b/src/applications/phid/storage/base/PhabricatorPHIDDAO.php
@@ -1,25 +1,25 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorPHIDDAO extends PhabricatorLiskDAO {
+abstract class PhabricatorPHIDDAO extends PhabricatorLiskDAO {
public function getApplicationName() {
return 'phid';
}
}
diff --git a/src/applications/phid/storage/phid/PhabricatorPHID.php b/src/applications/phid/storage/phid/PhabricatorPHID.php
index f84e794bcf..a14fe5332d 100644
--- a/src/applications/phid/storage/phid/PhabricatorPHID.php
+++ b/src/applications/phid/storage/phid/PhabricatorPHID.php
@@ -1,47 +1,47 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorPHID extends PhabricatorPHIDDAO {
+final class PhabricatorPHID extends PhabricatorPHIDDAO {
protected $phid;
protected $phidType;
protected $ownerPHID;
protected $parentPHID;
public static function generateNewPHID($type, array $config = array()) {
$owner = idx($config, 'owner');
$parent = idx($config, 'parent');
if (!$type) {
throw new Exception("Can not generate PHID with no type.");
}
$uniq = Filesystem::readRandomCharacters(20);
$phid = 'PHID-'.$type.'-'.$uniq;
$phid_rec = new PhabricatorPHID();
$phid_rec->setPHIDType($type);
$phid_rec->setOwnerPHID($owner);
$phid_rec->setParentPHID($parent);
$phid_rec->setPHID($phid);
$phid_rec->save();
return $phid;
}
}
diff --git a/src/applications/phriction/constants/action/PhrictionActionConstants.php b/src/applications/phriction/constants/action/PhrictionActionConstants.php
index ef92d328b8..5db36e7ada 100644
--- a/src/applications/phriction/constants/action/PhrictionActionConstants.php
+++ b/src/applications/phriction/constants/action/PhrictionActionConstants.php
@@ -1,38 +1,38 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phriction
*/
-class PhrictionActionConstants extends PhrictionConstants {
+final class PhrictionActionConstants extends PhrictionConstants {
const ACTION_CREATE = 'create';
const ACTION_EDIT = 'edit';
const ACTION_DELETE = 'delete';
public static function getActionPastTenseVerb($action) {
static $map = array(
self::ACTION_CREATE => 'created',
self::ACTION_EDIT => 'edited',
self::ACTION_DELETE => 'deleted',
);
return idx($map, $action, "brazenly {$action}'d");
}
}
diff --git a/src/applications/phriction/storage/content/PhrictionContent.php b/src/applications/phriction/storage/content/PhrictionContent.php
index 9e1c152fa3..b03e731906 100644
--- a/src/applications/phriction/storage/content/PhrictionContent.php
+++ b/src/applications/phriction/storage/content/PhrictionContent.php
@@ -1,60 +1,60 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phriction
*/
-class PhrictionContent extends PhrictionDAO {
+final class PhrictionContent extends PhrictionDAO {
protected $id;
protected $documentID;
protected $version;
protected $authorPHID;
protected $title;
protected $slug;
protected $content;
protected $description;
protected $changeType;
protected $changeRef;
public function renderContent() {
$engine = PhabricatorMarkupEngine::newPhrictionMarkupEngine();
$markup = $engine->markupText($this->getContent());
$toc = PhutilRemarkupEngineRemarkupHeaderBlockRule::renderTableOfContents(
$engine);
if ($toc) {
$toc =
'<div class="phabricator-remarkup-toc">'.
'<div class="phabricator-remarkup-toc-header">'.
'Table of Contents'.
'</div>'.
$toc.
'</div>';
}
return
'<div class="phabricator-remarkup">'.
$toc.
$markup.
'</div>';
}
}
diff --git a/src/applications/phriction/storage/document/PhrictionDocument.php b/src/applications/phriction/storage/document/PhrictionDocument.php
index 1a3abaf2d3..19f45fba9a 100644
--- a/src/applications/phriction/storage/document/PhrictionDocument.php
+++ b/src/applications/phriction/storage/document/PhrictionDocument.php
@@ -1,158 +1,158 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phriction
*/
-class PhrictionDocument extends PhrictionDAO {
+final class PhrictionDocument extends PhrictionDAO {
protected $id;
protected $phid;
protected $slug;
protected $depth;
protected $contentID;
protected $status;
private $contentObject;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_WIKI);
}
public static function getSlugURI($slug, $type = 'document') {
static $types = array(
'document' => '/w/',
'history' => '/phriction/history/',
);
if (empty($types[$type])) {
throw new Exception("Unknown URI type '{$type}'!");
}
$prefix = $types[$type];
if ($slug == '/') {
return $prefix;
} else {
return $prefix.$slug;
}
}
public static function normalizeSlug($slug) {
// TODO: We need to deal with unicode at some point, this is just a very
// basic proof-of-concept implementation.
$slug = strtolower($slug);
$slug = preg_replace('@/+@', '/', $slug);
$slug = trim($slug, '/');
$slug = preg_replace('@[^a-z0-9/]+@', '_', $slug);
$slug = trim($slug, '_');
return $slug.'/';
}
public static function getDefaultSlugTitle($slug) {
$parts = explode('/', trim($slug, '/'));
$default_title = end($parts);
$default_title = str_replace('_', ' ', $default_title);
$default_title = ucwords($default_title);
$default_title = nonempty($default_title, 'Untitled Document');
return $default_title;
}
public static function getSlugAncestry($slug) {
$slug = self::normalizeSlug($slug);
if ($slug == '/') {
return array();
}
$ancestors = array(
'/',
);
$slug = explode('/', $slug);
array_pop($slug);
array_pop($slug);
$accumulate = '';
foreach ($slug as $part) {
$accumulate .= $part.'/';
$ancestors[] = $accumulate;
}
return $ancestors;
}
public static function getSlugDepth($slug) {
$slug = self::normalizeSlug($slug);
if ($slug == '/') {
return 0;
} else {
return substr_count($slug, '/');
}
}
public function setSlug($slug) {
$this->slug = self::normalizeSlug($slug);
$this->depth = self::getSlugDepth($slug);
return $this;
}
public function attachContent(PhrictionContent $content) {
$this->contentObject = $content;
return $this;
}
public function getContent() {
if (!$this->contentObject) {
throw new Exception("Attach content with attachContent() first.");
}
return $this->contentObject;
}
public static function isProjectSlug($slug) {
$slug = self::normalizeSlug($slug);
$prefix = 'projects/';
if ($slug == $prefix) {
// The 'projects/' document is not itself a project slug.
return false;
}
return !strncmp($slug, $prefix, strlen($prefix));
}
public static function getProjectSlugIdentifier($slug) {
if (!self::isProjectSlug($slug)) {
throw new Exception("Slug '{$slug}' is not a project slug!");
}
$slug = self::normalizeSlug($slug);
$parts = explode('/', $slug);
return $parts[1].'/';
}
}
diff --git a/src/applications/phriction/storage/document/__tests__/PhrictionDocumentTestCase.php b/src/applications/phriction/storage/document/__tests__/PhrictionDocumentTestCase.php
index 5851755d17..785c13439f 100644
--- a/src/applications/phriction/storage/document/__tests__/PhrictionDocumentTestCase.php
+++ b/src/applications/phriction/storage/document/__tests__/PhrictionDocumentTestCase.php
@@ -1,119 +1,119 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phriction
*/
-class PhrictionDocumentTestCase extends PhabricatorTestCase {
+final class PhrictionDocumentTestCase extends PhabricatorTestCase {
public function testSlugNormalization() {
$slugs = array(
'' => '/',
'/' => '/',
'//' => '/',
'&&&' => '/',
'/derp/' => 'derp/',
'derp' => 'derp/',
'derp//derp' => 'derp/derp/',
'DERP//DERP' => 'derp/derp/',
'a B c' => 'a_b_c/',
);
foreach ($slugs as $slug => $normal) {
$this->assertEqual(
$normal,
PhrictionDocument::normalizeSlug($slug),
"Normalization of '{$slug}'");
}
}
public function testSlugAncestry() {
$slugs = array(
'/' => array(),
'pokemon/' => array('/'),
'pokemon/squirtle/' => array('/', 'pokemon/'),
);
foreach ($slugs as $slug => $ancestry) {
$this->assertEqual(
$ancestry,
PhrictionDocument::getSlugAncestry($slug),
"Ancestry of '{$slug}'");
}
}
public function testSlugDepth() {
$slugs = array(
'/' => 0,
'a/' => 1,
'a/b/' => 2,
'a////b/' => 2,
);
foreach ($slugs as $slug => $depth) {
$this->assertEqual(
$depth,
PhrictionDocument::getSlugDepth($slug),
"Depth of '{$slug}'");
}
}
public function testProjectSlugs() {
$slugs = array(
'/' => false,
'zebra/' => false,
'projects/' => false,
'projects/a/' => true,
'projects/a/b/' => true,
'stuff/projects/a/' => false,
);
foreach ($slugs as $slug => $expect) {
$this->assertEqual(
$expect,
PhrictionDocument::isProjectSlug($slug),
"Is '{$slug}' a project slug?");
}
}
public function testProjectSlugIdentifiers() {
$slugs = array(
'projects/' => null,
'derp/' => null,
'projects/a/' => 'a/',
'projects/a/b/' => 'a/',
);
foreach ($slugs as $slug => $expect) {
$ex = null;
$result = null;
try {
$result = PhrictionDocument::getProjectSlugIdentifier($slug);
} catch (Exception $e) {
$ex = $e;
}
if ($expect === null) {
$this->assertEqual(true, (bool)$ex, "Slug '{$slug}' is invalid.");
} else {
$this->assertEqual($expect, $result, "Slug '{$slug}' identifier.");
}
}
}
}
diff --git a/src/applications/project/storage/affiliation/PhabricatorProjectAffiliation.php b/src/applications/project/storage/affiliation/PhabricatorProjectAffiliation.php
index a059682bbe..b8d67a47d5 100644
--- a/src/applications/project/storage/affiliation/PhabricatorProjectAffiliation.php
+++ b/src/applications/project/storage/affiliation/PhabricatorProjectAffiliation.php
@@ -1,39 +1,39 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorProjectAffiliation extends PhabricatorProjectDAO {
+final class PhabricatorProjectAffiliation extends PhabricatorProjectDAO {
protected $projectPHID;
protected $userPHID;
protected $role;
protected $isOwner = 0;
public static function loadAllForProjectPHIDs($phids) {
if (!$phids) {
return array();
}
$default = array_fill_keys($phids, array());
$affiliations = id(new PhabricatorProjectAffiliation())->loadAllWhere(
'projectPHID IN (%Ls) ORDER BY dateCreated',
$phids);
return mgroup($affiliations, 'getProjectPHID') + $default;
}
}
diff --git a/src/applications/project/storage/base/PhabricatorProjectDAO.php b/src/applications/project/storage/base/PhabricatorProjectDAO.php
index 24d67bbc6f..1a9bd14e79 100644
--- a/src/applications/project/storage/base/PhabricatorProjectDAO.php
+++ b/src/applications/project/storage/base/PhabricatorProjectDAO.php
@@ -1,25 +1,25 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorProjectDAO extends PhabricatorLiskDAO {
+abstract class PhabricatorProjectDAO extends PhabricatorLiskDAO {
public function getApplicationName() {
return 'project';
}
}
diff --git a/src/applications/project/storage/profile/PhabricatorProjectProfile.php b/src/applications/project/storage/profile/PhabricatorProjectProfile.php
index 98df728d5b..f14d070138 100644
--- a/src/applications/project/storage/profile/PhabricatorProjectProfile.php
+++ b/src/applications/project/storage/profile/PhabricatorProjectProfile.php
@@ -1,30 +1,30 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorProjectProfile extends PhabricatorProjectDAO {
+final class PhabricatorProjectProfile extends PhabricatorProjectDAO {
protected $projectPHID;
protected $blurb;
protected $profileImagePHID;
public function getProfileImagePHID() {
return nonempty(
$this->profileImagePHID,
PhabricatorEnv::getEnvConfig('user.default-profile-image-phid'));
}
}
diff --git a/src/applications/project/storage/project/PhabricatorProject.php b/src/applications/project/storage/project/PhabricatorProject.php
index 249b546a50..fd54c93edc 100644
--- a/src/applications/project/storage/project/PhabricatorProject.php
+++ b/src/applications/project/storage/project/PhabricatorProject.php
@@ -1,102 +1,102 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorProject extends PhabricatorProjectDAO {
+final class PhabricatorProject extends PhabricatorProjectDAO {
protected $name;
protected $phid;
protected $status = PhabricatorProjectStatus::STATUS_ACTIVE;
protected $authorPHID;
protected $subprojectPHIDs = array();
protected $phrictionSlug;
private $subprojectsNeedUpdate;
private $affiliations;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'subprojectPHIDs' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_PROJ);
}
public function setSubprojectPHIDs(array $phids) {
$this->subprojectPHIDs = $phids;
$this->subprojectsNeedUpdate = true;
return $this;
}
public function loadProfile() {
$profile = id(new PhabricatorProjectProfile())->loadOneWhere(
'projectPHID = %s',
$this->getPHID());
return $profile;
}
public function getAffiliations() {
if ($this->affiliations === null) {
throw new Exception('Attach affiliations first!');
}
return $this->affiliations;
}
public function attachAffiliations(array $affiliations) {
$this->affiliations = $affiliations;
return $this;
}
public function loadAffiliations() {
$affils = PhabricatorProjectAffiliation::loadAllForProjectPHIDs(
array($this->getPHID()));
return $affils[$this->getPHID()];
}
public function setPhrictionSlug($slug) {
// NOTE: We're doing a little magic here and stripping out '/' so that
// project pages always appear at top level under projects/ even if the
// display name is "Hack / Slash" or similar (it will become
// 'hack_slash' instead of 'hack/slash').
$slug = str_replace('/', ' ', $slug);
$slug = PhrictionDocument::normalizeSlug($slug);
$this->phrictionSlug = $slug;
return $this;
}
public function save() {
$result = parent::save();
if ($this->subprojectsNeedUpdate) {
// If we've changed the project PHIDs for this task, update the link
// table.
PhabricatorProjectSubproject::updateProjectSubproject($this);
$this->subprojectsNeedUpdate = false;
}
return $result;
}
}
diff --git a/src/applications/project/storage/transaction/PhabricatorProjectTransaction.php b/src/applications/project/storage/transaction/PhabricatorProjectTransaction.php
index fad1338d66..d214fcb582 100644
--- a/src/applications/project/storage/transaction/PhabricatorProjectTransaction.php
+++ b/src/applications/project/storage/transaction/PhabricatorProjectTransaction.php
@@ -1,39 +1,39 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group project
*/
-class PhabricatorProjectTransaction extends PhabricatorProjectDAO {
+final class PhabricatorProjectTransaction extends PhabricatorProjectDAO {
protected $projectID;
protected $authorPHID;
protected $transactionType;
protected $oldValue;
protected $newValue;
public function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'oldValue' => self::SERIALIZATION_JSON,
'newValue' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
}
diff --git a/src/applications/repository/daemon/commitdiscovery/git/PhabricatorRepositoryGitCommitDiscoveryDaemon.php b/src/applications/repository/daemon/commitdiscovery/git/PhabricatorRepositoryGitCommitDiscoveryDaemon.php
index 275d02eaca..c76729b64c 100644
--- a/src/applications/repository/daemon/commitdiscovery/git/PhabricatorRepositoryGitCommitDiscoveryDaemon.php
+++ b/src/applications/repository/daemon/commitdiscovery/git/PhabricatorRepositoryGitCommitDiscoveryDaemon.php
@@ -1,162 +1,162 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepositoryGitCommitDiscoveryDaemon
+final class PhabricatorRepositoryGitCommitDiscoveryDaemon
extends PhabricatorRepositoryCommitDiscoveryDaemon {
protected function discoverCommits() {
// NOTE: PhabricatorRepositoryGitFetchDaemon does the actual pulls, this
// just parses HEAD.
$repository = $this->getRepository();
$vcs = $repository->getVersionControlSystem();
if ($vcs != PhabricatorRepositoryType::REPOSITORY_TYPE_GIT) {
throw new Exception("Repository is not a git repository.");
}
list($remotes) = $repository->execxLocalCommand(
'remote show -n origin');
$matches = null;
if (!preg_match('/^\s*Fetch URL:\s*(.*?)\s*$/m', $remotes, $matches)) {
throw new Exception(
"Expected 'Fetch URL' in 'git remote show -n origin'.");
}
self::verifySameGitOrigin(
$matches[1],
$repository->getRemoteURI(),
$repository->getLocalPath());
list($stdout) = $repository->execxLocalCommand(
'branch -r --verbose --no-abbrev');
$branches = DiffusionGitBranchQuery::parseGitRemoteBranchOutput(
$stdout,
$only_this_remote = DiffusionBranchInformation::DEFAULT_GIT_REMOTE);
$got_something = false;
$tracked_something = false;
foreach ($branches as $name => $commit) {
if (!$repository->shouldTrackBranch($name)) {
continue;
}
$tracked_something = true;
if ($this->isKnownCommit($commit)) {
continue;
} else {
$this->discoverCommit($commit);
$got_something = true;
}
}
if (!$tracked_something) {
$repo_name = $repository->getName();
$repo_callsign = $repository->getCallsign();
throw new Exception(
"Repository r{$repo_callsign} '{$repo_name}' has no tracked branches! ".
"Verify that your branch filtering settings are correct.");
}
return $got_something;
}
private function discoverCommit($commit) {
$discover = array();
$insert = array();
$repository = $this->getRepository();
$discover[] = $commit;
$insert[] = $commit;
$seen_parent = array();
while (true) {
$target = array_pop($discover);
list($parents) = $repository->execxLocalCommand(
'log -n1 --pretty="%%P" %s',
$target);
$parents = array_filter(explode(' ', trim($parents)));
foreach ($parents as $parent) {
if (isset($seen_parent[$parent])) {
// We end up in a loop here somehow when we parse Arcanist if we
// don't do this. TODO: Figure out why and draw a pretty diagram
// since it's not evident how parsing a DAG with this causes the
// loop to stop terminating.
continue;
}
$seen_parent[$parent] = true;
if (!$this->isKnownCommit($parent)) {
$discover[] = $parent;
$insert[] = $parent;
}
}
if (empty($discover)) {
break;
}
$this->stillWorking();
}
while (true) {
$target = array_pop($insert);
list($epoch) = $repository->execxLocalCommand(
'log -n1 --pretty="%%at" %s',
$target);
$epoch = trim($epoch);
$this->recordCommit($target, $epoch);
if (empty($insert)) {
break;
}
}
}
public static function verifySameGitOrigin($remote, $expect, $where) {
$remote_uri = PhabricatorRepository::newPhutilURIFromGitURI($remote);
$expect_uri = PhabricatorRepository::newPhutilURIFromGitURI($expect);
$remote_path = $remote_uri->getPath();
$expect_path = $expect_uri->getPath();
$remote_match = self::normalizeGitPath($remote_path);
$expect_match = self::normalizeGitPath($expect_path);
if ($remote_match != $expect_match) {
throw new Exception(
"Working copy at '{$where}' has a mismatched origin URL. It has ".
"origin URL '{$remote}' (with remote path '{$remote_path}'), but the ".
"configured URL '{$expect}' (with remote path '{$expect_path}') is ".
"expected. Refusing to proceed because this may indicate that the ".
"working copy is actually some other repository.");
}
}
private static function normalizeGitPath($path) {
// Strip away trailing "/" and ".git", so similar paths correctly match.
$path = rtrim($path, '/');
$path = preg_replace('/\.git$/', '', $path);
return $path;
}
}
diff --git a/src/applications/repository/daemon/commitdiscovery/mercurial/PhabricatorRepositoryMercurialCommitDiscoveryDaemon.php b/src/applications/repository/daemon/commitdiscovery/mercurial/PhabricatorRepositoryMercurialCommitDiscoveryDaemon.php
index 66dd5c2e2d..6094d3a0f5 100644
--- a/src/applications/repository/daemon/commitdiscovery/mercurial/PhabricatorRepositoryMercurialCommitDiscoveryDaemon.php
+++ b/src/applications/repository/daemon/commitdiscovery/mercurial/PhabricatorRepositoryMercurialCommitDiscoveryDaemon.php
@@ -1,137 +1,137 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepositoryMercurialCommitDiscoveryDaemon
+final class PhabricatorRepositoryMercurialCommitDiscoveryDaemon
extends PhabricatorRepositoryCommitDiscoveryDaemon {
protected function discoverCommits() {
$repository = $this->getRepository();
$vcs = $repository->getVersionControlSystem();
if ($vcs != PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL) {
throw new Exception("Repository is not a Mercurial repository.");
}
$repository_phid = $repository->getPHID();
list($stdout) = $repository->execxLocalCommand('branches');
$branches = ArcanistMercurialParser::parseMercurialBranches($stdout);
$got_something = false;
foreach ($branches as $name => $branch) {
$commit = $branch['rev'];
$commit = $this->getFullHash($commit);
if ($this->isKnownCommit($commit)) {
continue;
} else {
$this->discoverCommit($commit);
$got_something = true;
}
}
return $got_something;
}
private function getFullHash($commit) {
// NOTE: Mercurial shortens hashes to 12 characters by default. This
// implies collisions with as few as a few million commits. The
// documentation sensibly advises "Do not use short-form IDs for
// long-lived representations". It then continues "You can use the
// --debug option to display the full changeset ID". What?! Yes, this
// is in fact the only way to turn on full hashes, and the hg source
// code is littered with "hexfn = ui.debugflag and hex or short" and
// similar. There is no more-selective flag or config option.
//
// Unfortunately, "hg --debug" turns on tons of other extra output,
// including full commit messages in "hg log" and "hg parents" (which
// ignore --style); this renders them unparseable. So we have to use
// "hg id" to convert short hashes into full hashes. See:
//
// <http://mercurial.selenic.com/wiki/ChangeSetID>
//
// Of course, this means that if there are collisions we will break here
// (the short commit identifier won't be unambiguous) but maybe Mercurial
// will have a --full-hashes flag or something by then and we can fix it
// properly. Until we run into that, this allows us to store data in the
// right format so when we eventually encounter this we won't have to
// reparse every Mercurial repository.
$repository = $this->getRepository();
list($stdout) = $repository->execxLocalCommand(
'id --debug -i --rev %s',
$commit);
return trim($stdout);
}
private function discoverCommit($commit) {
$discover = array();
$insert = array();
$repository = $this->getRepository();
$discover[] = $commit;
$insert[] = $commit;
$seen_parent = array();
// For all the new commits at the branch heads, walk backward until we find
// only commits we've aleady seen.
while (true) {
$target = array_pop($discover);
list($stdout) = $repository->execxLocalCommand(
'parents --style default --rev %s',
$target);
$parents = ArcanistMercurialParser::parseMercurialLog($stdout);
if ($parents) {
foreach ($parents as $parent) {
$parent_commit = $parent['rev'];
$parent_commit = $this->getFullHash($parent_commit);
if (isset($seen_parent[$parent_commit])) {
continue;
}
$seen_parent[$parent_commit] = true;
if (!$this->isKnownCommit($parent_commit)) {
$discover[] = $parent_commit;
$insert[] = $parent_commit;
}
}
}
if (empty($discover)) {
break;
}
$this->stillWorking();
}
while (true) {
$target = array_pop($insert);
list($stdout) = $repository->execxLocalCommand(
'log --rev %s --template %s',
$target,
'{date|rfc822date}');
$epoch = strtotime($stdout);
$this->recordCommit($target, $epoch);
if (empty($insert)) {
break;
}
}
}
}
diff --git a/src/applications/repository/daemon/commitdiscovery/svn/PhabricatorRepositorySvnCommitDiscoveryDaemon.php b/src/applications/repository/daemon/commitdiscovery/svn/PhabricatorRepositorySvnCommitDiscoveryDaemon.php
index 4020c887b5..360e80713e 100644
--- a/src/applications/repository/daemon/commitdiscovery/svn/PhabricatorRepositorySvnCommitDiscoveryDaemon.php
+++ b/src/applications/repository/daemon/commitdiscovery/svn/PhabricatorRepositorySvnCommitDiscoveryDaemon.php
@@ -1,123 +1,123 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepositorySvnCommitDiscoveryDaemon
+final class PhabricatorRepositorySvnCommitDiscoveryDaemon
extends PhabricatorRepositoryCommitDiscoveryDaemon {
protected function discoverCommits() {
$repository = $this->getRepository();
$vcs = $repository->getVersionControlSystem();
if ($vcs != PhabricatorRepositoryType::REPOSITORY_TYPE_SVN) {
throw new Exception("Repository is not a svn repository.");
}
$uri = $this->getBaseSVNLogURI();
list($xml) = $repository->execxRemoteCommand(
'log --xml --quiet --limit 1 %s@HEAD',
$uri);
$results = $this->parseSVNLogXML($xml);
$commit = key($results);
$epoch = reset($results);
if ($this->isKnownCommit($commit)) {
return false;
}
$this->discoverCommit($commit, $epoch);
return true;
}
private function discoverCommit($commit, $epoch) {
$uri = $this->getBaseSVNLogURI();
$repository = $this->getRepository();
$discover = array(
$commit => $epoch,
);
$upper_bound = $commit;
$limit = 1;
while ($upper_bound > 1 && !$this->isKnownCommit($upper_bound)) {
// Find all the unknown commits on this path. Note that we permit
// importing an SVN subdirectory rather than the entire repository, so
// commits may be nonsequential.
list($err, $xml, $stderr) = $repository->execRemoteCommand(
' log --xml --quiet --limit %d %s@%d',
$limit,
$uri,
$upper_bound - 1);
if ($err) {
if (preg_match('/(path|File) not found/', $stderr)) {
// We've gone all the way back through history and this path was not
// affected by earlier commits.
break;
} else {
throw new Exception("svn log error #{$err}: {$stderr}");
}
}
$discover += $this->parseSVNLogXML($xml);
$upper_bound = min(array_keys($discover));
// Discover 2, 4, 8, ... 256 logs at a time. This allows us to initially
// import large repositories fairly quickly, while pulling only as much
// data as we need in the common case (when we've already imported the
// repository and are just grabbing one commit at a time).
$limit = min($limit * 2, 256);
}
// NOTE: We do writes only after discovering all the commits so that we're
// never left in a state where we've missed commits -- if the discovery
// script terminates it can always resume and restore the import to a good
// state. This is also why we sort the discovered commits so we can do
// writes forward from the smallest one.
ksort($discover);
foreach ($discover as $commit => $epoch) {
$this->recordCommit($commit, $epoch);
}
}
private function parseSVNLogXML($xml) {
$xml = phutil_utf8ize($xml);
$result = array();
$log = new SimpleXMLElement($xml);
foreach ($log->logentry as $entry) {
$commit = (int)$entry['revision'];
$epoch = (int)strtotime((string)$entry->date[0]);
$result[$commit] = $epoch;
}
return $result;
}
private function getBaseSVNLogURI() {
$repository = $this->getRepository();
$uri = $repository->getDetail('remote-uri');
$subpath = $repository->getDetail('svn-subpath');
return $uri.$subpath;
}
}
diff --git a/src/applications/repository/daemon/committask/PhabricatorRepositoryCommitTaskDaemon.php b/src/applications/repository/daemon/committask/PhabricatorRepositoryCommitTaskDaemon.php
index c9ce60ca0a..0a041ee1e1 100644
--- a/src/applications/repository/daemon/committask/PhabricatorRepositoryCommitTaskDaemon.php
+++ b/src/applications/repository/daemon/committask/PhabricatorRepositoryCommitTaskDaemon.php
@@ -1,80 +1,80 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepositoryCommitTaskDaemon
+final class PhabricatorRepositoryCommitTaskDaemon
extends PhabricatorRepositoryDaemon {
final public function run() {
do {
$iterator = new PhabricatorTimelineIterator('cmittask', array('cmit'));
foreach ($iterator as $event) {
$data = $event->getData();
if (!$data) {
// TODO: This event can't be processed, provide some way to
// communicate that?
continue;
}
$commit = id(new PhabricatorRepositoryCommit())->load($data['id']);
if (!$commit) {
// TODO: Same as above.
continue;
}
// TODO: Cache these.
$repository = id(new PhabricatorRepository())->load(
$commit->getRepositoryID());
if (!$repository) {
// TODO: As above, although this almost certainly means the user just
// deleted the repository and we're correct to ignore the event in
// the timeline.
continue;
}
$vcs = $repository->getVersionControlSystem();
switch ($vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$class = 'PhabricatorRepositoryGitCommitMessageParserWorker';
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$class = 'PhabricatorRepositorySvnCommitMessageParserWorker';
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$class = 'PhabricatorRepositoryMercurialCommitMessageParserWorker';
break;
default:
throw new Exception("Unknown repository type.");
}
$task = new PhabricatorWorkerTask();
$task->setTaskClass($class);
$task->setData(
array(
'commitID' => $commit->getID(),
));
$task->save();
$this->stillWorking();
}
sleep(1);
$this->stillWorking();
} while (true);
}
}
diff --git a/src/applications/repository/parser/default/PhabricatorRepositoryDefaultCommitMessageDetailParser.php b/src/applications/repository/parser/default/PhabricatorRepositoryDefaultCommitMessageDetailParser.php
index 864e238a4f..4a84c997e4 100644
--- a/src/applications/repository/parser/default/PhabricatorRepositoryDefaultCommitMessageDetailParser.php
+++ b/src/applications/repository/parser/default/PhabricatorRepositoryDefaultCommitMessageDetailParser.php
@@ -1,85 +1,88 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+/**
+ * TODO: Facebook extends this (I think?), but should it?
+ */
class PhabricatorRepositoryDefaultCommitMessageDetailParser
extends PhabricatorRepositoryCommitMessageDetailParser {
public function parseCommitDetails() {
$commit = $this->getCommit();
$data = $this->getCommitData();
$details = nonempty($data->getCommitDetails(), array());
$message = $data->getCommitMessage();
$author_name = $data->getAuthorName();
// TODO: Some day, it would be good to drive all of this via
// DifferentialFieldSpecification configuration directly.
$match = null;
if (preg_match(
'/^\s*Differential Revision:\s*(\S+)\s*$/mi',
$message,
$match)) {
// NOTE: We now accept ONLY full URIs because if we accept numeric IDs
// then anyone importing the Phabricator repository will have their
// first few thousand revisions marked committed. This does mean that
// some older revisions won't re-parse correctly, but that shouldn't
// really affect anyone. If necessary, an install can extend the parser
// and restore the older, more-liberal parsing fairly easily.
$id = DifferentialRevisionIDFieldSpecification::parseRevisionIDFromURI(
$match[1]);
if ($id) {
$details['differential.revisionID'] = $id;
$revision = id(new DifferentialRevision())->load($id);
if ($revision) {
$details['differential.revisionPHID'] = $revision->getPHID();
}
}
}
if (preg_match(
'/^\s*Reviewed By:\s*(\S+)\s*$/mi',
$message,
$match)) {
$details['reviewerName'] = $match[1];
$reviewer_phid = $this->resolveUserPHID($details['reviewerName']);
if ($reviewer_phid) {
$details['reviewerPHID'] = $reviewer_phid;
} else {
unset($details['reviewerPHID']);
}
} else {
unset($details['reviewerName']);
unset($details['reviewerPHID']);
}
$author_phid = $this->resolveUserPHID($author_name);
if ($author_phid) {
$details['authorPHID'] = $author_phid;
} else {
unset($details['authorPHID']);
}
$data->setCommitDetails($details);
}
}
diff --git a/src/applications/repository/storage/arcanistproject/PhabricatorRepositoryArcanistProject.php b/src/applications/repository/storage/arcanistproject/PhabricatorRepositoryArcanistProject.php
index 5a95053a1c..ddbbe41148 100644
--- a/src/applications/repository/storage/arcanistproject/PhabricatorRepositoryArcanistProject.php
+++ b/src/applications/repository/storage/arcanistproject/PhabricatorRepositoryArcanistProject.php
@@ -1,51 +1,51 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepositoryArcanistProject
+final class PhabricatorRepositoryArcanistProject
extends PhabricatorRepositoryDAO {
protected $name;
protected $phid;
protected $repositoryID;
protected $symbolIndexLanguages = array();
protected $symbolIndexProjects = array();
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_SERIALIZATION => array(
'symbolIndexLanguages' => self::SERIALIZATION_JSON,
'symbolIndexProjects' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID('APRJ');
}
public function loadRepository() {
if (!$this->getRepositoryID()) {
return null;
}
return id(new PhabricatorRepository())->load($this->getRepositoryID());
}
}
diff --git a/src/applications/repository/storage/base/PhabricatorRepositoryDAO.php b/src/applications/repository/storage/base/PhabricatorRepositoryDAO.php
index abb6e328de..edaec83c7e 100644
--- a/src/applications/repository/storage/base/PhabricatorRepositoryDAO.php
+++ b/src/applications/repository/storage/base/PhabricatorRepositoryDAO.php
@@ -1,25 +1,25 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepositoryDAO extends PhabricatorLiskDAO {
+abstract class PhabricatorRepositoryDAO extends PhabricatorLiskDAO {
public function getApplicationName() {
return 'repository';
}
}
diff --git a/src/applications/repository/storage/commit/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/commit/PhabricatorRepositoryCommit.php
index 6b425276f4..91d08983d2 100644
--- a/src/applications/repository/storage/commit/PhabricatorRepositoryCommit.php
+++ b/src/applications/repository/storage/commit/PhabricatorRepositoryCommit.php
@@ -1,126 +1,126 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepositoryCommit extends PhabricatorRepositoryDAO {
+final class PhabricatorRepositoryCommit extends PhabricatorRepositoryDAO {
protected $repositoryID;
protected $phid;
protected $commitIdentifier;
protected $epoch;
protected $mailKey;
protected $authorPHID;
protected $auditStatus = PhabricatorAuditCommitStatusConstants::NONE;
private $commitData;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_CMIT);
}
public function loadCommitData() {
if (!$this->getID()) {
return null;
}
return id(new PhabricatorRepositoryCommitData())->loadOneWhere(
'commitID = %d',
$this->getID());
}
public function attachCommitData(PhabricatorRepositoryCommitData $data) {
$this->commitData = $data;
return $this;
}
public function getCommitData() {
if (!$this->commitData) {
throw new Exception("Attach commit data with attachCommitData() first!");
}
return $this->commitData;
}
public function save() {
if (!$this->mailKey) {
$this->mailKey = Filesystem::readRandomCharacters(20);
}
return parent::save();
}
public function delete() {
$data = $this->loadCommitData();
$this->openTransaction();
if ($data) {
$data->delete();
}
$result = parent::delete();
$this->saveTransaction();
return $result;
}
/**
* Synchronize a commit's overall audit status with the individual audit
* triggers.
*/
public function updateAuditStatus(array $requests) {
$any_concern = false;
$any_accept = false;
$any_need = false;
foreach ($requests as $request) {
switch ($request->getAuditStatus()) {
case PhabricatorAuditStatusConstants::AUDIT_REQUIRED:
$any_need = true;
break;
case PhabricatorAuditStatusConstants::ACCEPTED:
$any_accept = true;
break;
case PhabricatorAuditStatusConstants::CONCERNED:
$any_concern = true;
break;
}
}
if ($any_concern) {
$status = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED;
} else if ($any_accept) {
if ($any_need) {
$status = PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED;
} else {
$status = PhabricatorAuditCommitStatusConstants::FULLY_AUDITED;
}
} else if ($any_need) {
$status = PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT;
} else {
$status = PhabricatorAuditCommitStatusConstants::NONE;
}
return $this->setAuditStatus($status);
}
}
diff --git a/src/applications/repository/storage/commitdata/PhabricatorRepositoryCommitData.php b/src/applications/repository/storage/commitdata/PhabricatorRepositoryCommitData.php
index 5f2abaff21..26d9023a24 100644
--- a/src/applications/repository/storage/commitdata/PhabricatorRepositoryCommitData.php
+++ b/src/applications/repository/storage/commitdata/PhabricatorRepositoryCommitData.php
@@ -1,51 +1,51 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepositoryCommitData extends PhabricatorRepositoryDAO {
+final class PhabricatorRepositoryCommitData extends PhabricatorRepositoryDAO {
const SUMMARY_MAX_LENGTH = 100;
protected $commitID;
protected $authorName;
protected $commitMessage;
protected $commitDetails = array();
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_SERIALIZATION => array(
'commitDetails' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function getSummary() {
$message = $this->getCommitMessage();
$lines = explode("\n", $message);
$summary = head($lines);
$summary = phutil_utf8_shorten($summary, self::SUMMARY_MAX_LENGTH);
return $summary;
}
public function getCommitDetail($key, $default = null) {
return idx($this->commitDetails, $key, $default);
}
}
diff --git a/src/applications/repository/storage/repository/PhabricatorRepository.php b/src/applications/repository/storage/repository/PhabricatorRepository.php
index 66e2baa8cf..447f979508 100644
--- a/src/applications/repository/storage/repository/PhabricatorRepository.php
+++ b/src/applications/repository/storage/repository/PhabricatorRepository.php
@@ -1,364 +1,364 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepository extends PhabricatorRepositoryDAO {
+final class PhabricatorRepository extends PhabricatorRepositoryDAO {
const TABLE_PATH = 'repository_path';
const TABLE_PATHCHANGE = 'repository_pathchange';
const TABLE_FILESYSTEM = 'repository_filesystem';
const TABLE_SUMMARY = 'repository_summary';
const TABLE_BADCOMMIT = 'repository_badcommit';
protected $phid;
protected $name;
protected $callsign;
protected $uuid;
protected $versionControlSystem;
protected $details = array();
private $sshKeyfile;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'details' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_REPO);
}
public function getDetail($key, $default = null) {
return idx($this->details, $key, $default);
}
public function setDetail($key, $value) {
$this->details[$key] = $value;
return $this;
}
public function getDiffusionBrowseURIForPath($path) {
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$branch = '/'.$this->getDetail('default-branch');
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$branch = null;
break;
default:
throw new Exception("Unknown VCS.");
}
return '/diffusion/'.$this->getCallsign().'/browse'.$branch.$path;
}
public static function newPhutilURIFromGitURI($raw_uri) {
$uri = new PhutilURI($raw_uri);
if (!$uri->getProtocol()) {
if (strpos($raw_uri, '/') === 0) {
// If the URI starts with a '/', it's an implicit file:// URI on the
// local disk.
$uri = new PhutilURI('file://'.$raw_uri);
} else if (strpos($raw_uri, ':') !== false) {
// If there's no protocol (git implicit SSH) but the URI has a colon,
// it's a git implicit SSH URI. Reformat the URI to be a normal URI.
// These git URIs look like "user@domain.com:path" instead of
// "ssh://user@domain/path".
list($domain, $path) = explode(':', $raw_uri, 2);
$uri = new PhutilURI('ssh://'.$domain.'/'.$path);
} else {
throw new Exception("The Git URI '{$raw_uri}' could not be parsed.");
}
}
return $uri;
}
public function getRemoteURI() {
$raw_uri = $this->getDetail('remote-uri');
$vcs = $this->getVersionControlSystem();
$is_git = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT);
if ($is_git) {
$uri = self::newPhutilURIFromGitURI($raw_uri);
} else {
$uri = new PhutilURI($raw_uri);
}
if ($this->isSSHProtocol($uri->getProtocol())) {
if ($this->getSSHLogin()) {
$uri->setUser($this->getSSHLogin());
}
}
return (string)$uri;
}
public function getLocalPath() {
return $this->getDetail('local-path');
}
public function execRemoteCommand($pattern /*, $arg, ... */) {
$args = func_get_args();
$args = $this->formatRemoteCommand($args);
return call_user_func_array('exec_manual', $args);
}
public function execxRemoteCommand($pattern /*, $arg, ... */) {
$args = func_get_args();
$args = $this->formatRemoteCommand($args);
return call_user_func_array('execx', $args);
}
public function getRemoteCommandFuture($pattern /*, $arg, ... */) {
$args = func_get_args();
$args = $this->formatRemoteCommand($args);
return newv('ExecFuture', $args);
}
public function passthruRemoteCommand($pattern /*, $arg, ... */) {
$args = func_get_args();
$args = $this->formatRemoteCommand($args);
return call_user_func_array('phutil_passthru', $args);
}
public function execLocalCommand($pattern /*, $arg, ... */) {
$args = func_get_args();
$args = $this->formatLocalCommand($args);
return call_user_func_array('exec_manual', $args);
}
public function execxLocalCommand($pattern /*, $arg, ... */) {
$args = func_get_args();
$args = $this->formatLocalCommand($args);
return call_user_func_array('execx', $args);
}
public function getLocalCommandFuture($pattern /*, $arg, ... */) {
$args = func_get_args();
$args = $this->formatLocalCommand($args);
return newv('ExecFuture', $args);
}
public function passthruLocalCommand($pattern /*, $arg, ... */) {
$args = func_get_args();
$args = $this->formatLocalCommand($args);
return call_user_func_array('phutil_passthru', $args);
}
private function formatRemoteCommand(array $args) {
$pattern = $args[0];
$args = array_slice($args, 1);
if ($this->shouldUseSSH()) {
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$pattern = "SVN_SSH=%s svn --non-interactive {$pattern}";
array_unshift(
$args,
csprintf(
'ssh -l %s -i %s',
$this->getSSHLogin(),
$this->getSSHKeyfile()));
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$command = call_user_func_array(
'csprintf',
array_merge(
array(
"(ssh-add %s && git {$pattern})",
$this->getSSHKeyfile(),
),
$args));
$pattern = "ssh-agent sh -c %s";
$args = array($command);
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$pattern = "hg --config ui.ssh=%s {$pattern}";
array_unshift(
$args,
csprintf(
'ssh -l %s -i %s',
$this->getSSHLogin(),
$this->getSSHKeyfile()));
break;
default:
throw new Exception("Unrecognized version control system.");
}
} else if ($this->shouldUseHTTP()) {
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$pattern =
"svn ".
"--non-interactive ".
"--no-auth-cache ".
"--trust-server-cert ".
"--username %s ".
"--password %s ".
$pattern;
array_unshift(
$args,
$this->getDetail('http-login'),
$this->getDetail('http-pass'));
break;
default:
throw new Exception(
"No support for HTTP Basic Auth in this version control system.");
}
} else {
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$pattern = "svn --non-interactive {$pattern}";
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$pattern = "git {$pattern}";
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$pattern = "hg {$pattern}";
break;
default:
throw new Exception("Unrecognized version control system.");
}
}
array_unshift($args, $pattern);
return $args;
}
private function formatLocalCommand(array $args) {
$pattern = $args[0];
$args = array_slice($args, 1);
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$pattern = "(cd %s && svn --non-interactive {$pattern})";
array_unshift($args, $this->getLocalPath());
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$pattern = "(cd %s && git {$pattern})";
array_unshift($args, $this->getLocalPath());
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$pattern = "(cd %s && HGPLAIN=1 hg {$pattern})";
array_unshift($args, $this->getLocalPath());
break;
default:
throw new Exception("Unrecognized version control system.");
}
array_unshift($args, $pattern);
return $args;
}
private function getSSHLogin() {
return $this->getDetail('ssh-login');
}
private function getSSHKeyfile() {
if ($this->sshKeyfile === null) {
$key = $this->getDetail('ssh-key');
$keyfile = $this->getDetail('ssh-keyfile');
if ($keyfile) {
// Make sure we can read the file, that it exists, etc.
Filesystem::readFile($keyfile);
$this->sshKeyfile = $keyfile;
} else if ($key) {
$keyfile = new TempFile('phabricator-repository-ssh-key');
chmod($keyfile, 0600);
Filesystem::writeFile($keyfile, $key);
$this->sshKeyfile = $keyfile;
} else {
$this->sshKeyfile = '';
}
}
return (string)$this->sshKeyfile;
}
public function shouldUseSSH() {
$uri = new PhutilURI($this->getRemoteURI());
$protocol = $uri->getProtocol();
if ($this->isSSHProtocol($protocol)) {
return (bool)$this->getSSHKeyfile();
} else {
return false;
}
}
public function shouldUseHTTP() {
$uri = new PhutilURI($this->getRemoteURI());
$protocol = $uri->getProtocol();
if ($this->isHTTPProtocol($protocol)) {
return (bool)$this->getDetail('http-login');
} else {
return false;
}
}
public function getPublicRemoteURI() {
$uri = new PhutilURI($this->getRemoteURI());
// Make sure we don't leak anything if this repo is using HTTP Basic Auth
// with the credentials in the URI or something zany like that.
$uri->setUser(null);
$uri->setPass(null);
return $uri;
}
private function isSSHProtocol($protocol) {
return ($protocol == 'ssh' || $protocol == 'svn+ssh');
}
private function isHTTPProtocol($protocol) {
return ($protocol == 'http' || $protocol == 'https');
}
public function isTracked() {
return $this->getDetail('tracking-enabled', false);
}
public function shouldTrackBranch($branch) {
$vcs = $this->getVersionControlSystem();
$is_git = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT);
$use_filter = ($is_git);
if ($use_filter) {
$filter = $this->getDetail('branch-filter', array());
if ($filter && !isset($filter[$branch])) {
return false;
}
}
// By default, track all branches.
return true;
}
}
diff --git a/src/applications/repository/storage/shortcut/PhabricatorRepositoryShortcut.php b/src/applications/repository/storage/shortcut/PhabricatorRepositoryShortcut.php
index 16700ee11f..d682e98047 100644
--- a/src/applications/repository/storage/shortcut/PhabricatorRepositoryShortcut.php
+++ b/src/applications/repository/storage/shortcut/PhabricatorRepositoryShortcut.php
@@ -1,32 +1,32 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepositoryShortcut extends PhabricatorRepositoryDAO {
+final class PhabricatorRepositoryShortcut extends PhabricatorRepositoryDAO {
protected $name;
protected $href;
protected $description;
protected $sequence;
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
}
diff --git a/src/applications/repository/storage/symbol/PhabricatorRepositorySymbol.php b/src/applications/repository/storage/symbol/PhabricatorRepositorySymbol.php
index e3414aced5..776810417c 100644
--- a/src/applications/repository/storage/symbol/PhabricatorRepositorySymbol.php
+++ b/src/applications/repository/storage/symbol/PhabricatorRepositorySymbol.php
@@ -1,100 +1,99 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
/**
* Records information about symbol locations in a codebase, like where classes
* and functions are defined.
*
* Query symbols with @{class:DiffusionSymbolQuery}.
*
* @group diffusion
*/
-class PhabricatorRepositorySymbol extends PhabricatorRepositoryDAO {
+final class PhabricatorRepositorySymbol extends PhabricatorRepositoryDAO {
protected $arcanistProjectID;
protected $symbolName;
protected $symbolType;
protected $symbolLanguage;
protected $pathID;
protected $lineNumber;
private $path = false;
private $arcanistProject = false;
private $repository = false;
public function getConfiguration() {
return array(
self::CONFIG_IDS => self::IDS_MANUAL,
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
public function getURI() {
$repo = $this->getRepository();
$file = $this->getPath();
$line = $this->getLineNumber();
$drequest = DiffusionRequest::newFromAphrontRequestDictionary(
array(
'callsign' => $repo->getCallsign(),
));
$branch = $drequest->getBranchURIComponent($drequest->getBranch());
$file = $branch.ltrim($file, '/');
return '/diffusion/'.$repo->getCallsign().'/browse/'.$file.'$'.$line;
}
public function getPath() {
if ($this->path === false) {
throw new Exception('Call attachPath() before getPath()!');
}
return $this->path;
}
public function attachPath($path) {
$this->path = $path;
return $this;
}
public function getRepository() {
if ($this->repository === false) {
throw new Exception('Call attachRepository() before getRepository()!');
}
return $this->repository;
}
public function attachRepository($repository) {
$this->repository = $repository;
return $this;
}
public function getArcanistProject() {
if ($this->arcanistProject === false) {
throw new Exception(
'Call attachArcanistProject() before getArcanistProject()!');
}
return $this->arcanistProject;
}
public function attachArcanistProject($project) {
$this->arcanistProject = $project;
return $this;
}
}
diff --git a/src/applications/repository/worker/commitchangeparser/git/PhabricatorRepositoryGitCommitChangeParserWorker.php b/src/applications/repository/worker/commitchangeparser/git/PhabricatorRepositoryGitCommitChangeParserWorker.php
index bfb0410717..25a841fd4a 100644
--- a/src/applications/repository/worker/commitchangeparser/git/PhabricatorRepositoryGitCommitChangeParserWorker.php
+++ b/src/applications/repository/worker/commitchangeparser/git/PhabricatorRepositoryGitCommitChangeParserWorker.php
@@ -1,265 +1,265 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepositoryGitCommitChangeParserWorker
+final class PhabricatorRepositoryGitCommitChangeParserWorker
extends PhabricatorRepositoryCommitChangeParserWorker {
protected function parseCommit(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {
$full_name = 'r'.$repository->getCallsign().$commit->getCommitIdentifier();
echo "Parsing {$full_name}...\n";
if ($this->isBadCommit($full_name)) {
echo "This commit is marked bad!\n";
return;
}
// NOTE: "--pretty=format: " is to disable log output, we only want the
// part we get from "--raw".
list($raw) = $repository->execxLocalCommand(
'log -n1 -M -C -B --find-copies-harder --raw -t '.
'--abbrev=40 --pretty=format: %s',
$commit->getCommitIdentifier());
$changes = array();
$move_away = array();
$copy_away = array();
$lines = explode("\n", $raw);
foreach ($lines as $line) {
if (!strlen(trim($line))) {
continue;
}
list($old_mode, $new_mode,
$old_hash, $new_hash,
$more_stuff) = preg_split('/ +/', $line);
// We may only have two pieces here.
list($action, $src_path, $dst_path) = array_merge(
explode("\t", $more_stuff),
array(null));
// Normalize the paths for consistency with the SVN workflow.
$src_path = '/'.$src_path;
if ($dst_path) {
$dst_path = '/'.$dst_path;
}
$old_mode = intval($old_mode, 8);
$new_mode = intval($new_mode, 8);
switch ($new_mode & 0160000) {
case 0160000:
$file_type = DifferentialChangeType::FILE_SUBMODULE;
break;
case 0120000:
$file_type = DifferentialChangeType::FILE_SYMLINK;
break;
case 0040000:
$file_type = DifferentialChangeType::FILE_DIRECTORY;
break;
default:
$file_type = DifferentialChangeType::FILE_NORMAL;
break;
}
// TODO: We can detect binary changes as git does, through a combination
// of running 'git check-attr' for stuff like 'binary', 'merge' or 'diff',
// and by falling back to inspecting the first 8,000 characters of the
// buffer for null bytes (this is seriously git's algorithm, see
// buffer_is_binary() in xdiff-interface.c).
$change_type = null;
$change_path = $src_path;
$change_target = null;
$is_direct = true;
switch ($action[0]) {
case 'A':
$change_type = DifferentialChangeType::TYPE_ADD;
break;
case 'D':
$change_type = DifferentialChangeType::TYPE_DELETE;
break;
case 'C':
$change_type = DifferentialChangeType::TYPE_COPY_HERE;
$change_path = $dst_path;
$change_target = $src_path;
$copy_away[$change_target][] = $change_path;
break;
case 'R':
$change_type = DifferentialChangeType::TYPE_MOVE_HERE;
$change_path = $dst_path;
$change_target = $src_path;
$move_away[$change_target][] = $change_path;
break;
case 'T':
// Type of the file changed, fall through and treat it as a
// modification. Not 100% sure this is the right thing to do but it
// seems reasonable.
case 'M':
if ($file_type == DifferentialChangeType::FILE_DIRECTORY) {
$change_type = DifferentialChangeType::TYPE_CHILD;
$is_direct = false;
} else {
$change_type = DifferentialChangeType::TYPE_CHANGE;
}
break;
// NOTE: "U" (unmerged) and "X" (unknown) statuses are also possible
// in theory but shouldn't appear here.
default:
throw new Exception("Failed to parse line '{$line}'.");
}
$changes[$change_path] = array(
'repositoryID' => $repository->getID(),
'commitID' => $commit->getID(),
'path' => $change_path,
'changeType' => $change_type,
'fileType' => $file_type,
'isDirect' => $is_direct,
'commitSequence' => $commit->getEpoch(),
'targetPath' => $change_target,
'targetCommitID' => $change_target ? $commit->getID() : null,
);
}
// Add a change to '/' since git doesn't mention it.
$changes['/'] = array(
'repositoryID' => $repository->getID(),
'commitID' => $commit->getID(),
'path' => '/',
'changeType' => DifferentialChangeType::TYPE_CHILD,
'fileType' => DifferentialChangeType::FILE_DIRECTORY,
'isDirect' => false,
'commitSequence' => $commit->getEpoch(),
'targetPath' => null,
'targetCommitID' => null,
);
foreach ($copy_away as $change_path => $destinations) {
if (isset($move_away[$change_path])) {
$change_type = DifferentialChangeType::TYPE_MULTICOPY;
$is_direct = true;
unset($move_away[$change_path]);
} else {
$change_type = DifferentialChangeType::TYPE_COPY_AWAY;
$is_direct = false;
}
$reference = $changes[reset($destinations)];
$changes[$change_path] = array(
'repositoryID' => $repository->getID(),
'commitID' => $commit->getID(),
'path' => $change_path,
'changeType' => $change_type,
'fileType' => $reference['fileType'],
'isDirect' => $is_direct,
'commitSequence' => $commit->getEpoch(),
'targetPath' => null,
'targetCommitID' => null,
);
}
foreach ($move_away as $change_path => $destinations) {
$reference = $changes[reset($destinations)];
$changes[$change_path] = array(
'repositoryID' => $repository->getID(),
'commitID' => $commit->getID(),
'path' => $change_path,
'changeType' => DifferentialChangeType::TYPE_MOVE_AWAY,
'fileType' => $reference['fileType'],
'isDirect' => true,
'commitSequence' => $commit->getEpoch(),
'targetPath' => null,
'targetCommitID' => null,
);
}
$paths = array();
foreach ($changes as $change) {
$paths[$change['path']] = true;
if ($change['targetPath']) {
$paths[$change['targetPath']] = true;
}
}
$path_map = $this->lookupOrCreatePaths(array_keys($paths));
foreach ($changes as $key => $change) {
$changes[$key]['pathID'] = $path_map[$change['path']];
if ($change['targetPath']) {
$changes[$key]['targetPathID'] = $path_map[$change['targetPath']];
} else {
$changes[$key]['targetPathID'] = null;
}
}
$conn_w = $repository->establishConnection('w');
$changes_sql = array();
foreach ($changes as $change) {
$values = array(
(int)$change['repositoryID'],
(int)$change['pathID'],
(int)$change['commitID'],
$change['targetPathID']
? (int)$change['targetPathID']
: 'null',
$change['targetCommitID']
? (int)$change['targetCommitID']
: 'null',
(int)$change['changeType'],
(int)$change['fileType'],
(int)$change['isDirect'],
(int)$change['commitSequence'],
);
$changes_sql[] = '('.implode(', ', $values).')';
}
queryfx(
$conn_w,
'DELETE FROM %T WHERE commitID = %d',
PhabricatorRepository::TABLE_PATHCHANGE,
$commit->getID());
foreach (array_chunk($changes_sql, 256) as $sql_chunk) {
queryfx(
$conn_w,
'INSERT INTO %T
(repositoryID, pathID, commitID, targetPathID, targetCommitID,
changeType, fileType, isDirect, commitSequence)
VALUES %Q',
PhabricatorRepository::TABLE_PATHCHANGE,
implode(', ', $sql_chunk));
}
$this->finishParse();
}
}
diff --git a/src/applications/repository/worker/commitchangeparser/mercurial/PhabricatorRepositoryMercurialCommitChangeParserWorker.php b/src/applications/repository/worker/commitchangeparser/mercurial/PhabricatorRepositoryMercurialCommitChangeParserWorker.php
index 093be2d743..baaa02b559 100644
--- a/src/applications/repository/worker/commitchangeparser/mercurial/PhabricatorRepositoryMercurialCommitChangeParserWorker.php
+++ b/src/applications/repository/worker/commitchangeparser/mercurial/PhabricatorRepositoryMercurialCommitChangeParserWorker.php
@@ -1,354 +1,354 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepositoryMercurialCommitChangeParserWorker
+final class PhabricatorRepositoryMercurialCommitChangeParserWorker
extends PhabricatorRepositoryCommitChangeParserWorker {
protected function parseCommit(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {
$full_name = 'r'.$repository->getCallsign().$commit->getCommitIdentifier();
echo "Parsing {$full_name}...\n";
if ($this->isBadCommit($full_name)) {
echo "This commit is marked bad!\n";
return;
}
list($stdout) = $repository->execxLocalCommand(
'status -C --change %s',
$commit->getCommitIdentifier());
$status = ArcanistMercurialParser::parseMercurialStatusDetails($stdout);
$common_attributes = array(
'repositoryID' => $repository->getID(),
'commitID' => $commit->getID(),
'commitSequence' => $commit->getEpoch(),
);
$changes = array();
// Like Git, Mercurial doesn't track directories directly. We need to infer
// directory creation and removal by observing file creation and removal
// and testing if the directories in question are previously empty (thus,
// created) or subsequently empty (thus, removed).
$maybe_new_directories = array();
$maybe_del_directories = array();
$all_directories = array();
// Parse the basic information from "hg status", which shows files that
// were directly affected by the change.
foreach ($status as $path => $path_info) {
$path = '/'.$path;
$flags = $path_info['flags'];
$change_target = $path_info['from'] ? '/'.$path_info['from'] : null;
$changes[$path] = array(
'path' => $path,
'isDirect' => true,
'targetPath' => $change_target,
'targetCommitID' => $change_target ? $commit->getID() : null,
// We're going to fill these in shortly.
'changeType' => null,
'fileType' => null,
'flags' => $flags,
) + $common_attributes;
if ($flags & ArcanistRepositoryAPI::FLAG_ADDED) {
$maybe_new_directories[] = dirname($path);
} else if ($flags & ArcanistRepositoryAPI::FLAG_DELETED) {
$maybe_del_directories[] = dirname($path);
}
$all_directories[] = dirname($path);
}
// Add change information for each source path which doesn't appear in the
// status. These files were copied, but were not modified. We also know they
// must exist.
foreach ($changes as $path => $change) {
$from = $change['targetPath'];
if ($from && empty($changes[$from])) {
$changes[$from] = array(
'path' => $from,
'isDirect' => false,
'targetPath' => null,
'targetCommitID' => null,
'changeType' => DifferentialChangeType::TYPE_COPY_AWAY,
'fileType' => null,
'flags' => 0,
) + $common_attributes;
}
}
$away = array();
foreach ($changes as $path => $change) {
if ($path['targetPath']) {
$away[$path['targetPath']][] = $path;
}
}
// Now that we have all the direct changes, figure out change types.
foreach ($changes as $path => $change) {
$flags = $change['flags'];
$from = $change['targetPath'];
if ($from) {
$target = $changes[$from];
} else {
$target = null;
}
if ($flags & ArcanistRepositoryAPI::FLAG_ADDED) {
if ($target) {
if ($target['flags'] & ArcanistRepositoryAPI::FLAG_DELETED) {
$change_type = DifferentialChangeType::TYPE_MOVE_HERE;
} else {
$change_type = DifferentialChangeType::TYPE_COPY_HERE;
}
} else {
$change_type = DifferentialChangeType::TYPE_ADD;
}
} else if ($flags & ArcanistRepositoryAPI::FLAG_DELETED) {
if (isset($away[$path])) {
if (count($away[$path]) > 1) {
$change_type = DifferentialChangeType::TYPE_MULTICOPY;
} else {
$change_type = DifferentialChangeType::TYPE_MOVE_AWAY;
}
} else {
$change_type = DifferentialChangeType::TYPE_DELETE;
}
} else {
if (isset($away[$path])) {
$change_type = DifferentialChangeType::TYPE_COPY_AWAY;
} else {
$change_type = DifferentialChangeType::TYPE_CHANGE;
}
}
$changes[$path]['changeType'] = $change_type;
}
// Go through all the affected directories and identify any which were
// actually added or deleted.
$dir_status = array();
foreach ($maybe_del_directories as $dir) {
$exists = false;
foreach (DiffusionPathIDQuery::expandPathToRoot($dir) as $path) {
if (isset($dir_status[$path])) {
break;
}
// If we know some child exists, we know this path exists. If we don't
// know that a child exists, test if this directory still exists.
if (!$exists) {
$exists = $this->mercurialPathExists(
$repository,
$path,
$commit->getCommitIdentifier());
}
if ($exists) {
$dir_status[$path] = DifferentialChangeType::TYPE_CHILD;
} else {
$dir_status[$path] = DifferentialChangeType::TYPE_DELETE;
}
}
}
list($stdout) = $repository->execxLocalCommand(
'parents --rev %s --style default',
$commit->getCommitIdentifier());
$parents = ArcanistMercurialParser::parseMercurialLog($stdout);
$parent = reset($parents);
if ($parent) {
// TODO: We should expand this to a full 40-character hash using "hg id".
$parent = $parent['rev'];
}
foreach ($maybe_new_directories as $dir) {
$exists = false;
foreach (DiffusionPathIDQuery::expandPathToRoot($dir) as $path) {
if (isset($dir_status[$path])) {
break;
}
if (!$exists) {
if ($parent) {
$exists = $this->mercurialPathExists($repository, $path, $parent);
} else {
$exists = false;
}
}
if ($exists) {
$dir_status[$path] = DifferentialChangeType::TYPE_CHILD;
} else {
$dir_status[$path] = DifferentialChangeType::TYPE_ADD;
}
}
}
foreach ($all_directories as $dir) {
foreach (DiffusionPathIDQuery::expandPathToRoot($dir) as $path) {
if (isset($dir_status[$path])) {
break;
}
$dir_status[$path] = DifferentialChangeType::TYPE_CHILD;
}
}
// Merge all the directory statuses into the path statuses.
foreach ($dir_status as $path => $status) {
if (isset($changes[$path])) {
// TODO: The UI probably doesn't handle any of these cases with
// terrible elegance, but they are exceedingly rare.
$existing_type = $changes[$path]['changeType'];
if ($existing_type == DifferentialChangeType::TYPE_DELETE) {
// This change removes a file, replaces it with a directory, and then
// adds children of that directory. Mark it as a "change" instead,
// and make the type a directory.
$changes[$path]['fileType'] = DifferentialChangeType::FILE_DIRECTORY;
$changes[$path]['changeType'] = DifferentialChangeType::TYPE_CHANGE;
} else if ($existing_type == DifferentialChangeType::TYPE_MOVE_AWAY ||
$existing_type == DifferentialChangeType::TYPE_MULTICOPY) {
// This change moves or copies a file, replaces it with a directory,
// and then adds children to that directory. Mark it as "copy away"
// instead of whatever it was, and make the type a directory.
$changes[$path]['fileType'] = DifferentialChangeType::FILE_DIRECTORY;
$changes[$path]['changeType']
= DifferentialChangeType::TYPE_COPY_AWAY;
} else if ($existing_type == DifferentialChangeType::TYPE_ADD) {
// This change removes a diretory and replaces it with a file. Mark
// it as "change" instead of "add".
$changes[$path]['changeType'] = DifferentialChangeType::TYPE_CHANGE;
}
continue;
}
$changes[$path] = array(
'path' => $path,
'isDirect' => ($status == DifferentialChangeType::TYPE_CHILD)
? false
: true,
'fileType' => DifferentialChangeType::FILE_DIRECTORY,
'changeType' => $status,
'targetPath' => null,
'targetCommitID' => null,
) + $common_attributes;
}
// TODO: use "hg diff --git" to figure out which files are symlinks.
foreach ($changes as $path => $change) {
if (empty($change['fileType'])) {
$changes[$path]['fileType'] = DifferentialChangeType::FILE_NORMAL;
}
}
$all_paths = array();
foreach ($changes as $path => $change) {
$all_paths[$path] = true;
if ($change['targetPath']) {
$all_paths[$change['targetPath']] = true;
}
}
$path_map = $this->lookupOrCreatePaths(array_keys($all_paths));
foreach ($changes as $key => $change) {
$changes[$key]['pathID'] = $path_map[$change['path']];
if ($change['targetPath']) {
$changes[$key]['targetPathID'] = $path_map[$change['targetPath']];
} else {
$changes[$key]['targetPathID'] = null;
}
}
$conn_w = $repository->establishConnection('w');
$changes_sql = array();
foreach ($changes as $change) {
$values = array(
(int)$change['repositoryID'],
(int)$change['pathID'],
(int)$change['commitID'],
$change['targetPathID']
? (int)$change['targetPathID']
: 'null',
$change['targetCommitID']
? (int)$change['targetCommitID']
: 'null',
(int)$change['changeType'],
(int)$change['fileType'],
(int)$change['isDirect'],
(int)$change['commitSequence'],
);
$changes_sql[] = '('.implode(', ', $values).')';
}
queryfx(
$conn_w,
'DELETE FROM %T WHERE commitID = %d',
PhabricatorRepository::TABLE_PATHCHANGE,
$commit->getID());
foreach (array_chunk($changes_sql, 256) as $sql_chunk) {
queryfx(
$conn_w,
'INSERT INTO %T
(repositoryID, pathID, commitID, targetPathID, targetCommitID,
changeType, fileType, isDirect, commitSequence)
VALUES %Q',
PhabricatorRepository::TABLE_PATHCHANGE,
implode(', ', $sql_chunk));
}
$this->finishParse();
}
private function mercurialPathExists(
PhabricatorRepository $repository,
$path,
$rev) {
if ($path == '/') {
return true;
}
// NOTE: For directories, this grabs the entire directory contents, but
// we don't have any more surgical approach available to us in Mercurial.
// We can't use "log" because it doesn't have enough information for us
// to figure out when a directory is deleted by a change.
list($err) = $repository->execLocalCommand(
'cat --rev %s -- %s > /dev/null',
$rev,
$path);
if ($err) {
return false;
} else {
return true;
}
}
}
diff --git a/src/applications/repository/worker/commitmessageparser/git/PhabricatorRepositoryGitCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/git/PhabricatorRepositoryGitCommitMessageParserWorker.php
index 3a252b7be4..ebe0aead5b 100644
--- a/src/applications/repository/worker/commitmessageparser/git/PhabricatorRepositoryGitCommitMessageParserWorker.php
+++ b/src/applications/repository/worker/commitmessageparser/git/PhabricatorRepositoryGitCommitMessageParserWorker.php
@@ -1,72 +1,72 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepositoryGitCommitMessageParserWorker
+final class PhabricatorRepositoryGitCommitMessageParserWorker
extends PhabricatorRepositoryCommitMessageParserWorker {
public function parseCommit(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {
// NOTE: %B was introduced somewhat recently in git's history, so pull
// commit message information with %s and %b instead.
list($info) = $repository->execxLocalCommand(
"log -n 1 --encoding='UTF-8' --pretty=format:%%an%%x00%%s%%n%%n%%b %s",
$commit->getCommitIdentifier());
list($author, $message) = explode("\0", $info);
// Make sure these are valid UTF-8.
$author = phutil_utf8ize($author);
$message = phutil_utf8ize($message);
$message = trim($message);
$this->updateCommitData($author, $message);
if ($this->shouldQueueFollowupTasks()) {
$task = new PhabricatorWorkerTask();
$task->setTaskClass('PhabricatorRepositoryGitCommitChangeParserWorker');
$task->setData(
array(
'commitID' => $commit->getID(),
));
$task->save();
}
}
protected function getCommitHashes(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {
list($stdout) = $repository->execxLocalCommand(
'log -n 1 --format=%s %s --',
'%T',
$commit->getCommitIdentifier());
$commit_hash = $commit->getCommitIdentifier();
$tree_hash = trim($stdout);
return array(
array(ArcanistDifferentialRevisionHash::HASH_GIT_COMMIT,
$commit_hash),
array(ArcanistDifferentialRevisionHash::HASH_GIT_TREE,
$tree_hash),
);
}
}
diff --git a/src/applications/repository/worker/commitmessageparser/mercurial/PhabricatorRepositoryMercurialCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/mercurial/PhabricatorRepositoryMercurialCommitMessageParserWorker.php
index 9670e0fadb..22f02753fb 100644
--- a/src/applications/repository/worker/commitmessageparser/mercurial/PhabricatorRepositoryMercurialCommitMessageParserWorker.php
+++ b/src/applications/repository/worker/commitmessageparser/mercurial/PhabricatorRepositoryMercurialCommitMessageParserWorker.php
@@ -1,63 +1,63 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepositoryMercurialCommitMessageParserWorker
+final class PhabricatorRepositoryMercurialCommitMessageParserWorker
extends PhabricatorRepositoryCommitMessageParserWorker {
public function parseCommit(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {
list($stdout) = $repository->execxLocalCommand(
'log --template %s --rev %s',
'{author}\\n{desc}',
$commit->getCommitIdentifier());
list($author, $message) = explode("\n", $stdout, 2);
$author = phutil_utf8ize($author);
$message = phutil_utf8ize($message);
$message = trim($message);
$this->updateCommitData($author, $message);
if ($this->shouldQueueFollowupTasks()) {
$task = new PhabricatorWorkerTask();
$task->setTaskClass(
'PhabricatorRepositoryMercurialCommitChangeParserWorker');
$task->setData(
array(
'commitID' => $commit->getID(),
));
$task->save();
}
}
protected function getCommitHashes(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {
$commit_hash = $commit->getCommitIdentifier();
return array(
array(ArcanistDifferentialRevisionHash::HASH_MERCURIAL_COMMIT,
$commit_hash),
);
}
}
diff --git a/src/applications/repository/worker/commitmessageparser/svn/PhabricatorRepositorySvnCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/svn/PhabricatorRepositorySvnCommitMessageParserWorker.php
index c27c848016..0a16138e33 100644
--- a/src/applications/repository/worker/commitmessageparser/svn/PhabricatorRepositorySvnCommitMessageParserWorker.php
+++ b/src/applications/repository/worker/commitmessageparser/svn/PhabricatorRepositorySvnCommitMessageParserWorker.php
@@ -1,58 +1,58 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepositorySvnCommitMessageParserWorker
+final class PhabricatorRepositorySvnCommitMessageParserWorker
extends PhabricatorRepositoryCommitMessageParserWorker {
public function parseCommit(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {
$uri = $repository->getDetail('remote-uri');
$log = $this->getSVNLogXMLObject(
$uri,
$commit->getCommitIdentifier(),
$verbose = false);
$entry = $log->logentry[0];
$author = (string)$entry->author;
$message = (string)$entry->msg;
$this->updateCommitData($author, $message);
if ($this->shouldQueueFollowupTasks()) {
$task = new PhabricatorWorkerTask();
$task->setTaskClass('PhabricatorRepositorySvnCommitChangeParserWorker');
$task->setData(
array(
'commitID' => $commit->getID(),
));
$task->save();
}
}
protected function getCommitHashes(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {
return array();
}
}
diff --git a/src/applications/repository/worker/herald/PhabricatorRepositoryCommitHeraldWorker.php b/src/applications/repository/worker/herald/PhabricatorRepositoryCommitHeraldWorker.php
index ccfb8bfaa3..0ecbb7f3bc 100644
--- a/src/applications/repository/worker/herald/PhabricatorRepositoryCommitHeraldWorker.php
+++ b/src/applications/repository/worker/herald/PhabricatorRepositoryCommitHeraldWorker.php
@@ -1,268 +1,268 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepositoryCommitHeraldWorker
+final class PhabricatorRepositoryCommitHeraldWorker
extends PhabricatorRepositoryCommitParserWorker {
public function parseCommit(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {
$data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
'commitID = %d',
$commit->getID());
if (!$data) {
// TODO: Permanent failure.
return;
}
$rules = HeraldRule::loadAllByContentTypeWithFullData(
HeraldContentTypeConfig::CONTENT_TYPE_COMMIT,
$commit->getPHID());
$adapter = new HeraldCommitAdapter(
$repository,
$commit,
$data);
$engine = new HeraldEngine();
$effects = $engine->applyRules($rules, $adapter);
$engine->applyEffects($effects, $adapter, $rules);
$audit_phids = $adapter->getAuditMap();
if ($audit_phids) {
$this->createAudits($commit, $audit_phids, $rules);
}
$this->createAuditsFromCommitMessage($commit, $data);
$email_phids = $adapter->getEmailPHIDs();
if (!$email_phids) {
return;
}
if ($repository->getDetail('herald-disabled')) {
// This just means "disable email"; audits are (mostly) idempotent.
return;
}
$xscript = $engine->getTranscript();
$commit_name = $adapter->getHeraldName();
$revision = $adapter->loadDifferentialRevision();
$name = null;
if ($revision) {
$name = ' '.$revision->getTitle();
}
$author_phid = $data->getCommitDetail('authorPHID');
$reviewer_phid = $data->getCommitDetail('reviewerPHID');
$phids = array_filter(array($author_phid, $reviewer_phid));
$handles = array();
if ($phids) {
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
}
if ($author_phid) {
$author_name = $handles[$author_phid]->getName();
} else {
$author_name = $data->getAuthorName();
}
if ($reviewer_phid) {
$reviewer_name = $handles[$reviewer_phid]->getName();
} else {
$reviewer_name = null;
}
$who = implode(', ', array_filter(array($author_name, $reviewer_name)));
$description = $data->getCommitMessage();
$details = PhabricatorEnv::getProductionURI('/'.$commit_name);
$differential = $revision
? PhabricatorEnv::getProductionURI('/D'.$revision->getID())
: 'No revision.';
$files = $adapter->loadAffectedPaths();
sort($files);
$files = implode("\n ", $files);
$xscript_id = $xscript->getID();
$manage_uri = PhabricatorEnv::getProductionURI('/herald/view/commits/');
$why_uri = PhabricatorEnv::getProductionURI(
'/herald/transcript/'.$xscript_id.'/');
$reply_handler = PhabricatorAuditCommentEditor::newReplyHandlerForCommit(
$commit);
$reply_instructions = $reply_handler->getReplyHandlerInstructions();
if ($reply_instructions) {
$reply_instructions =
"\n".
"REPLY HANDLER ACTIONS\n".
" ".$reply_instructions."\n";
}
$body = <<<EOBODY
DESCRIPTION
{$description}
DETAILS
{$details}
DIFFERENTIAL REVISION
{$differential}
AFFECTED FILES
{$files}
{$reply_instructions}
MANAGE HERALD COMMIT RULES
{$manage_uri}
WHY DID I GET THIS EMAIL?
{$why_uri}
EOBODY;
$subject = "[Herald/Commit] {$commit_name} ({$who}){$name}";
$threading = PhabricatorAuditCommentEditor::getMailThreading(
$commit->getPHID());
list($thread_id, $thread_topic) = $threading;
$template = new PhabricatorMetaMTAMail();
$template->setRelatedPHID($commit->getPHID());
$template->setSubject($subject);
$template->setBody($body);
$template->setThreadID($thread_id, $is_new = true);
$template->addHeader('Thread-Topic', $thread_topic);
$template->setIsBulk(true);
$template->addHeader('X-Herald-Rules', $xscript->getXHeraldRulesHeader());
if ($author_phid) {
$template->setFrom($author_phid);
}
$mails = $reply_handler->multiplexMail(
$template,
id(new PhabricatorObjectHandleData($email_phids))->loadHandles(),
array());
foreach ($mails as $mail) {
$mail->saveAndSend();
}
}
private function createAudits(
PhabricatorRepositoryCommit $commit,
array $map,
array $rules) {
$requests = id(new PhabricatorRepositoryAuditRequest())->loadAllWhere(
'commitPHID = %s',
$commit->getPHID());
$requests = mpull($requests, null, 'getAuditorPHID');
$rules = mpull($rules, null, 'getID');
foreach ($map as $phid => $rule_ids) {
$request = idx($requests, $phid);
if ($request) {
continue;
}
$reasons = array();
foreach ($rule_ids as $id) {
$rule_name = '?';
if ($rules[$id]) {
$rule_name = $rules[$id]->getName();
}
$reasons[] = 'Herald Rule #'.$id.' "'.$rule_name.'" Triggered Audit';
}
$request = new PhabricatorRepositoryAuditRequest();
$request->setCommitPHID($commit->getPHID());
$request->setAuditorPHID($phid);
$request->setAuditStatus(PhabricatorAuditStatusConstants::AUDIT_REQUIRED);
$request->setAuditReasons($reasons);
$request->save();
}
$commit->updateAuditStatus($requests);
$commit->save();
}
/**
* Find audit requests in the "Auditors" field if it is present and trigger
* explicit audit requests.
*/
private function createAuditsFromCommitMessage(
PhabricatorRepositoryCommit $commit,
PhabricatorRepositoryCommitData $data) {
$message = $data->getCommitMessage();
$matches = null;
if (!preg_match('/^Auditors:\s*(.*)$/im', $message, $matches)) {
return;
}
$phids = DifferentialFieldSpecification::parseCommitMessageObjectList(
$matches[1],
$include_mailables = false,
$allow_partial = true);
if (!$phids) {
return;
}
$requests = id(new PhabricatorRepositoryAuditRequest())->loadAllWhere(
'commitPHID = %s',
$commit->getPHID());
$requests = mpull($requests, null, 'getAuditorPHID');
foreach ($phids as $phid) {
if (isset($requests[$phid])) {
continue;
}
$request = new PhabricatorRepositoryAuditRequest();
$request->setCommitPHID($commit->getPHID());
$request->setAuditorPHID($phid);
$request->setAuditStatus(
PhabricatorAuditStatusConstants::AUDIT_REQUESTED);
$request->setAuditReasons(
array(
'Requested by Author',
));
$request->save();
$requests[$phid] = $request;
}
$commit->updateAuditStatus($requests);
$commit->save();
}
}
diff --git a/src/applications/repository/worker/owner/PhabricatorRepositoryCommitOwnersWorker.php b/src/applications/repository/worker/owner/PhabricatorRepositoryCommitOwnersWorker.php
index 3f6ffb5af2..5c8eb12ef5 100644
--- a/src/applications/repository/worker/owner/PhabricatorRepositoryCommitOwnersWorker.php
+++ b/src/applications/repository/worker/owner/PhabricatorRepositoryCommitOwnersWorker.php
@@ -1,143 +1,143 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepositoryCommitOwnersWorker
+final class PhabricatorRepositoryCommitOwnersWorker
extends PhabricatorRepositoryCommitParserWorker {
protected function parseCommit(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) {
$affected_paths = PhabricatorOwnerPathQuery::loadAffectedPaths(
$repository, $commit);
$affected_packages = PhabricatorOwnersPackage::loadAffectedPackages(
$repository,
$affected_paths);
if ($affected_packages) {
$requests = id(new PhabricatorRepositoryAuditRequest())
->loadAllWhere(
'commitPHID = %s',
$commit->getPHID());
$requests = mpull($requests, null, 'getAuditorPHID');
foreach ($affected_packages as $package) {
$request = idx($requests, $package->getPHID());
if ($request) {
// Don't update request if it exists already.
continue;
}
if ($package->getAuditingEnabled()) {
$reasons = $this->checkAuditReasons($commit, $package);
if ($reasons) {
$audit_status =
PhabricatorAuditStatusConstants::AUDIT_REQUIRED;
} else {
$audit_status =
PhabricatorAuditStatusConstants::AUDIT_NOT_REQUIRED;
}
} else {
$reasons = array();
$audit_status = PhabricatorAuditStatusConstants::NONE;
}
$relationship = new PhabricatorRepositoryAuditRequest();
$relationship->setAuditorPHID($package->getPHID());
$relationship->setCommitPHID($commit->getPHID());
$relationship->setAuditReasons($reasons);
$relationship->setAuditStatus($audit_status);
$relationship->save();
$requests[$package->getPHID()] = $relationship;
}
$commit->updateAuditStatus($requests);
$commit->save();
}
if ($this->shouldQueueFollowupTasks()) {
$herald_task = new PhabricatorWorkerTask();
$herald_task->setTaskClass('PhabricatorRepositoryCommitHeraldWorker');
$herald_task->setData(
array(
'commitID' => $commit->getID(),
));
$herald_task->save();
}
}
private function checkAuditReasons(
PhabricatorRepositoryCommit $commit,
PhabricatorOwnersPackage $package) {
$data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
'commitID = %d',
$commit->getID());
$reasons = array();
$commit_author_phid = $data->getCommitDetail('authorPHID');
if (!$commit_author_phid) {
$reasons[] = "Commit Author Not Recognized";
}
$revision_id = $data->getCommitDetail('differential.revisionID');
$revision_author_phid = null;
$commit_reviewedby_phid = null;
$commit_author_phid = null;
if ($revision_id) {
$revision = id(new DifferentialRevision())->load($revision_id);
if ($revision) {
$revision->loadRelationships();
$revision_author_phid = $revision->getAuthorPHID();
$revision_reviewedby_phid = $revision->loadReviewedBy();
$commit_reviewedby_phid = $data->getCommitDetail('reviewerPHID');
if ($revision_author_phid !== $commit_author_phid) {
$reasons[] = "Author Not Matching with Revision";
}
if ($revision_reviewedby_phid !== $commit_reviewedby_phid) {
$reasons[] = "ReviewedBy Not Matching with Revision";
}
} else {
$reasons[] = "Revision Not Found";
}
} else {
$reasons[] = "No Revision Specified";
}
$owners = id(new PhabricatorOwnersOwner())->loadAllWhere(
'packageID = %d',
$package->getID());
$owners_phids = mpull($owners, 'getUserPHID');
if (!($commit_author_phid && in_array($commit_author_phid, $owners_phids) ||
$commit_reviewedby_phid && in_array($commit_reviewedby_phid,
$owners_phids))) {
$reasons[] = "Owners Not Involved";
}
return $reasons;
}
}
diff --git a/src/applications/search/index/indexer/differential/PhabricatorSearchDifferentialIndexer.php b/src/applications/search/index/indexer/differential/PhabricatorSearchDifferentialIndexer.php
index 587c23e9cf..5202073054 100644
--- a/src/applications/search/index/indexer/differential/PhabricatorSearchDifferentialIndexer.php
+++ b/src/applications/search/index/indexer/differential/PhabricatorSearchDifferentialIndexer.php
@@ -1,115 +1,115 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group search
*/
-class PhabricatorSearchDifferentialIndexer
+final class PhabricatorSearchDifferentialIndexer
extends PhabricatorSearchDocumentIndexer {
public static function indexRevision(DifferentialRevision $rev) {
$doc = new PhabricatorSearchAbstractDocument();
$doc->setPHID($rev->getPHID());
$doc->setDocumentType(PhabricatorPHIDConstants::PHID_TYPE_DREV);
$doc->setDocumentTitle($rev->getTitle());
$doc->setDocumentCreated($rev->getDateCreated());
$doc->setDocumentModified($rev->getDateModified());
$doc->addField(
PhabricatorSearchField::FIELD_BODY,
$rev->getSummary());
$doc->addField(
PhabricatorSearchField::FIELD_TEST_PLAN,
$rev->getTestPlan());
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR,
$rev->getAuthorPHID(),
PhabricatorPHIDConstants::PHID_TYPE_USER,
$rev->getDateCreated());
if ($rev->getStatus() != ArcanistDifferentialRevisionStatus::COMMITTED &&
$rev->getStatus() != ArcanistDifferentialRevisionStatus::ABANDONED) {
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_OPEN,
$rev->getPHID(),
PhabricatorPHIDConstants::PHID_TYPE_DREV,
time());
}
$comments = id(new DifferentialInlineComment())->loadAllWhere(
'revisionID = %d AND commentID is not null',
$rev->getID());
$touches = array();
foreach ($comments as $comment) {
if (strlen($comment->getContent())) {
// TODO: we should also index inline comments.
$doc->addField(
PhabricatorSearchField::FIELD_COMMENT,
$comment->getContent());
}
$author = $comment->getAuthorPHID();
$touches[$author] = $comment->getDateCreated();
}
foreach ($touches as $touch => $time) {
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_TOUCH,
$touch,
PhabricatorPHIDConstants::PHID_TYPE_USER,
$time);
}
$rev->loadRelationships();
// If a revision needs review, the owners are the reviewers. Otherwise, the
// owner is the author (e.g., accepted, rejected, committed).
if ($rev->getStatus() == ArcanistDifferentialRevisionStatus::NEEDS_REVIEW) {
foreach ($rev->getReviewers() as $phid) {
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_OWNER,
$phid,
PhabricatorPHIDConstants::PHID_TYPE_USER,
$rev->getDateModified()); // Bogus timestamp.
}
} else {
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_OWNER,
$rev->getAuthorPHID(),
PhabricatorPHIDConstants::PHID_TYPE_USER,
$rev->getDateCreated());
}
$ccphids = $rev->getCCPHIDs();
$handles = id(new PhabricatorObjectHandleData($ccphids))
->loadHandles();
foreach ($handles as $phid => $handle) {
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_SUBSCRIBER,
$phid,
$handle->getType(),
$rev->getDateModified()); // Bogus timestamp.
}
self::reindexAbstractDocument($doc);
}
}
diff --git a/src/applications/search/index/indexer/maniphest/PhabricatorSearchManiphestIndexer.php b/src/applications/search/index/indexer/maniphest/PhabricatorSearchManiphestIndexer.php
index b88b5d9b94..af86c6374a 100644
--- a/src/applications/search/index/indexer/maniphest/PhabricatorSearchManiphestIndexer.php
+++ b/src/applications/search/index/indexer/maniphest/PhabricatorSearchManiphestIndexer.php
@@ -1,135 +1,135 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group search
*/
-class PhabricatorSearchManiphestIndexer
+final class PhabricatorSearchManiphestIndexer
extends PhabricatorSearchDocumentIndexer {
public static function indexTask(ManiphestTask $task) {
$doc = new PhabricatorSearchAbstractDocument();
$doc->setPHID($task->getPHID());
$doc->setDocumentType(PhabricatorPHIDConstants::PHID_TYPE_TASK);
$doc->setDocumentTitle($task->getTitle());
$doc->setDocumentCreated($task->getDateCreated());
$doc->setDocumentModified($task->getDateModified());
$doc->addField(
PhabricatorSearchField::FIELD_BODY,
$task->getDescription());
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR,
$task->getAuthorPHID(),
PhabricatorPHIDConstants::PHID_TYPE_USER,
$task->getDateCreated());
if ($task->getStatus() == ManiphestTaskStatus::STATUS_OPEN) {
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_OPEN,
$task->getPHID(),
PhabricatorPHIDConstants::PHID_TYPE_TASK,
time());
}
$transactions = id(new ManiphestTransaction())->loadAllWhere(
'taskID = %d',
$task->getID());
$current_ccs = $task->getCCPHIDs();
$touches = array();
$owner = null;
$ccs = array();
foreach ($transactions as $transaction) {
if ($transaction->hasComments()) {
$doc->addField(
PhabricatorSearchField::FIELD_COMMENT,
$transaction->getComments());
}
$author = $transaction->getAuthorPHID();
// Record the most recent time they touched this object.
$touches[$author] = $transaction->getDateCreated();
switch ($transaction->getTransactionType()) {
case ManiphestTransactionType::TYPE_OWNER:
$owner = $transaction;
break;
case ManiphestTransactionType::TYPE_CCS:
// For users who are still CC'd, record the first time they were
// added to CC.
foreach ($transaction->getNewValue() as $added_cc) {
if (in_array($added_cc, $current_ccs)) {
if (empty($ccs[$added_cc])) {
$ccs[$added_cc] = $transaction->getDateCreated();
}
}
}
break;
}
}
foreach ($task->getProjectPHIDs() as $phid) {
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_PROJECT,
$phid,
PhabricatorPHIDConstants::PHID_TYPE_PROJ,
$task->getDateModified()); // Bogus.
}
if ($owner && $owner->getNewValue()) {
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_OWNER,
$owner->getNewValue(),
PhabricatorPHIDConstants::PHID_TYPE_USER,
$owner->getDateCreated());
} else {
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_OWNER,
ManiphestTaskOwner::OWNER_UP_FOR_GRABS,
PhabricatorPHIDConstants::PHID_TYPE_MAGIC,
$owner
? $owner->getDateCreated()
: $task->getDateCreated());
}
foreach ($touches as $touch => $time) {
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_TOUCH,
$touch,
PhabricatorPHIDConstants::PHID_TYPE_USER,
$time);
}
// We need to load handles here since non-users may subscribe (mailing
// lists, e.g.)
$handles = id(new PhabricatorObjectHandleData(array_keys($ccs)))
->loadHandles();
foreach ($ccs as $cc => $time) {
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_SUBSCRIBER,
$handles[$cc]->getPHID(),
$handles[$cc]->getType(),
$time);
}
self::reindexAbstractDocument($doc);
}
}
diff --git a/src/applications/search/index/indexer/phriction/PhabricatorSearchPhrictionIndexer.php b/src/applications/search/index/indexer/phriction/PhabricatorSearchPhrictionIndexer.php
index dcceaaae4b..c1e6ae9deb 100644
--- a/src/applications/search/index/indexer/phriction/PhabricatorSearchPhrictionIndexer.php
+++ b/src/applications/search/index/indexer/phriction/PhabricatorSearchPhrictionIndexer.php
@@ -1,49 +1,49 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group search
*/
-class PhabricatorSearchPhrictionIndexer
+final class PhabricatorSearchPhrictionIndexer
extends PhabricatorSearchDocumentIndexer {
public static function indexDocument(PhrictionDocument $document) {
$content = $document->getContent();
$doc = new PhabricatorSearchAbstractDocument();
$doc->setPHID($document->getPHID());
$doc->setDocumentType(PhabricatorPHIDConstants::PHID_TYPE_WIKI);
$doc->setDocumentTitle($content->getTitle());
// TODO: This isn't precisely correct, denormalize into the Document table?
$doc->setDocumentCreated($content->getDateCreated());
$doc->setDocumentModified($content->getDateModified());
$doc->addField(
PhabricatorSearchField::FIELD_BODY,
$content->getContent());
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR,
$content->getAuthorPHID(),
PhabricatorPHIDConstants::PHID_TYPE_USER,
$content->getDateCreated());
self::reindexAbstractDocument($doc);
}
}
diff --git a/src/applications/search/index/indexer/repository/PhabricatorSearchCommitIndexer.php b/src/applications/search/index/indexer/repository/PhabricatorSearchCommitIndexer.php
index a3ad51fa49..72e3ac52fe 100644
--- a/src/applications/search/index/indexer/repository/PhabricatorSearchCommitIndexer.php
+++ b/src/applications/search/index/indexer/repository/PhabricatorSearchCommitIndexer.php
@@ -1,83 +1,83 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group search
*/
-class PhabricatorSearchCommitIndexer
+final class PhabricatorSearchCommitIndexer
extends PhabricatorSearchDocumentIndexer {
public static function indexCommit(PhabricatorRepositoryCommit $commit) {
$commit_data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
'commitID = %d',
$commit->getID());
$date_created = $commit->getEpoch();
$commit_message = $commit_data->getCommitMessage();
$author_phid = $commit_data->getCommitDetail('authorPHID');
$repository = id(new PhabricatorRepository())->loadOneWhere(
'id = %d',
$commit->getRepositoryID());
if (!$repository) {
return;
}
$title = 'r'.$repository->getCallsign().$commit->getCommitIdentifier().
" ".$commit_data->getSummary();
$doc = new PhabricatorSearchAbstractDocument();
$doc->setPHID($commit->getPHID());
$doc->setDocumentType(PhabricatorPHIDConstants::PHID_TYPE_CMIT);
$doc->setDocumentCreated($date_created);
$doc->setDocumentModified($date_created);
$doc->setDocumentTitle($title);
$doc->addField(
PhabricatorSearchField::FIELD_BODY,
$commit_message);
if ($author_phid) {
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR,
$author_phid,
PhabricatorPHIDConstants::PHID_TYPE_USER,
$date_created);
}
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY,
$repository->getPHID(),
PhabricatorPHIDConstants::PHID_TYPE_REPO,
$date_created);
$comments = id(new PhabricatorAuditComment())->loadAllWhere(
'targetPHID = %s',
$commit->getPHID());
foreach ($comments as $comment) {
if (strlen($comment->getContent())) {
$doc->addField(
PhabricatorSearchField::FIELD_COMMENT,
$comment->getContent());
}
}
self::reindexAbstractDocument($doc);
}
}
diff --git a/src/applications/search/index/indexer/user/PhabricatorSearchUserIndexer.php b/src/applications/search/index/indexer/user/PhabricatorSearchUserIndexer.php
index 031d5f4294..e321eff38e 100644
--- a/src/applications/search/index/indexer/user/PhabricatorSearchUserIndexer.php
+++ b/src/applications/search/index/indexer/user/PhabricatorSearchUserIndexer.php
@@ -1,38 +1,38 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group search
*/
-class PhabricatorSearchUserIndexer
+final class PhabricatorSearchUserIndexer
extends PhabricatorSearchDocumentIndexer {
public static function indexUser(PhabricatorUser $user) {
$doc = new PhabricatorSearchAbstractDocument();
$doc->setPHID($user->getPHID());
$doc->setDocumentType(PhabricatorPHIDConstants::PHID_TYPE_USER);
$doc->setDocumentTitle($user->getUserName().'('.$user->getRealName().')');
$doc->setDocumentCreated($user->getDateCreated());
$doc->setDocumentModified($user->getDateModified());
// TODO: Index the blurbs from their profile or something? Probably not
// actually useful...
self::reindexAbstractDocument($doc);
}
}
diff --git a/src/applications/search/storage/document/document/PhabricatorSearchDocument.php b/src/applications/search/storage/document/document/PhabricatorSearchDocument.php
index a0837c373f..c6b5be4a45 100644
--- a/src/applications/search/storage/document/document/PhabricatorSearchDocument.php
+++ b/src/applications/search/storage/document/document/PhabricatorSearchDocument.php
@@ -1,41 +1,41 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group search
*/
-class PhabricatorSearchDocument extends PhabricatorSearchDAO {
+final class PhabricatorSearchDocument extends PhabricatorSearchDAO {
protected $phid;
protected $documentType;
protected $documentTitle;
protected $documentCreated;
protected $documentModified;
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_IDS => self::IDS_MANUAL,
) + parent::getConfiguration();
}
public function getIDKey() {
return 'phid';
}
}
diff --git a/src/applications/search/storage/document/field/PhabricatorSearchDocumentField.php b/src/applications/search/storage/document/field/PhabricatorSearchDocumentField.php
index 86e03b45f9..d2fa2c78f2 100644
--- a/src/applications/search/storage/document/field/PhabricatorSearchDocumentField.php
+++ b/src/applications/search/storage/document/field/PhabricatorSearchDocumentField.php
@@ -1,36 +1,36 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group search
*/
-class PhabricatorSearchDocumentField extends PhabricatorSearchDAO {
+final class PhabricatorSearchDocumentField extends PhabricatorSearchDAO {
protected $phid;
protected $field;
protected $auxPHID;
protected $corpus;
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_IDS => self::IDS_MANUAL,
) + parent::getConfiguration();
}
}
diff --git a/src/applications/search/storage/document/relationship/PhabricatorSearchDocumentRelationship.php b/src/applications/search/storage/document/relationship/PhabricatorSearchDocumentRelationship.php
index 6985799512..a90ac616f9 100644
--- a/src/applications/search/storage/document/relationship/PhabricatorSearchDocumentRelationship.php
+++ b/src/applications/search/storage/document/relationship/PhabricatorSearchDocumentRelationship.php
@@ -1,37 +1,37 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group search
*/
-class PhabricatorSearchDocumentRelationship extends PhabricatorSearchDAO {
+final class PhabricatorSearchDocumentRelationship extends PhabricatorSearchDAO {
protected $phid;
protected $relatedPHID;
protected $relation;
protected $relatedType;
protected $relatedTime;
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_IDS => self::IDS_MANUAL,
) + parent::getConfiguration();
}
}
diff --git a/src/applications/slowvote/storage/base/PhabricatorSlowvoteDAO.php b/src/applications/slowvote/storage/base/PhabricatorSlowvoteDAO.php
index 3e770dea5c..e6cc45bd7e 100644
--- a/src/applications/slowvote/storage/base/PhabricatorSlowvoteDAO.php
+++ b/src/applications/slowvote/storage/base/PhabricatorSlowvoteDAO.php
@@ -1,28 +1,28 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group slowvote
*/
-class PhabricatorSlowvoteDAO extends PhabricatorLiskDAO {
+abstract class PhabricatorSlowvoteDAO extends PhabricatorLiskDAO {
public function getApplicationName() {
return 'slowvote';
}
}
diff --git a/src/applications/slowvote/storage/choice/PhabricatorSlowvoteChoice.php b/src/applications/slowvote/storage/choice/PhabricatorSlowvoteChoice.php
index ebcfed524e..95bec958ea 100644
--- a/src/applications/slowvote/storage/choice/PhabricatorSlowvoteChoice.php
+++ b/src/applications/slowvote/storage/choice/PhabricatorSlowvoteChoice.php
@@ -1,28 +1,28 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group slowvote
*/
-class PhabricatorSlowvoteChoice extends PhabricatorSlowvoteDAO {
+final class PhabricatorSlowvoteChoice extends PhabricatorSlowvoteDAO {
protected $pollID;
protected $optionID;
protected $authorPHID;
}
diff --git a/src/applications/slowvote/storage/comment/PhabricatorSlowvoteComment.php b/src/applications/slowvote/storage/comment/PhabricatorSlowvoteComment.php
index dca41d1f9d..5823f2406e 100644
--- a/src/applications/slowvote/storage/comment/PhabricatorSlowvoteComment.php
+++ b/src/applications/slowvote/storage/comment/PhabricatorSlowvoteComment.php
@@ -1,28 +1,28 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group slowvote
*/
-class PhabricatorSlowvoteComment extends PhabricatorSlowvoteDAO {
+final class PhabricatorSlowvoteComment extends PhabricatorSlowvoteDAO {
protected $pollID;
protected $authorPHID;
protected $commentText;
}
diff --git a/src/applications/slowvote/storage/option/PhabricatorSlowvoteOption.php b/src/applications/slowvote/storage/option/PhabricatorSlowvoteOption.php
index 5f123b262b..6459eac828 100644
--- a/src/applications/slowvote/storage/option/PhabricatorSlowvoteOption.php
+++ b/src/applications/slowvote/storage/option/PhabricatorSlowvoteOption.php
@@ -1,28 +1,28 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group slowvote
*/
-class PhabricatorSlowvoteOption extends PhabricatorSlowvoteDAO {
+final class PhabricatorSlowvoteOption extends PhabricatorSlowvoteDAO {
protected $pollID;
protected $name;
}
diff --git a/src/applications/slowvote/storage/poll/PhabricatorSlowvotePoll.php b/src/applications/slowvote/storage/poll/PhabricatorSlowvotePoll.php
index 717b0da667..47b91d69df 100644
--- a/src/applications/slowvote/storage/poll/PhabricatorSlowvotePoll.php
+++ b/src/applications/slowvote/storage/poll/PhabricatorSlowvotePoll.php
@@ -1,49 +1,49 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group slowvote
*/
-class PhabricatorSlowvotePoll extends PhabricatorSlowvoteDAO {
+final class PhabricatorSlowvotePoll extends PhabricatorSlowvoteDAO {
const RESPONSES_VISIBLE = 0;
const RESPONSES_VOTERS = 1;
const RESPONSES_OWNER = 2;
const METHOD_PLURALITY = 0;
const METHOD_APPROVAL = 1;
protected $question;
protected $phid;
protected $authorPHID;
protected $responseVisibility;
protected $shuffle;
protected $method;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_POLL);
}
}
diff --git a/src/applications/uiexample/examples/client/JavelinViewExample.php b/src/applications/uiexample/examples/client/JavelinViewExample.php
index 9eb1cbaaac..dc89deee07 100644
--- a/src/applications/uiexample/examples/client/JavelinViewExample.php
+++ b/src/applications/uiexample/examples/client/JavelinViewExample.php
@@ -1,59 +1,59 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class JavelinViewExample extends PhabricatorUIExample {
+final class JavelinViewExample extends PhabricatorUIExample {
public function getName() {
return 'Javelin Views';
}
public function getDescription() {
return 'Mix and match client and server views.';
}
public function renderExample() {
$request = $this->getRequest();
$init = $request->getStr('init');
$parent_server_template = new JavelinViewExampleServerView();
$parent_client_template = new AphrontJavelinView();
$parent_client_template
->setName('JavelinViewExample')
->setCelerityResource('phabricator-uiexample-javelin-view');
$child_server_template = new JavelinViewExampleServerView();
$child_client_template = new AphrontJavelinView();
$child_client_template
->setName('JavelinViewExample')
->setCelerityResource('phabricator-uiexample-javelin-view');
$parent_server_template->appendChild($parent_client_template);
$parent_client_template->appendChild($child_server_template);
$child_server_template->appendChild($child_client_template);
$child_client_template->appendChild('Hey, it worked.');
$panel = new AphrontPanelView();
$panel->appendChild($parent_server_template);
return $panel;
}
}
diff --git a/src/applications/uiexample/examples/client/JavelinViewExampleServerView.php b/src/applications/uiexample/examples/client/JavelinViewExampleServerView.php
index f7370f979b..f4fb2d46cd 100644
--- a/src/applications/uiexample/examples/client/JavelinViewExampleServerView.php
+++ b/src/applications/uiexample/examples/client/JavelinViewExampleServerView.php
@@ -1,27 +1,27 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class JavelinViewExampleServerView extends AphrontView {
+final class JavelinViewExampleServerView extends AphrontView {
public function render() {
return phutil_render_tag(
'div',
array('class' => 'server-view'),
$this->renderChildren()
);
}
}
diff --git a/src/applications/uiexample/examples/listfilter/PhabricatorUIListFilterExample.php b/src/applications/uiexample/examples/listfilter/PhabricatorUIListFilterExample.php
index eca9c74f1b..29c3ca80f6 100644
--- a/src/applications/uiexample/examples/listfilter/PhabricatorUIListFilterExample.php
+++ b/src/applications/uiexample/examples/listfilter/PhabricatorUIListFilterExample.php
@@ -1,57 +1,57 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorUIListFilterExample extends PhabricatorUIExample {
+final class PhabricatorUIListFilterExample extends PhabricatorUIExample {
public function getName() {
return 'ListFilter';
}
public function getDescription() {
return 'Use <tt>AphrontListFilterView</tt> to layout controls for '.
'filtering and manipulating lists of objects.';
}
public function renderExample() {
$filter = new AphrontListFilterView();
$filter->addButton(
phutil_render_tag(
'a',
array(
'href' => '#',
'class' => 'button green',
),
'Create New Thing'));
$form = new AphrontFormView();
$form->setUser($this->getRequest()->getUser());
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Query'))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Search'));
$filter->appendChild($form);
return $filter;
}
}
diff --git a/src/applications/uiexample/examples/pager/PhabricatorUIPagerExample.php b/src/applications/uiexample/examples/pager/PhabricatorUIPagerExample.php
index 5de81aa323..fbde21decb 100644
--- a/src/applications/uiexample/examples/pager/PhabricatorUIPagerExample.php
+++ b/src/applications/uiexample/examples/pager/PhabricatorUIPagerExample.php
@@ -1,94 +1,94 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorUIPagerExample extends PhabricatorUIExample {
+final class PhabricatorUIPagerExample extends PhabricatorUIExample {
public function getName() {
return 'Pager';
}
public function getDescription() {
return 'Use <tt>AphrontPagerView</tt> to create a control which allows '.
'users to paginate through large amounts of content.';
}
public function renderExample() {
$request = $this->getRequest();
$offset = (int)$request->getInt('offset');
$page_size = 20;
$item_count = 173;
$rows = array();
for ($ii = $offset; $ii < min($item_count, $offset + $page_size); $ii++) {
$rows[] = array(
'Item #'.($ii + 1),
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
'Item',
));
$panel = new AphrontPanelView();
$panel->appendChild($table);
$panel->appendChild(
'<p class="phabricator-ui-example-note">'.
'Use <tt>AphrontPagerView</tt> to render a pager element.'.
'</p>');
$pager = new AphrontPagerView();
$pager->setPageSize($page_size);
$pager->setOffset($offset);
$pager->setCount($item_count);
$pager->setURI($request->getRequestURI(), 'offset');
$panel->appendChild($pager);
$panel->appendChild(
'<p class="phabricator-ui-example-note">'.
'You can show more or fewer pages of surrounding context.'.
'</p>');
$many_pages_pager = new AphrontPagerView();
$many_pages_pager->setPageSize($page_size);
$many_pages_pager->setOffset($offset);
$many_pages_pager->setCount($item_count);
$many_pages_pager->setURI($request->getRequestURI(), 'offset');
$many_pages_pager->setSurroundingPages(7);
$panel->appendChild($many_pages_pager);
$panel->appendChild(
'<p class="phabricator-ui-example-note">'.
'When it is prohibitively expensive or complex to attain a complete '.
'count of the items, you can select one extra item and set '.
'<tt>hasMorePages(true)</tt> if it exists, creating an inexact pager.'.
'</p>');
$inexact_pager = new AphrontPagerView();
$inexact_pager->setPageSize($page_size);
$inexact_pager->setOffset($offset);
$inexact_pager->setHasMorePages($offset < ($item_count - $page_size));
$inexact_pager->setURI($request->getRequestURI(), 'offset');
$panel->appendChild($inexact_pager);
return $panel;
}
}
diff --git a/src/applications/uiexample/examples/reactor/JavelinReactorExample.php b/src/applications/uiexample/examples/reactor/JavelinReactorExample.php
index 9bdf56b397..bd005814df 100644
--- a/src/applications/uiexample/examples/reactor/JavelinReactorExample.php
+++ b/src/applications/uiexample/examples/reactor/JavelinReactorExample.php
@@ -1,105 +1,105 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class JavelinReactorExample extends PhabricatorUIExample {
+final class JavelinReactorExample extends PhabricatorUIExample {
public function getName() {
return 'Javelin Reactor Examples';
}
public function getDescription() {
return 'Lots of code';
}
public function renderExample() {
$rows = array();
$examples = array(
array(
'Reactive button only generates a stream of events',
'ReactorButtonExample',
'phabricator-uiexample-reactor-button',
array(),
),
array(
'Reactive checkbox generates a boolean dynamic value',
'ReactorCheckboxExample',
'phabricator-uiexample-reactor-checkbox',
array('checked' => true)
),
array(
'Reactive focus detector generates a boolean dynamic value',
'ReactorFocusExample',
'phabricator-uiexample-reactor-focus',
array(),
),
array(
'Reactive input box, with normal and calmed output',
'ReactorInputExample',
'phabricator-uiexample-reactor-input',
array('init' => 'Initial value'),
),
array(
'Reactive mouseover detector generates a boolean dynamic value',
'ReactorMouseoverExample',
'phabricator-uiexample-reactor-mouseover',
array(),
),
array(
'Reactive radio buttons generate a string dynamic value',
'ReactorRadioExample',
'phabricator-uiexample-reactor-radio',
array(),
),
array(
'Reactive select box generates a string dynamic value',
'ReactorSelectExample',
'phabricator-uiexample-reactor-select',
array(),
),
array(
'sendclass makes the class of an element a string dynamic value',
'ReactorSendClassExample',
'phabricator-uiexample-reactor-sendclass',
array()
),
array(
'sendproperties makes some properties of an object into dynamic values',
'ReactorSendPropertiesExample',
'phabricator-uiexample-reactor-sendproperties',
array(),
),
);
foreach ($examples as $example) {
list($desc, $name, $resource, $params) = $example;
$template = new AphrontJavelinView();
$template
->setName($name)
->setParameters($params)
->setCelerityResource($resource);
$rows[] = array($desc, $template->render());
}
$table = new AphrontTableView($rows);
$panel = new AphrontPanelView();
$panel->appendChild($table);
return $panel;
}
}
diff --git a/src/applications/xhpastview/storage/parsetree/PhabricatorXHPASTViewParseTree.php b/src/applications/xhpastview/storage/parsetree/PhabricatorXHPASTViewParseTree.php
index c53ee68355..1786997cf6 100644
--- a/src/applications/xhpastview/storage/parsetree/PhabricatorXHPASTViewParseTree.php
+++ b/src/applications/xhpastview/storage/parsetree/PhabricatorXHPASTViewParseTree.php
@@ -1,25 +1,25 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorXHPASTViewParseTree extends PhabricatorXHPASTViewDAO {
+final class PhabricatorXHPASTViewParseTree extends PhabricatorXHPASTViewDAO {
protected $authorPHID;
protected $input;
protected $stdout;
}
diff --git a/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollectorDaemon.php b/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollectorDaemon.php
index 641a48651a..0ad87fb617 100644
--- a/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollectorDaemon.php
+++ b/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollectorDaemon.php
@@ -1,157 +1,157 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Collects old logs and caches to reduce the amount of data stored in the
* database.
*
* @group daemon
*/
-class PhabricatorGarbageCollectorDaemon extends PhabricatorDaemon {
+final class PhabricatorGarbageCollectorDaemon extends PhabricatorDaemon {
public function run() {
// Keep track of when we start and stop the GC so we can emit useful log
// messages.
$just_ran = false;
do {
$run_at = PhabricatorEnv::getEnvConfig('gcdaemon.run-at');
$run_for = PhabricatorEnv::getEnvConfig('gcdaemon.run-for');
// Just use the default timezone, we don't need to get fancy and try
// to localize this.
$start = strtotime($run_at);
if ($start === false) {
throw new Exception(
"Configuration 'gcdaemon.run-at' could not be parsed: '{$run_at}'.");
}
$now = time();
if ($now < $start || $now > ($start + $run_for)) {
if ($just_ran) {
echo "Stopped garbage collector.\n";
$just_ran = false;
}
// The configuration says we can't collect garbage right now, so
// just sleep until we can.
$this->sleep(300);
continue;
}
if (!$just_ran) {
echo "Started garbage collector.\n";
$just_ran = true;
}
$n_herald = $this->collectHeraldTranscripts();
$n_daemon = $this->collectDaemonLogs();
$n_parse = $this->collectParseCaches();
$collected = array(
'Herald Transcript' => $n_herald,
'Daemon Log' => $n_daemon,
'Differential Parse Cache' => $n_parse,
);
$collected = array_filter($collected);
foreach ($collected as $thing => $count) {
if ($thing == 'Daemon Log' && !$this->getTraceMode()) {
continue;
}
$count = number_format($count);
echo "Garbage collected {$count} '{$thing}' objects.\n";
}
$total = array_sum($collected);
if ($total < 100) {
// We didn't max out any of the GCs so we're basically caught up. Ease
// off the GC loop so we don't keep doing table scans just to delete
// a handful of rows.
$this->sleep(300);
} else {
$this->stillWorking();
}
} while (true);
}
private function collectHeraldTranscripts() {
$ttl = PhabricatorEnv::getEnvConfig('gcdaemon.ttl.herald-transcripts');
if ($ttl <= 0) {
return 0;
}
$table = new HeraldTranscript();
$conn_w = $table->establishConnection('w');
queryfx(
$conn_w,
'UPDATE %T SET
objectTranscript = "",
ruleTranscripts = "",
conditionTranscripts = "",
applyTranscripts = "",
garbageCollected = 1
WHERE garbageCollected = 0 AND `time` < %d
LIMIT 100',
$table->getTableName(),
time() - $ttl);
return $conn_w->getAffectedRows();
}
private function collectDaemonLogs() {
$ttl = PhabricatorEnv::getEnvConfig('gcdaemon.ttl.daemon-logs');
if ($ttl <= 0) {
return 0;
}
$table = new PhabricatorDaemonLogEvent();
$conn_w = $table->establishConnection('w');
queryfx(
$conn_w,
'DELETE FROM %T WHERE epoch < %d LIMIT 100',
$table->getTableName(),
time() - $ttl);
return $conn_w->getAffectedRows();
}
private function collectParseCaches() {
$key = 'gcdaemon.ttl.differential-parse-cache';
$ttl = PhabricatorEnv::getEnvConfig($key);
if ($ttl <= 0) {
return 0;
}
$table = new DifferentialChangeset();
$conn_w = $table->establishConnection('w');
queryfx(
$conn_w,
'DELETE FROM %T WHERE dateCreated < %d LIMIT 100',
DifferentialChangeset::TABLE_CACHE,
time() - $ttl);
return $conn_w->getAffectedRows();
}
}
diff --git a/src/infrastructure/daemon/irc/handler/differentialnotification/PhabricatorIRCDifferentialNotificationHandler.php b/src/infrastructure/daemon/irc/handler/differentialnotification/PhabricatorIRCDifferentialNotificationHandler.php
index 843fc8694b..e26fef8385 100644
--- a/src/infrastructure/daemon/irc/handler/differentialnotification/PhabricatorIRCDifferentialNotificationHandler.php
+++ b/src/infrastructure/daemon/irc/handler/differentialnotification/PhabricatorIRCDifferentialNotificationHandler.php
@@ -1,66 +1,66 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group irc
*/
-class PhabricatorIRCDifferentialNotificationHandler
+final class PhabricatorIRCDifferentialNotificationHandler
extends PhabricatorIRCHandler {
private $skippedOldEvents;
public function receiveMessage(PhabricatorIRCMessage $message) {
return;
}
public function runBackgroundTasks() {
$iterator = new PhabricatorTimelineIterator('ircdiffx', array('difx'));
$show = $this->getConfig('notification.actions');
if (!$this->skippedOldEvents) {
// Since we only want to post notifications about new events, skip
// everything that's happened in the past when we start up so we'll
// only process real-time events.
foreach ($iterator as $event) {
// Ignore all old events.
}
$this->skippedOldEvents = true;
return;
}
foreach ($iterator as $event) {
$data = $event->getData();
if (!$data || ($show !== null && !in_array($data['action'], $show))) {
continue;
}
$actor_phid = $data['actor_phid'];
$phids = array($actor_phid);
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
$verb = DifferentialAction::getActionPastTenseVerb($data['action']);
$actor_name = $handles[$actor_phid]->getName();
$message = "{$actor_name} {$verb} revision D".$data['revision_id'].".";
foreach ($this->getConfig('notification.channels') as $channel) {
$this->write('PRIVMSG', "{$channel} :{$message}");
}
}
}
}
diff --git a/src/infrastructure/daemon/irc/handler/objectname/PhabricatorIRCObjectNameHandler.php b/src/infrastructure/daemon/irc/handler/objectname/PhabricatorIRCObjectNameHandler.php
index f2b200922a..ac6d6ab358 100644
--- a/src/infrastructure/daemon/irc/handler/objectname/PhabricatorIRCObjectNameHandler.php
+++ b/src/infrastructure/daemon/irc/handler/objectname/PhabricatorIRCObjectNameHandler.php
@@ -1,256 +1,256 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Looks for Dxxxx, Txxxx and links to them.
*
* @group irc
*/
-class PhabricatorIRCObjectNameHandler extends PhabricatorIRCHandler {
+final class PhabricatorIRCObjectNameHandler extends PhabricatorIRCHandler {
/**
* Map of PHIDs to the last mention of them (as an epoch timestamp); prevents
* us from spamming chat when a single object is discussed.
*/
private $recentlyMentioned = array();
public function receiveMessage(PhabricatorIRCMessage $message) {
switch ($message->getCommand()) {
case 'PRIVMSG':
$reply_to = $message->getReplyTo();
if (!$reply_to) {
break;
}
$this->handleSymbols($message);
$message = $message->getMessageText();
$matches = null;
$pattern =
'@'.
'(?<!/)(?:^|\b)'. // Negative lookbehind prevent matching "/D123".
'(D|T|P|V|F)(\d+)'.
'(?:\b|$)'.
'@';
$pattern_override = '/(^[^\s]+)[,:] [DTPVF]\d+/';
$revision_ids = array();
$task_ids = array();
$paste_ids = array();
$commit_names = array();
$vote_ids = array();
$file_ids = array();
$matches_override = array();
if (preg_match_all($pattern, $message, $matches, PREG_SET_ORDER)) {
if (preg_match($pattern_override, $message, $matches_override)) {
$reply_to = $matches_override[1];
}
foreach ($matches as $match) {
switch ($match[1]) {
case 'D':
$revision_ids[] = $match[2];
break;
case 'T':
$task_ids[] = $match[2];
break;
case 'P':
$paste_ids[] = $match[2];
break;
case 'V':
$vote_ids[] = $match[2];
break;
case 'F':
$file_ids[] = $match[2];
break;
}
}
}
$pattern =
'@'.
'(?<!/)(?:^|\b)'.
'(r[A-Z]+[0-9a-z]{1,40})'.
'(?:\b|$)'.
'@';
if (preg_match_all($pattern, $message, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$commit_names[] = $match[1];
}
}
$output = array();
if ($revision_ids) {
$revisions = $this->getConduit()->callMethodSynchronous(
'differential.find',
array(
'query' => 'revision-ids',
'guids' => $revision_ids,
));
foreach ($revisions as $revision) {
$output[$revision['phid']] =
'D'.$revision['id'].' '.$revision['name'].' - '.
$revision['uri'];
}
}
if ($task_ids) {
foreach ($task_ids as $task_id) {
if ($task_id == 1000) {
$output[1000] = 'T1000: A nanomorph mimetic poly-alloy'
.'(liquid metal) assassin controlled by Skynet: '
.'http://en.wikipedia.org/wiki/T-1000';
continue;
}
$task = $this->getConduit()->callMethodSynchronous(
'maniphest.info',
array(
'task_id' => $task_id,
));
$output[$task['phid']] = 'T'.$task['id'].': '.$task['title'].
' (Priority: '.$task['priority'].') - '.$task['uri'];
}
}
if ($vote_ids) {
foreach ($vote_ids as $vote_id) {
$vote = $this->getConduit()->callMethodSynchronous(
'slowvote.info',
array(
'poll_id' => $vote_id,
));
$output[$vote['phid']] = 'V'.$vote['id'].': '.$vote['question'].
' Come Vote '.$vote['uri'];
}
}
if ($file_ids) {
foreach ($file_ids as $file_id) {
$file = $this->getConduit()->callMethodSynchronous(
'file.info',
array(
'id' => $file_id,
));
$output[$file['phid']] = $file['objectName'].": ".$file['uri']." - ".
$file['name'];
}
}
if ($paste_ids) {
foreach ($paste_ids as $paste_id) {
$paste = $this->getConduit()->callMethodSynchronous(
'paste.info',
array(
'paste_id' => $paste_id,
));
// Eventually I'd like to show the username of the paster as well,
// however that will need something like a user.username_from_phid
// since we (ideally) want to keep the bot to Conduit calls...and
// not call to Phabricator-specific stuff (like actually loading
// the User object and fetching his/her username.)
$output[$paste['phid']] = 'P'.$paste['id'].': '.$paste['uri'].' - '.
$paste['title'];
if ($paste['language']) {
$output[$paste['phid']] .= ' ('.$paste['language'].')';
}
}
}
if ($commit_names) {
$commits = $this->getConduit()->callMethodSynchronous(
'diffusion.getcommits',
array(
'commits' => $commit_names,
));
foreach ($commits as $commit) {
if (isset($commit['error'])) {
continue;
}
$output[$commit['commitPHID']] = $commit['uri'];
}
}
foreach ($output as $phid => $description) {
// Don't mention the same object more than once every 10 minutes
// in public channels, so we avoid spamming the chat over and over
// again for discsussions of a specific revision, for example. In
// direct-to-bot chat, respond to every object reference.
if ($this->isChannelName($reply_to)) {
if (empty($this->recentlyMentioned[$reply_to])) {
$this->recentlyMentioned[$reply_to] = array();
}
$quiet_until = idx(
$this->recentlyMentioned[$reply_to],
$phid,
0) + (60 * 10);
if (time() < $quiet_until) {
// Remain quiet on this channel.
continue;
}
$this->recentlyMentioned[$reply_to][$phid] = time();
}
$this->write('PRIVMSG', "{$reply_to} :{$description}");
}
break;
}
}
private function handleSymbols(PhabricatorIRCMessage $message) {
$reply_to = $message->getReplyTo();
$text = $message->getMessageText();
$matches = null;
if (!preg_match('/where is (\S+?)\?/i', $text, $matches)) {
return;
}
$symbol = $matches[1];
$results = $this->getConduit()->callMethodSynchronous(
'diffusion.findsymbols',
array(
'name' => $symbol,
));
if (count($results) > 1) {
$uri = $this->getURI('/diffusion/symbol/'.$symbol.'/');
$response = "Multiple symbols named '{$symbol}': {$uri}";
} else if (count($results) == 1) {
$result = head($results);
$response =
$result['type'].' '.
$result['name'].' '.
'('.$result['language'].'): '.
$result['uri'];
} else {
$response = "No symbol '{$symbol}' found anywhere.";
}
$this->write('PRIVMSG', "{$reply_to} :{$response}");
}
}
diff --git a/src/infrastructure/daemon/irc/handler/protocol/PhabricatorIRCProtocolHandler.php b/src/infrastructure/daemon/irc/handler/protocol/PhabricatorIRCProtocolHandler.php
index a03ecf1c8e..daf2958a68 100644
--- a/src/infrastructure/daemon/irc/handler/protocol/PhabricatorIRCProtocolHandler.php
+++ b/src/infrastructure/daemon/irc/handler/protocol/PhabricatorIRCProtocolHandler.php
@@ -1,43 +1,43 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Implements the base IRC protocol so servers don't kick you off.
*
* @group irc
*/
-class PhabricatorIRCProtocolHandler extends PhabricatorIRCHandler {
+final class PhabricatorIRCProtocolHandler extends PhabricatorIRCHandler {
public function receiveMessage(PhabricatorIRCMessage $message) {
switch ($message->getCommand()) {
case '376': // End of MOTD
$join = $this->getConfig('join');
if (!$join) {
throw new Exception("Not configured to join any channels!");
}
foreach ($join as $channel) {
$this->write('JOIN', $channel);
}
break;
case 'PING':
$this->write('PONG', $message->getRawData());
break;
}
}
}
diff --git a/src/infrastructure/daemon/storage/event/PhabricatorDaemonLogEvent.php b/src/infrastructure/daemon/storage/event/PhabricatorDaemonLogEvent.php
index 75e66c68f7..a02d61e9f4 100644
--- a/src/infrastructure/daemon/storage/event/PhabricatorDaemonLogEvent.php
+++ b/src/infrastructure/daemon/storage/event/PhabricatorDaemonLogEvent.php
@@ -1,32 +1,32 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorDaemonLogEvent extends PhabricatorDaemonDAO {
+final class PhabricatorDaemonLogEvent extends PhabricatorDaemonDAO {
protected $logID;
protected $logType;
protected $message;
protected $epoch;
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
}
diff --git a/src/infrastructure/daemon/storage/log/PhabricatorDaemonLog.php b/src/infrastructure/daemon/storage/log/PhabricatorDaemonLog.php
index b128651fa9..0c0e8fc960 100644
--- a/src/infrastructure/daemon/storage/log/PhabricatorDaemonLog.php
+++ b/src/infrastructure/daemon/storage/log/PhabricatorDaemonLog.php
@@ -1,34 +1,34 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorDaemonLog extends PhabricatorDaemonDAO {
+final class PhabricatorDaemonLog extends PhabricatorDaemonDAO {
protected $daemon;
protected $host;
protected $pid;
protected $argv;
public function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'argv' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
}
diff --git a/src/infrastructure/daemon/timeline/cursor/iterator/PhabricatorTimelineIterator.php b/src/infrastructure/daemon/timeline/cursor/iterator/PhabricatorTimelineIterator.php
index 0fc1b5bcd4..370c824fe5 100644
--- a/src/infrastructure/daemon/timeline/cursor/iterator/PhabricatorTimelineIterator.php
+++ b/src/infrastructure/daemon/timeline/cursor/iterator/PhabricatorTimelineIterator.php
@@ -1,116 +1,116 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorTimelineIterator implements Iterator {
+final class PhabricatorTimelineIterator implements Iterator {
protected $cursorName;
protected $eventTypes;
protected $cursor;
protected $index = -1;
protected $events = array();
const LOAD_CHUNK_SIZE = 128;
public function __construct($cursor_name, array $event_types) {
$this->cursorName = $cursor_name;
$this->eventTypes = $event_types;
}
protected function loadEvents() {
if (!$this->cursor) {
$this->cursor = id(new PhabricatorTimelineCursor())->loadOneWhere(
'name = %s',
$this->cursorName);
if (!$this->cursor) {
$cursor = new PhabricatorTimelineCursor();
$cursor->setName($this->cursorName);
$cursor->setPosition(0);
$cursor->save();
$this->cursor = $cursor;
}
}
$event = new PhabricatorTimelineEvent('NULL');
$event_data = new PhabricatorTimelineEventData();
$raw_data = queryfx_all(
$event->establishConnection('r'),
'SELECT event.*, event_data.eventData eventData
FROM %T event
LEFT JOIN %T event_data ON event_data.id = event.dataID
WHERE event.id > %d AND event.type in (%Ls)
ORDER BY event.id ASC LIMIT %d',
$event->getTableName(),
$event_data->getTableName(),
$this->cursor->getPosition(),
$this->eventTypes,
self::LOAD_CHUNK_SIZE);
$events = $event->loadAllFromArray($raw_data);
$events = mpull($events, null, 'getID');
$raw_data = ipull($raw_data, 'eventData', 'id');
foreach ($raw_data as $id => $data) {
if ($data) {
$decoded = json_decode($data, true);
$events[$id]->setData($decoded);
}
}
$this->events = $events;
if ($this->events) {
$this->events = array_values($this->events);
$this->index = 0;
} else {
$this->cursor = null;
}
}
public function current() {
return $this->events[$this->index];
}
public function key() {
return $this->events[$this->index]->getID();
}
public function next() {
if ($this->valid()) {
$this->cursor->setPosition($this->key());
$this->cursor->save();
}
$this->index++;
if (!$this->valid()) {
$this->loadEvents();
}
}
public function valid() {
return isset($this->events[$this->index]);
}
public function rewind() {
if (!$this->valid()) {
$this->loadEvents();
}
}
}
diff --git a/src/infrastructure/daemon/timeline/storage/base/PhabricatorTimelineDAO.php b/src/infrastructure/daemon/timeline/storage/base/PhabricatorTimelineDAO.php
index 406a3a861a..31d2c35c89 100644
--- a/src/infrastructure/daemon/timeline/storage/base/PhabricatorTimelineDAO.php
+++ b/src/infrastructure/daemon/timeline/storage/base/PhabricatorTimelineDAO.php
@@ -1,25 +1,25 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorTimelineDAO extends PhabricatorLiskDAO {
+abstract class PhabricatorTimelineDAO extends PhabricatorLiskDAO {
public function getApplicationName() {
return 'timeline';
}
}
diff --git a/src/infrastructure/daemon/timeline/storage/cursor/PhabricatorTimelineCursor.php b/src/infrastructure/daemon/timeline/storage/cursor/PhabricatorTimelineCursor.php
index a55375c40b..80af3e26ab 100644
--- a/src/infrastructure/daemon/timeline/storage/cursor/PhabricatorTimelineCursor.php
+++ b/src/infrastructure/daemon/timeline/storage/cursor/PhabricatorTimelineCursor.php
@@ -1,42 +1,42 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorTimelineCursor extends PhabricatorTimelineDAO {
+final class PhabricatorTimelineCursor extends PhabricatorTimelineDAO {
protected $name;
protected $position;
public function getIDKey() {
return 'name';
}
public function getConfiguration() {
return array(
self::CONFIG_IDS => self::IDS_MANUAL,
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
public function shouldInsertWhenSaved() {
if ($this->position == 0) {
return true;
}
return false;
}
}
diff --git a/src/infrastructure/daemon/timeline/storage/event/PhabricatorTimelineEvent.php b/src/infrastructure/daemon/timeline/storage/event/PhabricatorTimelineEvent.php
index 8ff507624a..870160be89 100644
--- a/src/infrastructure/daemon/timeline/storage/event/PhabricatorTimelineEvent.php
+++ b/src/infrastructure/daemon/timeline/storage/event/PhabricatorTimelineEvent.php
@@ -1,71 +1,71 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorTimelineEvent extends PhabricatorTimelineDAO {
+final class PhabricatorTimelineEvent extends PhabricatorTimelineDAO {
protected $type;
protected $dataID;
private $data;
public function __construct($type, $data = null) {
parent::__construct();
if (strlen($type) !== 4) {
throw new Exception("Event types must be exactly 4 characters long.");
}
$this->type = $type;
$this->data = $data;
}
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
public function recordEvent() {
if ($this->getID()) {
throw new Exception("Event has already been recorded!");
}
// Save the data first and point to it from the event to avoid a race
// condition where we insert the event before the data and a consumer reads
// it immediately.
if ($this->data !== null) {
$data = new PhabricatorTimelineEventData();
$data->setEventData($this->data);
$data->save();
$this->setDataID($data->getID());
}
$this->save();
}
public function setData($data) {
$this->data = $data;
return $this;
}
public function getData() {
return $this->data;
}
}
diff --git a/src/infrastructure/daemon/timeline/storage/eventdata/PhabricatorTimelineEventData.php b/src/infrastructure/daemon/timeline/storage/eventdata/PhabricatorTimelineEventData.php
index 407d4e1ece..4163578bf6 100644
--- a/src/infrastructure/daemon/timeline/storage/eventdata/PhabricatorTimelineEventData.php
+++ b/src/infrastructure/daemon/timeline/storage/eventdata/PhabricatorTimelineEventData.php
@@ -1,32 +1,32 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorTimelineEventData extends PhabricatorTimelineDAO {
+final class PhabricatorTimelineEventData extends PhabricatorTimelineDAO {
protected $eventData;
public function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'eventData' => self::SERIALIZATION_JSON,
),
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
}
diff --git a/src/infrastructure/daemon/workers/storage/task/PhabricatorWorkerTask.php b/src/infrastructure/daemon/workers/storage/task/PhabricatorWorkerTask.php
index 8eaa414674..5fe32dbe74 100644
--- a/src/infrastructure/daemon/workers/storage/task/PhabricatorWorkerTask.php
+++ b/src/infrastructure/daemon/workers/storage/task/PhabricatorWorkerTask.php
@@ -1,82 +1,82 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorWorkerTask extends PhabricatorWorkerDAO {
+final class PhabricatorWorkerTask extends PhabricatorWorkerDAO {
protected $taskClass;
protected $leaseOwner;
protected $leaseExpires;
protected $failureCount;
protected $dataID;
private $serverTime;
private $localTime;
private $data;
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
public function setServerTime($server_time) {
$this->serverTime = $server_time;
$this->localTime = time();
return $this;
}
public function setLeaseDuration($lease_duration) {
$server_lease_expires = $this->serverTime + $lease_duration;
$this->setLeaseExpires($server_lease_expires);
return $this->save();
}
public function save() {
if ($this->leaseOwner) {
$current_server_time = $this->serverTime + (time() - $this->localTime);
if ($current_server_time >= $this->leaseExpires) {
throw new Exception("Trying to update task after lease expiration!");
}
}
$is_new = !$this->getID();
if ($is_new) {
$this->failureCount = 0;
}
if ($is_new && $this->data) {
$data = new PhabricatorWorkerTaskData();
$data->setData($this->data);
$data->save();
$this->setDataID($data->getID());
}
return parent::save();
}
public function setData($data) {
$this->data = $data;
return $this;
}
public function getData() {
return $this->data;
}
}
diff --git a/src/infrastructure/daemon/workers/storage/taskdata/PhabricatorWorkerTaskData.php b/src/infrastructure/daemon/workers/storage/taskdata/PhabricatorWorkerTaskData.php
index 007aea0892..9e2672a42f 100644
--- a/src/infrastructure/daemon/workers/storage/taskdata/PhabricatorWorkerTaskData.php
+++ b/src/infrastructure/daemon/workers/storage/taskdata/PhabricatorWorkerTaskData.php
@@ -1,32 +1,32 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorWorkerTaskData extends PhabricatorWorkerDAO {
+final class PhabricatorWorkerTaskData extends PhabricatorWorkerDAO {
protected $data;
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_SERIALIZATION => array(
'data' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
}
diff --git a/src/infrastructure/daemon/workers/taskmaster/PhabricatorTaskmasterDaemon.php b/src/infrastructure/daemon/workers/taskmaster/PhabricatorTaskmasterDaemon.php
index a8cc469b8a..8152adc206 100644
--- a/src/infrastructure/daemon/workers/taskmaster/PhabricatorTaskmasterDaemon.php
+++ b/src/infrastructure/daemon/workers/taskmaster/PhabricatorTaskmasterDaemon.php
@@ -1,128 +1,128 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorTaskmasterDaemon extends PhabricatorDaemon {
+final class PhabricatorTaskmasterDaemon extends PhabricatorDaemon {
public function run() {
$lease_ownership_name = $this->getLeaseOwnershipName();
$task_table = new PhabricatorWorkerTask();
$taskdata_table = new PhabricatorWorkerTaskData();
$sleep = 0;
do {
$conn_w = $task_table->establishConnection('w');
queryfx(
$conn_w,
'UPDATE %T SET leaseOwner = %s, leaseExpires = UNIX_TIMESTAMP() + 15
WHERE leaseOwner IS NULL LIMIT 1',
$task_table->getTableName(),
$lease_ownership_name);
$rows = $conn_w->getAffectedRows();
if (!$rows) {
$rows = queryfx(
$conn_w,
'UPDATE %T SET leaseOwner = %s, leaseExpires = UNIX_TIMESTAMP() + 15
WHERE leaseExpires < UNIX_TIMESTAMP() LIMIT 1',
$task_table->getTableName(),
$lease_ownership_name);
$rows = $conn_w->getAffectedRows();
}
if ($rows) {
$data = queryfx_all(
$conn_w,
'SELECT task.*, taskdata.data _taskData, UNIX_TIMESTAMP() _serverTime
FROM %T task LEFT JOIN %T taskdata
ON taskdata.id = task.dataID
WHERE leaseOwner = %s AND leaseExpires > UNIX_TIMESTAMP()
LIMIT 1',
$task_table->getTableName(),
$taskdata_table->getTableName(),
$lease_ownership_name);
$tasks = $task_table->loadAllFromArray($data);
$tasks = mpull($tasks, null, 'getID');
$task_data = array();
foreach ($data as $row) {
$tasks[$row['id']]->setServerTime($row['_serverTime']);
if ($row['_taskData']) {
$task_data[$row['id']] = json_decode($row['_taskData'], true);
} else {
$task_data[$row['id']] = null;
}
}
foreach ($tasks as $task) {
// TODO: We should detect if we acquired a task with an expired lease
// and log about it / bump up failure count.
// TODO: We should detect if we acquired a task with an excessive
// failure count and fail it permanently.
$data = idx($task_data, $task->getID());
$class = $task->getTaskClass();
try {
PhutilSymbolLoader::loadClass($class);
if (!is_subclass_of($class, 'PhabricatorWorker')) {
throw new Exception(
"Task class '{$class}' does not extend PhabricatorWorker.");
}
$worker = newv($class, array($data));
$lease = $worker->getRequiredLeaseTime();
if ($lease !== null) {
$task->setLeaseDuration($lease);
}
$worker->executeTask();
$task->delete();
if ($data !== null) {
queryfx(
$conn_w,
'DELETE FROM %T WHERE id = %d',
$taskdata_table->getTableName(),
$task->getDataID());
}
} catch (Exception $ex) {
$task->setFailureCount($task->getFailureCount() + 1);
$task->save();
throw $ex;
}
}
$sleep = 0;
} else {
$sleep = min($sleep + 1, 30);
}
$this->sleep($sleep);
} while (true);
}
private function getLeaseOwnershipName() {
static $name = null;
if ($name === null) {
$name = getmypid().':'.time().':'.php_uname('n');
}
return $name;
}
}
diff --git a/src/infrastructure/daemon/workers/worker/goodfornothing/PhabricatorGoodForNothingWorker.php b/src/infrastructure/daemon/workers/worker/goodfornothing/PhabricatorGoodForNothingWorker.php
index 6315904aff..9d3e9341d5 100644
--- a/src/infrastructure/daemon/workers/worker/goodfornothing/PhabricatorGoodForNothingWorker.php
+++ b/src/infrastructure/daemon/workers/worker/goodfornothing/PhabricatorGoodForNothingWorker.php
@@ -1,26 +1,26 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Trivial example worker; processes tasks which require no work very slowly.
*/
-class PhabricatorGoodForNothingWorker extends PhabricatorWorker {
+final class PhabricatorGoodForNothingWorker extends PhabricatorWorker {
protected function doWork() {
sleep(10);
}
}
diff --git a/src/infrastructure/events/engine/PhabricatorEventEngine.php b/src/infrastructure/events/engine/PhabricatorEventEngine.php
index 783abeeb88..fa2ac53e2f 100644
--- a/src/infrastructure/events/engine/PhabricatorEventEngine.php
+++ b/src/infrastructure/events/engine/PhabricatorEventEngine.php
@@ -1,31 +1,31 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorEventEngine {
+final class PhabricatorEventEngine {
public static function initialize() {
$listeners = PhabricatorEnv::getEnvConfig('events.listeners');
foreach ($listeners as $listener) {
id(new $listener())->register();
}
// Register the DarkConosole event logger.
id(new DarkConsoleEventPluginAPI())->register();
}
}
diff --git a/src/infrastructure/lint/engine/PhabricatorLintEngine.php b/src/infrastructure/lint/engine/PhabricatorLintEngine.php
index 217709152a..cbad441985 100644
--- a/src/infrastructure/lint/engine/PhabricatorLintEngine.php
+++ b/src/infrastructure/lint/engine/PhabricatorLintEngine.php
@@ -1,50 +1,50 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorLintEngine extends PhutilLintEngine {
+final class PhabricatorLintEngine extends PhutilLintEngine {
public function buildLinters() {
$linters = parent::buildLinters();
$paths = $this->getPaths();
foreach ($paths as $key => $path) {
if (!$this->pathExists($path)) {
unset($paths[$key]);
}
}
$javelin_linter = new PhabricatorJavelinLinter();
$linters[] = $javelin_linter;
foreach ($paths as $path) {
if (strpos($path, 'support/aphlict/') !== false) {
// This stuff is Node.js, not Javelin, so don't apply the Javelin
// linter.
continue;
}
if (preg_match('/\.js$/', $path)) {
$javelin_linter->addPath($path);
$javelin_linter->addData($path, $this->loadData($path));
}
}
return $linters;
}
}
diff --git a/src/infrastructure/lint/hook/xhpastsymbolname/PhabricatorSymbolNameLinter.php b/src/infrastructure/lint/hook/xhpastsymbolname/PhabricatorSymbolNameLinter.php
index b55f248d75..bc342d6da8 100644
--- a/src/infrastructure/lint/hook/xhpastsymbolname/PhabricatorSymbolNameLinter.php
+++ b/src/infrastructure/lint/hook/xhpastsymbolname/PhabricatorSymbolNameLinter.php
@@ -1,39 +1,39 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorSymbolNameLinter extends ArcanistXHPASTLintNamingHook {
+final class PhabricatorSymbolNameLinter extends ArcanistXHPASTLintNamingHook {
public function lintSymbolName($type, $name, $default) {
$matches = null;
if ($type == 'class' &&
preg_match('/^ConduitAPI_(.*)_Method$/', $name, $matches)) {
if (preg_match('/^[a-z]+(_[a-z]+)?$/', $matches[1])) {
// These are permitted since Conduit does reflectioney stuff to figure
// out the method name from the class name.
return null;
} else {
return 'Conduit method implementations should contain lowercase '.
'letters only, with an underscore separating group and method '.
'names for implementations, e.g. '.
'"ConduitAPI_thing_info_Method".';
}
}
return $default;
}
}
diff --git a/src/infrastructure/lint/linter/javelin/PhabricatorJavelinLinter.php b/src/infrastructure/lint/linter/javelin/PhabricatorJavelinLinter.php
index 14172ae4d9..4833d5e944 100644
--- a/src/infrastructure/lint/linter/javelin/PhabricatorJavelinLinter.php
+++ b/src/infrastructure/lint/linter/javelin/PhabricatorJavelinLinter.php
@@ -1,255 +1,255 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorJavelinLinter extends ArcanistLinter {
+final class PhabricatorJavelinLinter extends ArcanistLinter {
private $symbols = array();
private $haveSymbolsBinary;
private $haveWarnedAboutBinary;
const LINT_PRIVATE_ACCESS = 1;
const LINT_MISSING_DEPENDENCY = 2;
const LINT_UNNECESSARY_DEPENDENCY = 3;
const LINT_UNKNOWN_DEPENDENCY = 4;
const LINT_MISSING_BINARY = 5;
public function willLintPaths(array $paths) {
if ($this->haveSymbolsBinary === null) {
$binary = $this->getSymbolsBinaryPath();
$this->haveSymbolsBinary = Filesystem::pathExists($binary);
if (!$this->haveSymbolsBinary) {
return;
}
}
$futures = array();
foreach ($paths as $path) {
$future = $this->newSymbolsFuture($path);
$futures[$path] = $future;
}
foreach (Futures($futures)->limit(8) as $path => $future) {
$this->symbols[$path] = $future->resolvex();
}
}
public function getLinterName() {
return 'JAVELIN';
}
public function getLintSeverityMap() {
return array(
self::LINT_MISSING_BINARY => ArcanistLintSeverity::SEVERITY_WARNING,
);
}
public function getLintNameMap() {
return array(
self::LINT_PRIVATE_ACCESS => 'Private Method/Member Access',
self::LINT_MISSING_DEPENDENCY => 'Missing Javelin Dependency',
self::LINT_UNNECESSARY_DEPENDENCY => 'Unnecessary Javelin Dependency',
self::LINT_UNKNOWN_DEPENDENCY => 'Unknown Javelin Dependency',
self::LINT_MISSING_BINARY => '`javelinsymbols` Binary Not Built',
);
}
public function lintPath($path) {
if (!$this->haveSymbolsBinary) {
if (!$this->haveWarnedAboutBinary) {
$this->haveWarnedAboutBinary = true;
// TODO: Write build documentation for the Javelin binaries and point
// the user at it.
$this->raiseLintAtLine(
1,
0,
self::LINT_MISSING_BINARY,
"The 'javelinsymbols' binary in the Javelin project has not been ".
"built, so the Javelin linter can't run. This isn't a big concern, ".
"but means some Javelin problems can't be automatically detected.");
}
return;
}
list($uses, $installs) = $this->getUsedAndInstalledSymbolsForPath($path);
foreach ($uses as $symbol => $line) {
$parts = explode('.', $symbol);
foreach ($parts as $part) {
if ($part[0] == '_' && $part[1] != '_') {
$base = implode('.', array_slice($parts, 0, 2));
if (!array_key_exists($base, $installs)) {
$this->raiseLintAtLine(
$line,
0,
self::LINT_PRIVATE_ACCESS,
"This file accesses private symbol '{$symbol}' across file ".
"boundaries. You may only access private members and methods ".
"from the file where they are defined.");
}
break;
}
}
}
if ($this->getEngine()->getCommitHookMode()) {
// Don't do the dependency checks in commit-hook mode because we won't
// have an available working copy.
return;
}
$external_classes = array();
foreach ($uses as $symbol => $line) {
$parts = explode('.', $symbol);
$class = implode('.', array_slice($parts, 0, 2));
if (!array_key_exists($class, $external_classes) &&
!array_key_exists($class, $installs)) {
$external_classes[$class] = $line;
}
}
$celerity = CelerityResourceMap::getInstance();
$path = preg_replace(
'@^externals/javelin/src/@',
'webroot/rsrc/js/javelin/',
$path);
$need = $external_classes;
$info = $celerity->lookupFileInformation(substr($path, strlen('webroot')));
if (!$info) {
$info = array();
}
$requires = idx($info, 'requires', array());
foreach ($requires as $key => $name) {
$symbol_info = $celerity->lookupSymbolInformation($name);
if (!$symbol_info) {
$this->raiseLintAtLine(
0,
0,
self::LINT_UNKNOWN_DEPENDENCY,
"This file @requires component '{$name}', but it does not ".
"exist. You may need to rebuild the Celerity map.");
unset($requires[$key]);
continue;
}
$symbol_path = 'webroot'.$symbol_info['disk'];
list($ignored, $req_install) = $this->getUsedAndInstalledSymbolsForPath(
$symbol_path);
if (array_intersect_key($req_install, $external_classes)) {
$need = array_diff_key($need, $req_install);
unset($requires[$key]);
}
}
foreach ($need as $class => $line) {
$this->raiseLintAtLine(
$line,
0,
self::LINT_MISSING_DEPENDENCY,
"This file uses '{$class}' but does not @requires the component ".
"which installs it. You may need to rebuild the Celerity map.");
}
foreach ($requires as $component) {
$this->raiseLintAtLine(
0,
0,
self::LINT_UNNECESSARY_DEPENDENCY,
"This file @requires component '{$component}' but does not use ".
"anything it provides.");
}
}
private function loadSymbols($path) {
if (empty($this->symbols[$path])) {
$this->symbols[$path] = $this->newSymbolsFuture($path)->resolvex();
}
return $this->symbols[$path];
}
private function newSymbolsFuture($path) {
$javelinsymbols = $this->getSymbolsBinaryPath();
$future = new ExecFuture($javelinsymbols.' # '.escapeshellarg($path));
$future->write($this->getData($path));
return $future;
}
private function getSymbolsBinaryPath() {
$root = dirname(phutil_get_library_root('phabricator'));
$support = $root.'/externals/javelin/support';
return $support.'/javelinsymbols/javelinsymbols';
}
private function getUsedAndInstalledSymbolsForPath($path) {
list($symbols) = $this->loadSymbols($path);
$symbols = trim($symbols);
$uses = array();
$installs = array();
if (empty($symbols)) {
// This file has no symbols.
return array($uses, $installs);
}
$symbols = explode("\n", trim($symbols));
foreach ($symbols as $line) {
$matches = null;
if (!preg_match('/^([?+])([^:]*):(\d+)$/', $line, $matches)) {
throw new Exception(
"Received malformed output from `javelinsymbols`.");
}
$type = $matches[1];
$symbol = $matches[2];
$line = $matches[3];
switch ($type) {
case '?':
$uses[$symbol] = $line;
break;
case '+':
$installs['JX.'.$symbol] = $line;
break;
}
}
$contents = $this->getData($path);
$matches = null;
$count = preg_match_all(
'/@javelin-installs\W+(\S+)/',
$contents,
$matches,
PREG_PATTERN_ORDER);
if ($count) {
foreach ($matches[1] as $symbol) {
$installs[$symbol] = 0;
}
}
return array($uses, $installs);
}
}
diff --git a/src/infrastructure/markup/remarkup/markuprule/differential/PhabricatorRemarkupRuleDifferential.php b/src/infrastructure/markup/remarkup/markuprule/differential/PhabricatorRemarkupRuleDifferential.php
index 0e2bdb2558..f81c3e3cfd 100644
--- a/src/infrastructure/markup/remarkup/markuprule/differential/PhabricatorRemarkupRuleDifferential.php
+++ b/src/infrastructure/markup/remarkup/markuprule/differential/PhabricatorRemarkupRuleDifferential.php
@@ -1,29 +1,29 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group markup
*/
-class PhabricatorRemarkupRuleDifferential
+final class PhabricatorRemarkupRuleDifferential
extends PhabricatorRemarkupRuleObjectName {
protected function getObjectNamePrefix() {
return 'D';
}
}
diff --git a/src/infrastructure/markup/remarkup/markuprule/diffusion/PhabricatorRemarkupRuleDiffusion.php b/src/infrastructure/markup/remarkup/markuprule/diffusion/PhabricatorRemarkupRuleDiffusion.php
index 01a4385bf5..2bf86bdfa2 100644
--- a/src/infrastructure/markup/remarkup/markuprule/diffusion/PhabricatorRemarkupRuleDiffusion.php
+++ b/src/infrastructure/markup/remarkup/markuprule/diffusion/PhabricatorRemarkupRuleDiffusion.php
@@ -1,37 +1,37 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group markup
*/
-class PhabricatorRemarkupRuleDiffusion
+final class PhabricatorRemarkupRuleDiffusion
extends PhutilRemarkupRule {
public function apply($text) {
return preg_replace_callback(
'@\br([A-Z]+[a-f0-9]+)\b@',
array($this, 'markupDiffusionLink'),
$text);
}
public function markupDiffusionLink($matches) {
return $this->getEngine()->storeText(
'<a href="/r'.$matches[1].'">r'.$matches[1].'</a>');
}
}
diff --git a/src/infrastructure/markup/remarkup/markuprule/embedobject/PhabricatorRemarkupRuleEmbedFile.php b/src/infrastructure/markup/remarkup/markuprule/embedobject/PhabricatorRemarkupRuleEmbedFile.php
index 87da2996b3..9a6726a0ca 100644
--- a/src/infrastructure/markup/remarkup/markuprule/embedobject/PhabricatorRemarkupRuleEmbedFile.php
+++ b/src/infrastructure/markup/remarkup/markuprule/embedobject/PhabricatorRemarkupRuleEmbedFile.php
@@ -1,124 +1,124 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group markup
*/
-class PhabricatorRemarkupRuleEmbedFile
+final class PhabricatorRemarkupRuleEmbedFile
extends PhutilRemarkupRule {
public function apply($text) {
return preg_replace_callback(
"@{F(\d+)([^}]+?)?}@",
array($this, 'markupEmbedFile'),
$text);
}
public function markupEmbedFile($matches) {
$file = null;
if ($matches[1]) {
// TODO: This is pretty inefficient if there are a bunch of files.
$file = id(new PhabricatorFile())->load($matches[1]);
}
if (!$file) {
return $matches[0];
}
$options = array(
'size' => 'thumb',
'layout' => 'left',
'float' => false,
);
if (!empty($matches[2])) {
$matches[2] = trim($matches[2], ', ');
$options = PhutilSimpleOptions::parse($matches[2]) + $options;
}
switch ($options['size']) {
case 'full':
$src_uri = $file->getBestURI();
$link = null;
break;
case 'thumb':
default:
$src_uri = $file->getThumb160x120URI();
$link = $file->getBestURI();
break;
}
$embed = phutil_render_tag(
'img',
array(
'src' => $src_uri,
'class' => 'phabricator-remarkup-embed-image',
));
if ($link) {
$embed = phutil_render_tag(
'a',
array(
'href' => $link,
'target' => '_blank',
),
$embed);
}
$layout_class = null;
switch ($options['layout']) {
case 'right':
case 'center':
case 'inline':
case 'left':
$layout_class = 'phabricator-remarkup-embed-layout-'.$options['layout'];
break;
default:
$layout_class = 'phabricator-remarkup-embed-layout-left';
break;
}
if ($options['float']) {
switch ($options['layout']) {
case 'center':
case 'inline':
break;
case 'right':
$layout_class .= ' phabricator-remarkup-embed-float-right';
break;
case 'left':
default:
$layout_class .= ' phabricator-remarkup-embed-float-left';
break;
}
}
if ($layout_class) {
$embed = phutil_render_tag(
'div',
array(
'class' => $layout_class,
),
$embed);
}
return $this->getEngine()->storeText($embed);
}
}
diff --git a/src/infrastructure/markup/remarkup/markuprule/imagemacro/PhabricatorRemarkupRuleImageMacro.php b/src/infrastructure/markup/remarkup/markuprule/imagemacro/PhabricatorRemarkupRuleImageMacro.php
index 0df253ebd8..27a1e5a5f4 100644
--- a/src/infrastructure/markup/remarkup/markuprule/imagemacro/PhabricatorRemarkupRuleImageMacro.php
+++ b/src/infrastructure/markup/remarkup/markuprule/imagemacro/PhabricatorRemarkupRuleImageMacro.php
@@ -1,92 +1,92 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group markup
*/
-class PhabricatorRemarkupRuleImageMacro
+final class PhabricatorRemarkupRuleImageMacro
extends PhutilRemarkupRule {
const RANDOM_IMAGE_NAME = 'randomon';
private $images = array();
private $hash = 0;
public function __construct() {
$rows = id(new PhabricatorFileImageMacro())->loadAll();
foreach ($rows as $row) {
$this->images[$row->getName()] = $row->getFilePHID();
}
$this->images[self::RANDOM_IMAGE_NAME] = '';
$this->hash = 0;
}
public function apply($text) {
return preg_replace_callback(
'@\b([a-zA-Z0-9_\-]+)\b@',
array($this, 'markupImageMacro'),
$text);
}
/**
* Silly function for generating some 'randomness' based on the
* words in the text
*/
private function updateHash($word) {
// Simple Jenkins hash
for ($ii = 0; $ii < strlen($word); $ii++) {
$this->hash += ord($word[$ii]);
$this->hash += ($this->hash << 10);
$this->hash ^= ($this->hash >> 6);
}
}
public function markupImageMacro($matches) {
// Update the hash that is used for defining each 'randomon' image. This way
// each 'randomon' image will be different, but they won't change when the
// text is updated.
$this->updateHash($matches[1]);
if (array_key_exists($matches[1], $this->images)) {
if ($matches[1] === self::RANDOM_IMAGE_NAME) {
$keys = array_keys($this->images);
$phid = $this->images[$keys[$this->hash % count($this->images)]];
} else {
$phid = $this->images[$matches[1]];
}
$file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $phid);
if ($file) {
$src_uri = $file->getBestURI();
} else {
$src_uri = null;
}
$img = phutil_render_tag(
'img',
array(
'src' => $src_uri,
'alt' => $matches[1],
'title' => $matches[1]),
null);
return $this->getEngine()->storeText($img);
} else {
return $matches[1];
}
}
}
diff --git a/src/infrastructure/markup/remarkup/markuprule/maniphest/PhabricatorRemarkupRuleManiphest.php b/src/infrastructure/markup/remarkup/markuprule/maniphest/PhabricatorRemarkupRuleManiphest.php
index accf97bfdd..adb2cea862 100644
--- a/src/infrastructure/markup/remarkup/markuprule/maniphest/PhabricatorRemarkupRuleManiphest.php
+++ b/src/infrastructure/markup/remarkup/markuprule/maniphest/PhabricatorRemarkupRuleManiphest.php
@@ -1,29 +1,29 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group markup
*/
-class PhabricatorRemarkupRuleManiphest
+final class PhabricatorRemarkupRuleManiphest
extends PhabricatorRemarkupRuleObjectName {
protected function getObjectNamePrefix() {
return 'T';
}
}
diff --git a/src/infrastructure/markup/remarkup/markuprule/mention/PhabricatorRemarkupRuleMention.php b/src/infrastructure/markup/remarkup/markuprule/mention/PhabricatorRemarkupRuleMention.php
index cd3ce046ab..474b276a19 100644
--- a/src/infrastructure/markup/remarkup/markuprule/mention/PhabricatorRemarkupRuleMention.php
+++ b/src/infrastructure/markup/remarkup/markuprule/mention/PhabricatorRemarkupRuleMention.php
@@ -1,139 +1,139 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group markup
*/
-class PhabricatorRemarkupRuleMention
+final class PhabricatorRemarkupRuleMention
extends PhutilRemarkupRule {
const KEY_RULE_MENTION = 'rule.mention';
const KEY_RULE_MENTION_ORIGINAL = 'rule.mention.original';
const KEY_MENTIONED = 'phabricator.mentioned-user-phids';
public function apply($text) {
// NOTE: Negative lookahead for period prevents us from picking up email
// addresses, while allowing constructs like "@tomo, lol". The negative
// lookbehind for a word character prevents us from matching "mail@lists"
// while allowing "@tomo/@mroch". The negative lookahead prevents us from
// matching "@joe.com" while allowing us to match "hey, @joe.".
$regexp = '/(?<!\w)@([a-zA-Z0-9]+)\b(?![.]\w)/';
return preg_replace_callback(
$regexp,
array($this, 'markupMention'),
$text);
}
private function markupMention($matches) {
$engine = $this->getEngine();
$token = $engine->storeText('');
// Store the original text exactly so we can preserve casing if it doesn't
// resolve into a username.
$original_key = self::KEY_RULE_MENTION_ORIGINAL;
$original = $engine->getTextMetadata($original_key, array());
$original[$token] = $matches[1];
$engine->setTextMetadata($original_key, $original);
$metadata_key = self::KEY_RULE_MENTION;
$metadata = $engine->getTextMetadata($metadata_key, array());
$username = strtolower($matches[1]);
if (empty($metadata[$username])) {
$metadata[$username] = array();
}
$metadata[$username][] = $token;
$engine->setTextMetadata($metadata_key, $metadata);
return $token;
}
public function didMarkupText() {
$engine = $this->getEngine();
$metadata_key = self::KEY_RULE_MENTION;
$metadata = $engine->getTextMetadata($metadata_key, array());
if (empty($metadata)) {
// No mentions, or we already processed them.
return;
}
$original_key = self::KEY_RULE_MENTION_ORIGINAL;
$original = $engine->getTextMetadata($original_key, array());
$usernames = array_keys($metadata);
$user_table = new PhabricatorUser();
$real_user_names = queryfx_all(
$user_table->establishConnection('r'),
'SELECT username, phid, realName FROM %T WHERE username IN (%Ls)',
$user_table->getTableName(),
$usernames);
$actual_users = array();
$mentioned_key = self::KEY_MENTIONED;
$mentioned = $engine->getTextMetadata($mentioned_key, array());
foreach ($real_user_names as $row) {
$actual_users[strtolower($row['username'])] = $row;
$mentioned[$row['phid']] = $row['phid'];
}
$engine->setTextMetadata($mentioned_key, $mentioned);
foreach ($metadata as $username => $tokens) {
$exists = isset($actual_users[$username]);
$class = $exists
? 'phabricator-remarkup-mention-exists'
: 'phabricator-remarkup-mention-unknown';
if ($exists) {
$tag = phutil_render_tag(
'a',
array(
'class' => $class,
'href' => '/p/'.$actual_users[$username]['username'].'/',
'target' => '_blank',
'title' => $actual_users[$username]['realName'],
),
phutil_escape_html('@'.$actual_users[$username]['username']));
foreach ($tokens as $token) {
$engine->overwriteStoredText($token, $tag);
}
} else {
// NOTE: The structure here is different from the 'exists' branch,
// because we want to preserve the original text capitalization and it
// may differ for each token.
foreach ($tokens as $token) {
$tag = phutil_render_tag(
'span',
array(
'class' => $class,
),
phutil_escape_html('@'.idx($original, $token, $username)));
$engine->overwriteStoredText($token, $tag);
}
}
}
// Don't re-process these mentions.
$engine->setTextMetadata($metadata_key, array());
}
}
diff --git a/src/infrastructure/markup/remarkup/markuprule/paste/PhabricatorRemarkupRulePaste.php b/src/infrastructure/markup/remarkup/markuprule/paste/PhabricatorRemarkupRulePaste.php
index b734219b86..09c2a3d3aa 100644
--- a/src/infrastructure/markup/remarkup/markuprule/paste/PhabricatorRemarkupRulePaste.php
+++ b/src/infrastructure/markup/remarkup/markuprule/paste/PhabricatorRemarkupRulePaste.php
@@ -1,29 +1,29 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group markup
*/
-class PhabricatorRemarkupRulePaste
+final class PhabricatorRemarkupRulePaste
extends PhabricatorRemarkupRuleObjectName {
protected function getObjectNamePrefix() {
return 'P';
}
}
diff --git a/src/infrastructure/markup/remarkup/markuprule/phriction/PhabricatorRemarkupRulePhriction.php b/src/infrastructure/markup/remarkup/markuprule/phriction/PhabricatorRemarkupRulePhriction.php
index 14a67a8f7f..4789a66400 100644
--- a/src/infrastructure/markup/remarkup/markuprule/phriction/PhabricatorRemarkupRulePhriction.php
+++ b/src/infrastructure/markup/remarkup/markuprule/phriction/PhabricatorRemarkupRulePhriction.php
@@ -1,74 +1,74 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group markup
*/
-class PhabricatorRemarkupRulePhriction
+final class PhabricatorRemarkupRulePhriction
extends PhutilRemarkupRule {
public function apply($text) {
return preg_replace_callback(
'@\B\\[\\[([^|\\]]+)(?:\\|([^\\]]+))?\\]\\]\B@U',
array($this, 'markupDocumentLink'),
$text);
}
public function markupDocumentLink($matches) {
$slug = trim($matches[1]);
$name = trim(idx($matches, 2, $slug));
// If whatever is being linked to begins with "/" or has "://", treat it
// as a URI instead of a wiki page.
$is_uri = preg_match('@(^/)|(://)@', $slug);
if ($is_uri) {
$protocols = $this->getEngine()->getConfig(
'uri.allowed-protocols',
array());
$protocol = id(new PhutilURI($slug))->getProtocol();
if (!idx($protocols, $protocol)) {
// Don't treat this as a URI if it's not an allowed protocol.
$is_uri = false;
}
}
if ($is_uri) {
$uri = $slug;
// Leave the name unchanged, i.e. link the whole URI if there's no
// explicit name.
} else {
$name = explode('/', trim($name, '/'));
$name = end($name);
$slug = PhrictionDocument::normalizeSlug($slug);
$uri = PhrictionDocument::getSlugURI($slug);
}
return $this->getEngine()->storeText(
phutil_render_tag(
'a',
array(
'href' => $uri,
'class' => $is_uri ? null : 'phriction-link',
),
phutil_escape_html($name)));
}
}
diff --git a/src/infrastructure/markup/remarkup/markuprule/proxyimage/PhabricatorRemarkupRuleProxyImage.php b/src/infrastructure/markup/remarkup/markuprule/proxyimage/PhabricatorRemarkupRuleProxyImage.php
index eb4d2ea38b..28519c8b67 100644
--- a/src/infrastructure/markup/remarkup/markuprule/proxyimage/PhabricatorRemarkupRuleProxyImage.php
+++ b/src/infrastructure/markup/remarkup/markuprule/proxyimage/PhabricatorRemarkupRuleProxyImage.php
@@ -1,61 +1,61 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group markup
*/
-class PhabricatorRemarkupRuleProxyImage
+final class PhabricatorRemarkupRuleProxyImage
extends PhutilRemarkupRule {
public function apply($text) {
$filetypes = '(?:\.jpe?g|.png|.gif)';
$text = preg_replace_callback(
'@[<](\w{3,}://.+?'.$filetypes.')[>]@',
array($this, 'markupProxyImage'),
$text);
$text = preg_replace_callback(
'@(?<=^|\s)(\w{3,}://\S+'.$filetypes.')(?=\s|$)@',
array($this, 'markupProxyImage'),
$text);
return $text;
}
public function markupProxyImage($matches) {
$uri = PhabricatorFileProxyImage::getProxyImageURI($matches[1]);
return $this->getEngine()->storeText(
phutil_render_tag(
'a',
array(
'href' => $uri,
'target' => '_blank',
),
phutil_render_tag(
'img',
array(
'src' => $uri,
'class' => 'remarkup-proxy-image',
))));
}
}
diff --git a/src/infrastructure/markup/remarkup/markuprule/youtube/PhabricatorRemarkupRuleYoutube.php b/src/infrastructure/markup/remarkup/markuprule/youtube/PhabricatorRemarkupRuleYoutube.php
index 52b6575e86..4351b95d34 100644
--- a/src/infrastructure/markup/remarkup/markuprule/youtube/PhabricatorRemarkupRuleYoutube.php
+++ b/src/infrastructure/markup/remarkup/markuprule/youtube/PhabricatorRemarkupRuleYoutube.php
@@ -1,59 +1,59 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group markup
*/
-class PhabricatorRemarkupRuleYoutube
+final class PhabricatorRemarkupRuleYoutube
extends PhutilRemarkupRule {
public function apply($text) {
$this->uri = new PhutilURI($text);
if ($this->uri->getDomain() &&
preg_match('/youtube\.com$/', $this->uri->getDomain())) {
return $this->markupYoutubeLink();
}
return $text;
}
public function markupYoutubeLink() {
$v = idx($this->uri->getQueryParams(), 'v');
if ($v) {
$youtube_src = 'https://www.youtube.com/embed/'.$v;
$iframe =
'<div class="embedded-youtube-video">'.
phutil_render_tag(
'iframe',
array(
'width' => '650',
'height' => '400',
'style' => 'margin: 1em auto; border: 0px;',
'src' => $youtube_src,
'frameborder' => 0,
),
'').
'</div>';
return $this->getEngine()->storeText($iframe);
} else {
return $this->uri;
}
}
}
diff --git a/src/infrastructure/setup/PhabricatorSetup.php b/src/infrastructure/setup/PhabricatorSetup.php
index ca4e8c8c8f..dd53ef6490 100644
--- a/src/infrastructure/setup/PhabricatorSetup.php
+++ b/src/infrastructure/setup/PhabricatorSetup.php
@@ -1,793 +1,793 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorSetup {
+final class PhabricatorSetup {
public static function runSetup() {
header("Content-Type: text/plain");
self::write("PHABRICATOR SETUP\n\n");
// Force browser to stop buffering.
self::write(str_repeat(' ', 2048));
usleep(250000);
self::write("This setup mode will guide you through setting up your ".
"Phabricator configuration.\n");
self::writeHeader("CORE CONFIGURATION");
// NOTE: Test this first since other tests depend on the ability to
// execute system commands and will fail if safe_mode is enabled.
$safe_mode = ini_get('safe_mode');
if ($safe_mode) {
self::writeFailure();
self::write(
"Setup failure! You have 'safe_mode' enabled. Phabricator will not ".
"run in safe mode, and it has been deprecated in PHP 5.3 and removed ".
"in PHP 5.4.\n");
return;
} else {
self::write(" okay PHP's deprecated 'safe_mode' is disabled.\n");
}
// NOTE: Also test this early since we can't include files from other
// libraries if this is set strictly.
$open_basedir = ini_get('open_basedir');
if ($open_basedir) {
// 'open_basedir' restricts which files we're allowed to access with
// file operations. This might be okay -- we don't need to write to
// arbitrary places in the filesystem -- but we need to access certain
// resources. This setting is unlikely to be providing any real measure
// of security so warn even if things look OK.
try {
phutil_require_module('phutil', 'utils');
$open_libphutil = true;
} catch (Exception $ex) {
$message = $ex->getMessage();
self::write("Unable to load modules from libphutil: {$message}\n");
$open_libphutil = false;
}
try {
phutil_require_module('arcanist', 'workflow/base');
$open_arcanist = true;
} catch (Exception $ex) {
$message = $ex->getMessage();
self::write("Unable to load modules from Arcanist: {$message}\n");
$open_arcanist = false;
}
$open_urandom = @fopen('/dev/urandom', 'r');
if (!$open_urandom) {
self::write("Unable to open /dev/urandom!\n");
}
try {
$tmp = new TempFile();
file_put_contents($tmp, '.');
$open_tmp = @fopen((string)$tmp, 'r');
} catch (Exception $ex) {
$message = $ex->getMessage();
$dir = sys_get_temp_dir();
self::write("Unable to open temp files from '{$dir}': {$message}\n");
$open_tmp = false;
}
if (!$open_urandom || !$open_tmp || !$open_libphutil || !$open_arcanist) {
self::writeFailure();
self::write(
"Setup failure! Your server is configured with 'open_basedir' in ".
"php.ini which prevents Phabricator from opening files it needs to ".
"access. Either make the setting more permissive or remove it. It ".
"is unlikely you derive significant security benefits from having ".
"this configured; files outside this directory can still be ".
"accessed through system command execution.");
return;
} else {
self::write(
"[WARN] You have an 'open_basedir' configured in your php.ini. ".
"Although the setting seems permissive enough that Phabricator ".
"will run properly, you may run into problems because of it. It is ".
"unlikely you gain much real security benefit from having it ".
"configured, because the application can still access files outside ".
"the 'open_basedir' by running system commands.\n");
}
} else {
self::write(" okay 'open_basedir' is not set.\n");
}
if (!PhabricatorEnv::getEnvConfig('security.alternate-file-domain')) {
self::write(
"[WARN] You have not configured 'security.alternate-file-domain'. ".
"This makes your installation vulnerable to attack. Make sure you ".
"read the documentation for this parameter and understand the ".
"consequences of leaving it unconfigured.\n");
}
$path = getenv('PATH');
if (empty($path)) {
self::writeFailure();
self::write(
"Setup failure! The environmental \$PATH variable is empty. ".
"Phabricator needs to execute system commands like 'svn', 'git', ".
"'hg', and 'diff'. Set up your webserver so that it passes a valid ".
"\$PATH to the PHP process.\n\n");
if (php_sapi_name() == 'fpm-fcgi') {
self::write(
"You're running php-fpm, so the easiest way to do this is to add ".
"this line to your php-fpm.conf:\n\n".
" env[PATH] = /usr/local/bin:/usr/bin:/bin\n\n".
"Then restart php-fpm.\n");
}
return;
} else {
self::write(" okay \$PATH is nonempty.\n");
}
self::write("[OKAY] Core configuration OKAY.\n");
self::writeHeader("REQUIRED PHP EXTENSIONS");
$extensions = array(
'mysql',
'hash',
'json',
'openssl',
'mbstring',
'iconv',
// There is a chance we might not need this, but some configurations (like
// Amazon SES) will require it. Just mark it 'required' since it's widely
// available and relatively core.
'curl',
);
foreach ($extensions as $extension) {
$ok = self::requireExtension($extension);
if (!$ok) {
self::writeFailure();
self::write("Setup failure! Install PHP extension '{$extension}'.");
return;
}
}
list($err, $stdout, $stderr) = exec_manual('which php');
if ($err) {
self::writeFailure();
self::write("Unable to locate 'php' on the command line from the web ".
"server. Verify that 'php' is in the webserver's PATH.\n".
" err: {$err}\n".
"stdout: {$stdout}\n".
"stderr: {$stderr}\n");
return;
} else {
self::write(" okay PHP binary found on the command line.\n");
$php_bin = trim($stdout);
}
// NOTE: In cPanel + suphp installs, 'php' may be the PHP CGI SAPI, not the
// PHP CLI SAPI. proc_open() will pass the environment to the child process,
// which will re-execute the webpage (causing an infinite number of
// processes to spawn). To test that the 'php' binary is safe to execute,
// we call php_sapi_name() using "env -i" to wipe the environment so it
// doesn't execute another reuqest if it's the wrong binary. We can't use
// "-r" because php-cgi doesn't support that flag.
$tmp_file = new TempFile('sapi.php');
Filesystem::writeFile($tmp_file, '<?php echo php_sapi_name();');
list($err, $stdout, $stderr) = exec_manual(
'/usr/bin/env -i %s -f %s',
$php_bin,
$tmp_file);
if ($err) {
self::writeFailure();
self::write("Unable to execute 'php' on the command line from the web ".
"server.\n".
" err: {$err}\n".
"stdout: {$stdout}\n".
"stderr: {$stderr}\n");
return;
} else {
self::write(" okay PHP is available from the command line.\n");
$sapi = trim($stdout);
if ($sapi != 'cli') {
self::writeFailure();
self::write(
"The 'php' binary on this system uses the '{$sapi}' SAPI, but the ".
"'cli' SAPI is expected. Replace 'php' with the php-cli SAPI ".
"binary, or edit your webserver configuration so the first 'php' ".
"in PATH is the 'cli' SAPI.\n\n".
"If you're running cPanel with suphp, the easiest way to fix this ".
"is to add '/usr/local/bin' before '/usr/bin' for 'env_path' in ".
"suconf.php:\n\n".
' env_path="/bin:/usr/local/bin:/usr/bin"'.
"\n\n");
return;
} else {
self::write(" okay 'php' is CLI SAPI.\n");
}
}
$root = dirname(phutil_get_library_root('phabricator'));
// On RHEL6, doing a distro install of pcntl makes it available from the
// CLI binary but not from the Apache module. This isn't entirely
// unreasonable and we don't need it from Apache, so do an explicit test
// for CLI availability.
list($err, $stdout, $stderr) = exec_manual(
'%s/scripts/setup/pcntl_available.php',
$root);
if ($err) {
self::writeFailure();
self::write("Unable to execute scripts/setup/pcntl_available.php to ".
"test for the availability of pcntl from the CLI.\n".
" err: {$err}\n".
"stdout: {$stdout}\n".
"stderr: {$stderr}\n");
return;
} else {
if (trim($stdout) == 'YES') {
self::write(" okay pcntl is available from the command line.\n");
self::write("[OKAY] All extensions OKAY\n");
} else {
self::write(" warn pcntl is not available!\n");
self::write("[WARN] *** WARNING *** pcntl extension not available. ".
"You will not be able to run daemons.\n");
}
}
self::writeHeader("GIT SUBMODULES");
if (!Filesystem::pathExists($root.'/.git')) {
self::write(" skip Not a git clone.\n\n");
} else {
list($info) = execx(
'(cd %s && git submodule status)',
$root);
foreach (explode("\n", rtrim($info)) as $line) {
$matches = null;
if (!preg_match('/^(.)([0-9a-f]{40}) (\S+)(?: |$)/', $line, $matches)) {
self::writeFailure();
self::write(
"Setup failure! 'git submodule' produced unexpected output:\n".
$line);
return;
}
$status = $matches[1];
$module = $matches[3];
switch ($status) {
case '-':
case '+':
case 'U':
self::writeFailure();
self::write(
"Setup failure! Git submodule '{$module}' is not up to date. ".
"Run:\n\n".
" cd {$root} && git submodule update --init\n\n".
"...to update submodules.");
return;
case ' ':
self::write(" okay Git submodule '{$module}' up to date.\n");
break;
default:
self::writeFailure();
self::write(
"Setup failure! 'git submodule' reported unknown status ".
"'{$status}' for submodule '{$module}'. This is a bug; report ".
"it to the Phabricator maintainers.");
return;
}
}
}
self::write("[OKAY] All submodules OKAY.\n");
self::writeHeader("BASIC CONFIGURATION");
$env = PhabricatorEnv::getEnvConfig('phabricator.env');
if ($env == 'production' || $env == 'default' || $env == 'development') {
self::writeFailure();
self::write(
"Setup failure! Your PHABRICATOR_ENV is set to '{$env}', which is ".
"a Phabricator environmental default. You should create a custom ".
"environmental configuration instead of editing the defaults ".
"directly. See this document for instructions:\n");
self::writeDoc('article/Configuration_Guide.html');
return;
} else {
$host = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
$host_uri = new PhutilURI($host);
$protocol = $host_uri->getProtocol();
$allowed_protocols = array(
'http' => true,
'https' => true,
);
if (empty($allowed_protocols[$protocol])) {
self::writeFailure();
self::write(
"You must specify the protocol over which your host works (e.g.: ".
"\"http:// or https://\")\nin your custom config file.\nRefer to ".
"'default.conf.php' for documentation on configuration options.\n");
return;
}
if (preg_match('/.*\/$/', $host)) {
self::write(" okay phabricator.base-uri protocol\n");
} else {
self::writeFailure();
self::write(
"You must add a trailing slash at the end of the host\n(e.g.: ".
"\"http://phabricator.example.com/ instead of ".
"http://phabricator.example.com\")\nin your custom config file.".
"\nRefer to 'default.conf.php' for documentation on configuration ".
"options.\n");
return;
}
$host_domain = $host_uri->getDomain();
if (strpos($host_domain, '.') !== false) {
self::write(" okay phabricator.base-uri domain\n");
} else {
self::writeFailure();
self::write(
"You must host Phabricator on a domain that contains a dot ('.'). ".
"The current domain, '{$host_domain}', does not have a dot, so some ".
"browsers will not set cookies on it. For instance, ".
"'http://example.com/ is OK, but 'http://example/' won't work. ".
"If you are using localhost, create an entry in the hosts file like ".
"'127.0.0.1 example.com', and access the localhost with ".
"'http://example.com/'.");
return;
}
}
$timezone = nonempty(
PhabricatorEnv::getEnvConfig('phabricator.timezone'),
ini_get('date.timezone'));
if (!$timezone) {
self::writeFailure();
self::write(
"Setup failure! Your configuration fails to specify a server ".
"timezone. Either set 'date.timezone' in your php.ini or ".
"'phabricator.timezone' in your Phabricator configuration. See the ".
"PHP documentation for a list of supported timezones:\n\n".
"http://us.php.net/manual/en/timezones.php\n");
return;
} else {
self::write(" okay Timezone '{$timezone}' configured.\n");
}
self::write("[OKAY] Basic configuration OKAY\n");
$issue_gd_warning = false;
self::writeHeader('GD LIBRARY');
if (extension_loaded('gd')) {
self::write(" okay Extension 'gd' is loaded.\n");
$image_type_map = array(
'imagepng' => 'PNG',
'imagegif' => 'GIF',
'imagejpeg' => 'JPEG',
);
foreach ($image_type_map as $function => $image_type) {
if (function_exists($function)) {
self::write(" okay Support for '{$image_type}' is available.\n");
} else {
self::write(" warn Support for '{$image_type}' is not available!\n");
$issue_gd_warning = true;
}
}
} else {
self::write(" warn Extension 'gd' is not loaded.\n");
$issue_gd_warning = true;
}
if ($issue_gd_warning) {
self::write(
"[WARN] The 'gd' library is missing or lacks full support. ".
"Phabricator will not be able to generate image thumbnails without ".
"gd.\n");
} else {
self::write("[OKAY] 'gd' loaded and has full image type support.\n");
}
self::writeHeader('FACEBOOK INTEGRATION');
$fb_auth = PhabricatorEnv::getEnvConfig('facebook.auth-enabled');
if (!$fb_auth) {
self::write(" skip 'facebook.auth-enabled' not enabled.\n");
} else {
self::write(" okay 'facebook.auth-enabled' is enabled.\n");
$app_id = PhabricatorEnv::getEnvConfig('facebook.application-id');
$app_secret = PhabricatorEnv::getEnvConfig('facebook.application-secret');
if (!$app_id) {
self::writeFailure();
self::write(
"Setup failure! 'facebook.auth-enabled' is true but there is no ".
"setting for 'facebook.application-id'.\n");
return;
} else {
self::write(" okay 'facebook.application-id' is set.\n");
}
if (!is_string($app_id)) {
self::writeFailure();
self::write(
"Setup failure! 'facebook.application-id' should be a string.");
return;
} else {
self::write(" okay 'facebook.application-id' is string.\n");
}
if (!$app_secret) {
self::writeFailure();
self::write(
"Setup failure! 'facebook.auth-enabled' is true but there is no ".
"setting for 'facebook.application-secret'.");
return;
} else {
self::write(" okay 'facebook.application-secret is set.\n");
}
self::write("[OKAY] Facebook integration OKAY\n");
}
self::writeHeader("MySQL DATABASE & STORAGE CONFIGURATION");
$conf = DatabaseConfigurationProvider::getConfiguration();
$conn_user = $conf->getUser();
$conn_pass = $conf->getPassword();
$conn_host = $conf->getHost();
$timeout = ini_get('mysql.connect_timeout');
if ($timeout > 5) {
self::writeNote(
"Your MySQL connect timeout is very high ({$timeout} seconds). ".
"Consider reducing it by setting 'mysql.connect_timeout' in your ".
"php.ini.");
}
self::write(" okay Trying to connect to MySQL database ".
"{$conn_user}@{$conn_host}...\n");
ini_set('mysql.connect_timeout', 2);
$conn_raw = new AphrontMySQLDatabaseConnection(
array(
'user' => $conn_user,
'pass' => $conn_pass,
'host' => $conn_host,
'database' => null,
));
try {
queryfx($conn_raw, 'SELECT 1');
self::write(" okay Connection successful!\n");
} catch (AphrontQueryConnectionException $ex) {
$message = $ex->getMessage();
self::writeFailure();
self::write(
"Setup failure! MySQL exception: {$message} \n".
"Edit Phabricator configuration keys 'mysql.user', ".
"'mysql.host' and 'mysql.pass' to enable Phabricator ".
"to connect.");
return;
}
$databases = queryfx_all($conn_raw, 'SHOW DATABASES');
$databases = ipull($databases, 'Database');
$databases = array_fill_keys($databases, true);
if (empty($databases['phabricator_meta_data'])) {
self::writeFailure();
self::write(
"Setup failure! You haven't loaded the 'initialize.sql' file into ".
"MySQL. This file initializes necessary databases. See this guide for ".
"instructions:\n");
self::writeDoc('article/Configuration_Guide.html');
return;
} else {
self::write(" okay Databases have been initialized.\n");
}
$schema_version = queryfx_one(
$conn_raw,
'SELECT version FROM phabricator_meta_data.schema_version');
$schema_version = idx($schema_version, 'version', 'null');
$expect = PhabricatorSQLPatchList::getExpectedSchemaVersion();
if ($schema_version != $expect) {
self::writeFailure();
self::write(
"Setup failure! You haven't upgraded your database schema to the ".
"latest version. Expected version is '{$expect}', but your local ".
"version is '{$schema_version}'. See this guide for instructions:\n");
self::writeDoc('article/Upgrading_Schema.html');
return;
} else {
self::write(" okay Database schema are up to date (v{$expect}).\n");
}
$index_min_length = queryfx_one(
$conn_raw,
'SHOW VARIABLES LIKE %s',
'ft_min_word_len');
$index_min_length = idx($index_min_length, 'Value', 4);
if ($index_min_length >= 4) {
self::writeNote(
"MySQL is configured with a 'ft_min_word_len' of 4 or greater, which ".
"means you will not be able to search for 3-letter terms. Consider ".
"setting this in your configuration:\n".
"\n".
" [mysqld]\n".
" ft_min_word_len=3\n".
"\n".
"Then optionally run:\n".
"\n".
" REPAIR TABLE phabricator_search.search_documentfield QUICK;\n".
"\n".
"...to reindex existing documents.");
}
$max_allowed_packet = queryfx_one(
$conn_raw,
'SHOW VARIABLES LIKE %s',
'max_allowed_packet');
$max_allowed_packet = idx($max_allowed_packet, 'Value', PHP_INT_MAX);
$recommended_minimum = 1024 * 1024;
if ($max_allowed_packet < $recommended_minimum) {
self::writeNote(
"MySQL is configured with a small 'max_allowed_packet' ".
"('{$max_allowed_packet}'), which may cause some large writes to ".
"fail. Consider raising this to at least {$recommended_minimum}.");
} else {
self::write(" okay max_allowed_packet = {$max_allowed_packet}.\n");
}
$mysql_key = 'storage.mysql-engine.max-size';
$mysql_limit = PhabricatorEnv::getEnvConfig($mysql_key);
if ($mysql_limit && ($mysql_limit + 8192) > $max_allowed_packet) {
self::writeFailure();
self::write(
"Setup failure! Your Phabricator 'storage.mysql-engine.max-size' ".
"configuration ('{$mysql_limit}') must be at least 8KB smaller ".
"than your MySQL 'max_allowed_packet' configuration ".
"('{$max_allowed_packet}'). Raise the 'max_allowed_packet' in your ".
"MySQL configuration, or reduce the maximum file size allowed by ".
"the Phabricator configuration.\n");
return;
} else if (!$mysql_limit) {
self::write(" skip MySQL file storage engine not configured.\n");
} else {
self::write(" okay MySQL file storage engine configuration okay.\n");
}
$local_key = 'storage.local-disk.path';
$local_path = PhabricatorEnv::getEnvConfig($local_key);
if ($local_path) {
if (!Filesystem::pathExists($local_path) ||
!is_readable($local_path) ||
!is_writable($local_path)) {
self::writeFailure();
self::write(
"Setup failure! You have configured local disk storage but the ".
"path you specified ('{$local_path}') does not exist or is not ".
"readable or writable.\n");
if ($open_basedir) {
self::write(
"You have an 'open_basedir' setting -- make sure Phabricator is ".
"allowed to open files in the local storage directory.\n");
}
return;
} else {
self::write(" okay Local disk storage exists and is writable.\n");
}
} else {
self::write(" skip Not configured for local disk storage.\n");
}
$selector = PhabricatorEnv::getEnvConfig('storage.engine-selector');
try {
$storage_selector_exists = class_exists($selector);
} catch (Exception $ex) {
$storage_selector_exists = false;
}
if ($storage_selector_exists) {
self::write(" okay Using '{$selector}' as a storage engine selector.\n");
} else {
self::writeFailure();
self::write(
"Setup failure! You have configured '{$selector}' as a storage engine ".
"selector but it does not exist or could not be loaded.\n");
return;
}
self::write("[OKAY] Database and storage configuration OKAY\n");
self::writeHeader("OUTBOUND EMAIL CONFIGURATION");
$have_adapter = false;
$is_ses = false;
$adapter = PhabricatorEnv::getEnvConfig('metamta.mail-adapter');
switch ($adapter) {
case 'PhabricatorMailImplementationPHPMailerLiteAdapter':
$have_adapter = true;
if (!Filesystem::pathExists('/usr/bin/sendmail') &&
!Filesystem::pathExists('/usr/sbin/sendmail')) {
self::writeFailure();
self::write(
"Setup failure! You don't have a 'sendmail' binary on this system ".
"but outbound email is configured to use sendmail. Install an MTA ".
"(like sendmail, qmail or postfix) or use a different outbound ".
"mail configuration. See this guide for configuring outbound ".
"email:\n");
self::writeDoc('article/Configuring_Outbound_Email.html');
return;
} else {
self::write(" okay Sendmail is configured.\n");
}
break;
case 'PhabricatorMailImplementationAmazonSESAdapter':
$is_ses = true;
$have_adapter = true;
if (PhabricatorEnv::getEnvConfig('metamta.can-send-as-user')) {
self::writeFailure();
self::write(
"Setup failure! 'metamta.can-send-as-user' must be false when ".
"configured with Amazon SES.");
return;
} else {
self::write(" okay Sender config looks okay.\n");
}
if (!PhabricatorEnv::getEnvConfig('amazon-ses.access-key')) {
self::writeFailure();
self::write(
"Setup failure! 'amazon-ses.access-key' is not set, but ".
"outbound mail is configured to deliver via Amazon SES.");
return;
} else {
self::write(" okay Amazon SES access key is set.\n");
}
if (!PhabricatorEnv::getEnvConfig('amazon-ses.secret-key')) {
self::writeFailure();
self::write(
"Setup failure! 'amazon-ses.secret-key' is not set, but ".
"outbound mail is configured to deliver via Amazon SES.");
return;
} else {
self::write(" okay Amazon SES secret key is set.\n");
}
if (PhabricatorEnv::getEnvConfig('metamta.send-immediately')) {
self::writeNote(
"Your configuration uses Amazon SES to deliver email but tries ".
"to send it immediately. This will work, but it's slow. ".
"Consider configuring the MetaMTA daemon.");
}
break;
case 'PhabricatorMailImplementationTestAdapter':
self::write(" skip You have disabled outbound email.\n");
break;
default:
self::write(" skip Configured with a custom adapter.\n");
break;
}
if ($have_adapter) {
$default = PhabricatorEnv::getEnvConfig('metamta.default-address');
if (!$default || $default == 'noreply@example.com') {
self::writeFailure();
self::write(
"Setup failure! You have not set 'metamta.default-address'.");
return;
} else {
self::write(" okay metamta.default-address is set.\n");
}
if ($is_ses) {
self::writeNote(
"Make sure you've verified your 'from' address ('{$default}') with ".
"Amazon SES. Until you verify it, you will be unable to send mail ".
"using Amazon SES.");
}
$domain = PhabricatorEnv::getEnvConfig('metamta.domain');
if (!$domain || $domain == 'example.com') {
self::writeFailure();
self::write(
"Setup failure! You have not set 'metamta.domain'.");
return;
} else {
self::write(" okay metamta.domain is set.\n");
}
self::write("[OKAY] Mail configuration OKAY\n");
}
self::writeHeader('SUCCESS!');
self::write(
"Congratulations! Your setup seems mostly correct, or at least fairly ".
"reasonable.\n\n".
"*** NEXT STEP ***\n".
"Edit your configuration file (conf/{$env}.conf.php) and remove the ".
"'phabricator.setup' line to finish installation.");
}
public static function requireExtension($extension) {
if (extension_loaded($extension)) {
self::write(" okay Extension '{$extension}' installed.\n");
return true;
} else {
self::write("[FAIL] Extension '{$extension}' is NOT INSTALLED!\n");
return false;
}
}
private static function writeFailure() {
self::write("\n\n<<< *** FAILURE! *** >>>\n");
}
private static function write($str) {
echo $str;
ob_flush();
flush();
// This, uh, makes it look cool. -_-
usleep(20000);
}
private static function writeNote($note) {
$note = "*** NOTE: ".wordwrap($note, 75, "\n", true);
$note = "\n".str_replace("\n", "\n ", $note)."\n\n";
self::write($note);
}
public static function writeHeader($header) {
$template = '>>>'.str_repeat('-', 77);
$template = substr_replace(
$template,
' '.$header.' ',
3,
strlen($header) + 4);
self::write("\n\n{$template}\n\n");
}
public static function writeDoc($doc) {
self::write(
"\n".
' http://phabricator.com/docs/phabricator/'.$doc.
"\n\n");
}
}
diff --git a/src/infrastructure/testing/testcase/__tests__/PhabricatorTrivialTestCase.php b/src/infrastructure/testing/testcase/__tests__/PhabricatorTrivialTestCase.php
index b8943907db..20b097d99e 100644
--- a/src/infrastructure/testing/testcase/__tests__/PhabricatorTrivialTestCase.php
+++ b/src/infrastructure/testing/testcase/__tests__/PhabricatorTrivialTestCase.php
@@ -1,38 +1,38 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Trivial example test case.
*/
-class PhabricatorTrivialTestCase extends PhabricatorTestCase {
+final class PhabricatorTrivialTestCase extends PhabricatorTestCase {
// NOTE: Update developer/unit_tests.diviner when updating this class!
private $two;
public function willRunOneTest($test_name) {
// You can execute setup steps which will run before each test in this
// method.
$this->two = 2;
}
public function testAllIsRightWithTheWorld() {
$this->assertEqual(4, $this->two + $this->two, '2 + 2 = 4');
}
}
diff --git a/src/storage/connection/isolated/AphrontIsolatedDatabaseConnection.php b/src/storage/connection/isolated/AphrontIsolatedDatabaseConnection.php
index 60905247c1..11b0f8372f 100644
--- a/src/storage/connection/isolated/AphrontIsolatedDatabaseConnection.php
+++ b/src/storage/connection/isolated/AphrontIsolatedDatabaseConnection.php
@@ -1,130 +1,131 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group storage
*/
-class AphrontIsolatedDatabaseConnection extends AphrontDatabaseConnection {
+final class AphrontIsolatedDatabaseConnection
+ extends AphrontDatabaseConnection {
private $configuration;
private static $nextInsertID;
private $insertID;
private static $nextTransactionKey = 1;
private $transactionKey;
private $transcript = array();
public function __construct(array $configuration) {
$this->configuration = $configuration;
if (self::$nextInsertID === null) {
// Generate test IDs into a distant ID space to reduce the risk of
// collisions and make them distinctive.
self::$nextInsertID = 55555000000 + mt_rand(0, 1000);
}
$this->transactionKey = 'iso-xaction-'.(self::$nextTransactionKey++);
}
public function escapeString($string) {
return '<S>';
}
public function escapeColumnName($name) {
return '<C>';
}
public function escapeMultilineComment($comment) {
return '<K>';
}
public function escapeStringForLikeClause($value) {
return '<L>';
}
private function getConfiguration($key, $default = null) {
return idx($this->configuration, $key, $default);
}
public function getInsertID() {
return $this->insertID;
}
public function getAffectedRows() {
return $this->affectedRows;
}
public function getTransactionKey() {
return $this->transactionKey;
}
public function selectAllResults() {
return $this->allResults;
}
public function executeRawQuery($raw_query) {
// NOTE: "[\s<>K]*" allows any number of (properly escaped) comments to
// appear prior to the allowed keyword, since this connection escapes
// them as "<K>" (above).
$keywords = array(
'INSERT',
'UPDATE',
'DELETE',
'START',
'SAVEPOINT',
'COMMIT',
'ROLLBACK',
);
$preg_keywords = array();
foreach ($keywords as $key => $word) {
$preg_keywords[] = preg_quote($word, '/');
}
$preg_keywords = implode('|', $preg_keywords);
if (!preg_match('/^[\s<>K]*('.$preg_keywords.')\s*/i', $raw_query)) {
$doc_uri = PhabricatorEnv::getDoclink('article/Writing_Unit_Tests.html');
throw new Exception(
"Database isolation currently only supports some queries. For more ".
"information, see <{$doc_uri}>. You are trying to issue a query which ".
"does not begin with an allowed keyword ".
"(".implode(', ', $keywords)."): '".$raw_query."'");
}
$this->transcript[] = $raw_query;
// NOTE: This method is intentionally simplified for now, since we're only
// using it to stub out inserts/updates. In the future it will probably need
// to grow more powerful.
$this->allResults = array();
// NOTE: We jitter the insert IDs to keep tests honest; a test should cover
// the relationship between objects, not their exact insertion order. This
// guarantees that IDs are unique but makes it impossible to hard-code tests
// against this specific implementation detail.
$this->insertID = (self::$nextInsertID += mt_rand(1, 10));
$this->affectedRows = 1;
}
public function getQueryTranscript() {
return $this->transcript;
}
}
diff --git a/src/storage/connection/isolated/__tests__/AphrontIsolatedDatabaseConnectionTestCase.php b/src/storage/connection/isolated/__tests__/AphrontIsolatedDatabaseConnectionTestCase.php
index 59387abc93..eed20ebff5 100644
--- a/src/storage/connection/isolated/__tests__/AphrontIsolatedDatabaseConnectionTestCase.php
+++ b/src/storage/connection/isolated/__tests__/AphrontIsolatedDatabaseConnectionTestCase.php
@@ -1,164 +1,164 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class AphrontIsolatedDatabaseConnectionTestCase
+final class AphrontIsolatedDatabaseConnectionTestCase
extends PhabricatorTestCase {
protected function getPhabricatorTestCaseConfiguration() {
return array(
// We disable this here because this test is unique (it is testing that
// isolation actually occurs) and must establish a live connection to the
// database to verify that.
self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK => false,
);
}
public function testIsolation() {
$conn = $this->newIsolatedConnection();
$test_phid = $this->generateTestPHID();
queryfx(
$conn,
'INSERT INTO phabricator_phid.phid (phid) VALUES (%s)',
$test_phid);
$this->assertNoSuchPHID($test_phid);
}
public function testInsertGeneratesID() {
$conn = $this->newIsolatedConnection();
queryfx($conn, 'INSERT');
$id1 = $conn->getInsertID();
queryfx($conn, 'INSERT');
$id2 = $conn->getInsertID();
$this->assertEqual(true, (bool)$id1, 'ID1 exists.');
$this->assertEqual(true, (bool)$id2, 'ID2 exists.');
$this->assertEqual(
true,
$id1 != $id2,
"IDs '{$id1}' and '{$id2}' are distinct.");
}
public function testDeletePermitted() {
$conn = $this->newIsolatedConnection();
queryfx($conn, 'DELETE');
}
public function testTransactionStack() {
$conn = $this->newIsolatedConnection();
$conn->openTransaction();
queryfx($conn, 'INSERT');
$conn->saveTransaction();
$this->assertEqual(
array(
'START TRANSACTION',
'INSERT',
'COMMIT',
),
$conn->getQueryTranscript());
$conn = $this->newIsolatedConnection();
$conn->openTransaction();
queryfx($conn, 'INSERT 1');
$conn->openTransaction();
queryfx($conn, 'INSERT 2');
$conn->killTransaction();
$conn->openTransaction();
queryfx($conn, 'INSERT 3');
$conn->openTransaction();
queryfx($conn, 'INSERT 4');
$conn->saveTransaction();
$conn->saveTransaction();
$conn->openTransaction();
queryfx($conn, 'INSERT 5');
$conn->killTransaction();
queryfx($conn, 'INSERT 6');
$conn->saveTransaction();
$this->assertEqual(
array(
'START TRANSACTION',
'INSERT 1',
'SAVEPOINT Aphront_Savepoint_1',
'INSERT 2',
'ROLLBACK TO SAVEPOINT Aphront_Savepoint_1',
'SAVEPOINT Aphront_Savepoint_1',
'INSERT 3',
'SAVEPOINT Aphront_Savepoint_2',
'INSERT 4',
'SAVEPOINT Aphront_Savepoint_1',
'INSERT 5',
'ROLLBACK TO SAVEPOINT Aphront_Savepoint_1',
'INSERT 6',
'COMMIT',
),
$conn->getQueryTranscript());
}
public function testTransactionRollback() {
$check = array();
$phid = new PhabricatorPHID();
$phid->openTransaction();
for ($ii = 0; $ii < 3; $ii++) {
$test_phid = $this->generateTestPHID();
$obj = new PhabricatorPHID();
$obj->setPHID($test_phid);
$obj->setPHIDType('TEST');
$obj->setOwnerPHID('PHID-UNIT-!!!!');
$obj->save();
$check[] = $test_phid;
}
$phid->killTransaction();
foreach ($check as $test_phid) {
$this->assertNoSuchPHID($test_phid);
}
}
private function newIsolatedConnection() {
$config = array();
return new AphrontIsolatedDatabaseConnection($config);
}
private function generateTestPHID() {
return 'PHID-TEST-'.Filesystem::readRandomCharacters(20);
}
private function assertNoSuchPHID($phid) {
try {
$real_phid = id(new PhabricatorPHID())->loadOneWhere(
'phid = %s',
$phid);
$this->assertEqual(
null,
$real_phid,
'Expect fake PHID to exist only in isolation.');
} catch (AphrontQueryConnectionException $ex) {
// If we can't connect to the database, conclude that the isolated
// connection actually is isolated. Philosophically, this perhaps allows
// us to claim this test does not depend on the database?
}
}
}
diff --git a/src/storage/connection/mysql/AphrontMySQLDatabaseConnection.php b/src/storage/connection/mysql/AphrontMySQLDatabaseConnection.php
index f65998a261..9468281a0b 100644
--- a/src/storage/connection/mysql/AphrontMySQLDatabaseConnection.php
+++ b/src/storage/connection/mysql/AphrontMySQLDatabaseConnection.php
@@ -1,335 +1,335 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group storage
*/
-class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
+final class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
private $config;
private $connection;
private $nextError;
private static $connectionCache = array();
public function __construct(array $configuration) {
$this->configuration = $configuration;
}
public function escapeString($string) {
$this->requireConnection();
return mysql_real_escape_string($string, $this->connection);
}
public function escapeColumnName($name) {
return '`'.str_replace('`', '\\`', $name).'`';
}
public function escapeMultilineComment($comment) {
// These can either terminate a comment, confuse the hell out of the parser,
// make MySQL execute the comment as a query, or, in the case of semicolon,
// are quasi-dangerous because the semicolon could turn a broken query into
// a working query plus an ignored query.
static $map = array(
'--' => '(DOUBLEDASH)',
'*/' => '(STARSLASH)',
'//' => '(SLASHSLASH)',
'#' => '(HASH)',
'!' => '(BANG)',
';' => '(SEMICOLON)',
);
$comment = str_replace(
array_keys($map),
array_values($map),
$comment);
// For good measure, kill anything else that isn't a nice printable
// character.
$comment = preg_replace('/[^\x20-\x7F]+/', ' ', $comment);
return '/* '.$comment.' */';
}
public function escapeStringForLikeClause($value) {
$value = $this->escapeString($value);
// Ideally the query shouldn't be modified after safely escaping it,
// but we need to escape _ and % within LIKE terms.
$value = str_replace(
// Even though we've already escaped, we need to replace \ with \\
// because MYSQL unescapes twice inside a LIKE clause. See note
// at mysql.com. However, if the \ is being used to escape a single
// quote ('), then the \ should not be escaped. Thus, after all \
// are replaced with \\, we need to revert instances of \\' back to
// \'.
array('\\', '\\\\\'', '_', '%'),
array('\\\\', '\\\'', '\_', '\%'),
$value);
return $value;
}
private function getConfiguration($key, $default = null) {
return idx($this->configuration, $key, $default);
}
private function closeConnection() {
if ($this->connection) {
$this->connection = null;
$key = $this->getConnectionCacheKey();
unset(self::$connectionCache[$key]);
}
}
private function getConnectionCacheKey() {
$user = $this->getConfiguration('user');
$host = $this->getConfiguration('host');
$database = $this->getConfiguration('database');
return "{$user}:{$host}:{$database}";
}
private function establishConnection() {
$this->closeConnection();
$key = $this->getConnectionCacheKey();
if (isset(self::$connectionCache[$key])) {
$this->connection = self::$connectionCache[$key];
return;
}
$start = microtime(true);
if (!function_exists('mysql_connect')) {
// We have to '@' the actual call since it can spew all sorts of silly
// noise, but it will also silence fatals caused by not having MySQL
// installed, which has bitten me on three separate occasions. Make sure
// such failures are explicit and loud.
throw new Exception(
"About to call mysql_connect(), but the PHP MySQL extension is not ".
"available!");
}
$user = $this->getConfiguration('user');
$host = $this->getConfiguration('host');
$database = $this->getConfiguration('database');
$profiler = PhutilServiceProfiler::getInstance();
$call_id = $profiler->beginServiceCall(
array(
'type' => 'connect',
'host' => $host,
'database' => $database,
));
$retries = max(1, PhabricatorEnv::getEnvConfig('mysql.connection-retries'));
while ($retries--) {
try {
$conn = @mysql_connect(
$host,
$user,
$this->getConfiguration('pass'),
$new_link = true,
$flags = 0);
if (!$conn) {
$errno = mysql_errno();
$error = mysql_error();
throw new AphrontQueryConnectionException(
"Attempt to connect to {$user}@{$host} failed with error ".
"#{$errno}: {$error}.", $errno);
}
if ($database !== null) {
$ret = @mysql_select_db($database, $conn);
if (!$ret) {
$this->throwQueryException($conn);
}
mysql_set_charset('utf8');
}
$profiler->endServiceCall($call_id, array());
break;
} catch (Exception $ex) {
if ($retries && $ex->getCode() == 2003) {
$class = get_class($ex);
$message = $ex->getMessage();
phlog("Retrying ({$retries}) after {$class}: {$message}");
} else {
$profiler->endServiceCall($call_id, array());
throw $ex;
}
}
}
self::$connectionCache[$key] = $conn;
$this->connection = $conn;
}
public function getInsertID() {
return mysql_insert_id($this->requireConnection());
}
public function getAffectedRows() {
return mysql_affected_rows($this->requireConnection());
}
public function getTransactionKey() {
return (int)$this->requireConnection();
}
private function requireConnection() {
if (!$this->connection) {
$this->establishConnection();
}
return $this->connection;
}
public function selectAllResults() {
$result = array();
$res = $this->lastResult;
if ($res == null) {
throw new Exception('No query result to fetch from!');
}
while (($row = mysql_fetch_assoc($res)) !== false) {
$result[] = $row;
}
return $result;
}
public function executeRawQuery($raw_query) {
$this->lastResult = null;
$retries = max(1, PhabricatorEnv::getEnvConfig('mysql.connection-retries'));
while ($retries--) {
try {
$this->requireConnection();
// TODO: Do we need to include transactional statements here?
$is_write = !preg_match('/^(SELECT|SHOW|EXPLAIN)\s/', $raw_query);
if ($is_write) {
AphrontWriteGuard::willWrite();
}
$start = microtime(true);
$profiler = PhutilServiceProfiler::getInstance();
$call_id = $profiler->beginServiceCall(
array(
'type' => 'query',
'config' => $this->configuration,
'query' => $raw_query,
'write' => $is_write,
));
$result = @mysql_query($raw_query, $this->connection);
$profiler->endServiceCall($call_id, array());
if ($this->nextError) {
$result = null;
}
if ($result) {
$this->lastResult = $result;
break;
}
$this->throwQueryException($this->connection);
} catch (AphrontQueryConnectionLostException $ex) {
if ($this->isInsideTransaction()) {
// Zero out the transaction state to prevent a second exception
// ("program exited with open transaction") from being thrown, since
// we're about to throw a more relevant/useful one instead.
$state = $this->getTransactionState();
while ($state->getDepth()) {
$state->decreaseDepth();
}
// We can't close the connection before this because
// isInsideTransaction() and getTransactionState() depend on the
// connection.
$this->closeConnection();
throw $ex;
}
$this->closeConnection();
if (!$retries) {
throw $ex;
}
$class = get_class($ex);
$message = $ex->getMessage();
phlog("Retrying ({$retries}) after {$class}: {$message}");
}
}
}
private function throwQueryException($connection) {
if ($this->nextError) {
$errno = $this->nextError;
$error = 'Simulated error.';
$this->nextError = null;
} else {
$errno = mysql_errno($connection);
$error = mysql_error($connection);
}
$exmsg = "#{$errno}: {$error}";
switch ($errno) {
case 2013: // Connection Dropped
case 2006: // Gone Away
throw new AphrontQueryConnectionLostException($exmsg);
case 1213: // Deadlock
case 1205: // Lock wait timeout exceeded
throw new AphrontQueryRecoverableException($exmsg);
case 1062: // Duplicate Key
// NOTE: In some versions of MySQL we get a key name back here, but
// older versions just give us a key index ("key 2") so it's not
// portable to parse the key out of the error and attach it to the
// exception.
throw new AphrontQueryDuplicateKeyException($exmsg);
case 1044: // Access denied to database
case 1045: // Access denied (auth)
case 1142: // Access denied to table
case 1143: // Access denied to column
throw new AphrontQueryAccessDeniedException($exmsg);
case 1146: // No such table
case 1154: // Unknown column "..." in field list
throw new AphrontQuerySchemaException($exmsg);
default:
// TODO: 1064 is syntax error, and quite terrible in production.
throw new AphrontQueryException($exmsg);
}
}
/**
* Force the next query to fail with a simulated error. This should be used
* ONLY for unit tests.
*/
public function simulateErrorOnNextQuery($error) {
$this->nextError = $error;
return $this;
}
}
diff --git a/src/storage/exception/accessdenied/AphrontQueryAccessDeniedException.php b/src/storage/exception/accessdenied/AphrontQueryAccessDeniedException.php
index 0ce8cf7d38..1c2793d6da 100644
--- a/src/storage/exception/accessdenied/AphrontQueryAccessDeniedException.php
+++ b/src/storage/exception/accessdenied/AphrontQueryAccessDeniedException.php
@@ -1,23 +1,23 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group storage
*/
-class AphrontQueryAccessDeniedException
+final class AphrontQueryAccessDeniedException
extends AphrontQueryRecoverableException { }
diff --git a/src/storage/exception/base/AphrontQueryException.php b/src/storage/exception/base/AphrontQueryException.php
index 4f6db2ba92..fe246a6fc9 100644
--- a/src/storage/exception/base/AphrontQueryException.php
+++ b/src/storage/exception/base/AphrontQueryException.php
@@ -1,22 +1,23 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group storage
+ * @concrete-extensible
*/
class AphrontQueryException extends Exception { }
diff --git a/src/storage/exception/connection/AphrontQueryConnectionException.php b/src/storage/exception/connection/AphrontQueryConnectionException.php
index 2d4a4e35d8..7755f9b598 100644
--- a/src/storage/exception/connection/AphrontQueryConnectionException.php
+++ b/src/storage/exception/connection/AphrontQueryConnectionException.php
@@ -1,22 +1,22 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group storage
*/
-class AphrontQueryConnectionException extends AphrontQueryException { }
+final class AphrontQueryConnectionException extends AphrontQueryException { }
diff --git a/src/storage/exception/connectionlost/AphrontQueryConnectionLostException.php b/src/storage/exception/connectionlost/AphrontQueryConnectionLostException.php
index 3a73b41fd4..bfd793f906 100644
--- a/src/storage/exception/connectionlost/AphrontQueryConnectionLostException.php
+++ b/src/storage/exception/connectionlost/AphrontQueryConnectionLostException.php
@@ -1,23 +1,23 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group storage
*/
-class AphrontQueryConnectionLostException
+final class AphrontQueryConnectionLostException
extends AphrontQueryRecoverableException { }
diff --git a/src/storage/exception/count/AphrontQueryCountException.php b/src/storage/exception/count/AphrontQueryCountException.php
index 85712d4bb4..69dd59d93d 100644
--- a/src/storage/exception/count/AphrontQueryCountException.php
+++ b/src/storage/exception/count/AphrontQueryCountException.php
@@ -1,22 +1,22 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group storage
*/
-class AphrontQueryCountException extends AphrontQueryException { }
+final class AphrontQueryCountException extends AphrontQueryException { }
diff --git a/src/storage/exception/duplicatekey/AphrontQueryDuplicateKeyException.php b/src/storage/exception/duplicatekey/AphrontQueryDuplicateKeyException.php
index 496fec6fab..41ec08566b 100644
--- a/src/storage/exception/duplicatekey/AphrontQueryDuplicateKeyException.php
+++ b/src/storage/exception/duplicatekey/AphrontQueryDuplicateKeyException.php
@@ -1,24 +1,24 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group storage
*/
-class AphrontQueryDuplicateKeyException extends AphrontQueryException {
+final class AphrontQueryDuplicateKeyException extends AphrontQueryException {
}
diff --git a/src/storage/exception/objectmissing/AphrontQueryObjectMissingException.php b/src/storage/exception/objectmissing/AphrontQueryObjectMissingException.php
index 7459b431f6..c07878f5a1 100644
--- a/src/storage/exception/objectmissing/AphrontQueryObjectMissingException.php
+++ b/src/storage/exception/objectmissing/AphrontQueryObjectMissingException.php
@@ -1,22 +1,22 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group storage
*/
-class AphrontQueryObjectMissingException extends AphrontQueryException { }
+final class AphrontQueryObjectMissingException extends AphrontQueryException { }
diff --git a/src/storage/exception/parameter/AphrontQueryParameterException.php b/src/storage/exception/parameter/AphrontQueryParameterException.php
index af0ef5340f..45ccc87c55 100644
--- a/src/storage/exception/parameter/AphrontQueryParameterException.php
+++ b/src/storage/exception/parameter/AphrontQueryParameterException.php
@@ -1,35 +1,35 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group storage
*/
-class AphrontQueryParameterException extends AphrontQueryException {
+final class AphrontQueryParameterException extends AphrontQueryException {
private $query;
public function __construct($query, $message) {
parent::__construct($message." Query: ".$query);
$this->query = $query;
}
public function getQuery() {
return $this->query;
}
}
diff --git a/src/storage/exception/recoverable/AphrontQueryRecoverableException.php b/src/storage/exception/recoverable/AphrontQueryRecoverableException.php
index e1fe588ff5..96c544cb53 100644
--- a/src/storage/exception/recoverable/AphrontQueryRecoverableException.php
+++ b/src/storage/exception/recoverable/AphrontQueryRecoverableException.php
@@ -1,22 +1,25 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group storage
*/
-class AphrontQueryRecoverableException extends AphrontQueryException { }
+abstract class AphrontQueryRecoverableException
+ extends AphrontQueryException {
+
+}
diff --git a/src/storage/lisk/dao/__tests__/LiskIsolationTestCase.php b/src/storage/lisk/dao/__tests__/LiskIsolationTestCase.php
index 5c8081075b..12ac2d68ff 100644
--- a/src/storage/lisk/dao/__tests__/LiskIsolationTestCase.php
+++ b/src/storage/lisk/dao/__tests__/LiskIsolationTestCase.php
@@ -1,128 +1,128 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class LiskIsolationTestCase extends PhabricatorTestCase {
+final class LiskIsolationTestCase extends PhabricatorTestCase {
public function testIsolatedWrites() {
$dao = new LiskIsolationTestDAO();
$this->assertEqual(null, $dao->getID(), 'Expect no ID.');
$this->assertEqual(null, $dao->getPHID(), 'Expect no PHID.');
$dao->save(); // Effects insert
$id = $dao->getID();
$phid = $dao->getPHID();
$this->assertEqual(true, (bool)$id, 'Expect ID generated.');
$this->assertEqual(true, (bool)$phid, 'Expect PHID generated.');
$dao->save(); // Effects update
$this->assertEqual($id, $dao->getID(), 'Expect ID unchanged.');
$this->assertEqual($phid, $dao->getPHID(), 'Expect PHID unchanged.');
}
public function testEphemeral() {
$dao = new LiskIsolationTestDAO();
$dao->save();
$dao->makeEphemeral();
$this->tryTestCases(
array(
$dao,
),
array(
false,
),
array($this, 'saveDAO'));
}
public function saveDAO($dao) {
$dao->save();
}
public function testIsolationContainment() {
$dao = new LiskIsolationTestDAO();
try {
$dao->establishLiveConnection('r');
$this->assertFailure(
"LiskIsolationTestDAO did not throw an exception when instructed to ".
"explicitly connect to an external database.");
} catch (LiskIsolationTestDAOException $ex) {
// Expected, pass.
}
}
public function testMagicMethods() {
$dao = new LiskIsolationTestDAO();
$this->assertEqual(
null,
$dao->getName(),
'getName() on empty object');
$this->assertEqual(
$dao,
$dao->setName('x'),
'setName() returns $this');
$this->assertEqual(
'y',
$dao->setName('y')->getName(),
'setName() has an effect');
$ex = null;
try {
$dao->gxxName();
} catch (Exception $thrown) {
$ex = $thrown;
}
$this->assertEqual(
true,
(bool)$ex,
'Typoing "get" should throw.');
$ex = null;
try {
$dao->sxxName('z');
} catch (Exception $thrown) {
$ex = $thrown;
}
$this->assertEqual(
true,
(bool)$ex,
'Typoing "set" should throw.');
$ex = null;
try {
$dao->madeUpMethod();
} catch (Exception $thrown) {
$ex = $thrown;
}
$this->assertEqual(
true,
(bool)$ex,
'Made up method should throw.');
}
}
diff --git a/src/storage/lisk/dao/__tests__/LiskIsolationTestDAO.php b/src/storage/lisk/dao/__tests__/LiskIsolationTestDAO.php
index bd0a3aeea6..922ff57b62 100644
--- a/src/storage/lisk/dao/__tests__/LiskIsolationTestDAO.php
+++ b/src/storage/lisk/dao/__tests__/LiskIsolationTestDAO.php
@@ -1,44 +1,44 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class LiskIsolationTestDAO extends LiskDAO {
+final class LiskIsolationTestDAO extends LiskDAO {
protected $name;
protected $phid;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID('TISO');
}
public function establishLiveConnection($mode) {
throw new LiskIsolationTestDAOException(
"Isolation failure! DAO is attempting to connect to an external ".
"resource!");
}
public function getTableName() {
return 'test';
}
}
diff --git a/src/storage/lisk/dao/__tests__/LiskIsolationTestDAOException.php b/src/storage/lisk/dao/__tests__/LiskIsolationTestDAOException.php
index 3a68b686de..a0161d7c6a 100644
--- a/src/storage/lisk/dao/__tests__/LiskIsolationTestDAOException.php
+++ b/src/storage/lisk/dao/__tests__/LiskIsolationTestDAOException.php
@@ -1,19 +1,19 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class LiskIsolationTestDAOException extends Exception { }
+final class LiskIsolationTestDAOException extends Exception { }
diff --git a/src/view/control/objectselector/PhabricatorObjectSelectorDialog.php b/src/view/control/objectselector/PhabricatorObjectSelectorDialog.php
index 918c352f0a..ffc29369be 100644
--- a/src/view/control/objectselector/PhabricatorObjectSelectorDialog.php
+++ b/src/view/control/objectselector/PhabricatorObjectSelectorDialog.php
@@ -1,199 +1,199 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorObjectSelectorDialog {
+final class PhabricatorObjectSelectorDialog {
private $user;
private $filters = array();
private $handles = array();
private $cancelURI;
private $submitURI;
private $searchURI;
private $selectedFilter;
private $title;
private $header;
private $buttonText;
private $instructions;
public function setUser($user) {
$this->user = $user;
return $this;
}
public function setFilters(array $filters) {
$this->filters = $filters;
return $this;
}
public function setSelectedFilter($selected_filter) {
$this->selectedFilter = $selected_filter;
return $this;
}
public function setHandles(array $handles) {
$this->handles = $handles;
return $this;
}
public function setCancelURI($cancel_uri) {
$this->cancelURI = $cancel_uri;
return $this;
}
public function setSubmitURI($submit_uri) {
$this->submitURI = $submit_uri;
return $this;
}
public function setSearchURI($search_uri) {
$this->searchURI = $search_uri;
return $this;
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function setHeader($header) {
$this->header = $header;
return $this;
}
public function setButtonText($button_text) {
$this->buttonText = $button_text;
return $this;
}
public function setInstructions($instructions) {
$this->instructions = $instructions;
return $this;
}
public function buildDialog() {
$user = $this->user;
$filter_id = celerity_generate_unique_node_id();
$query_id = celerity_generate_unique_node_id();
$results_id = celerity_generate_unique_node_id();
$current_id = celerity_generate_unique_node_id();
$search_id = celerity_generate_unique_node_id();
$form_id = celerity_generate_unique_node_id();
require_celerity_resource('phabricator-object-selector-css');
$options = array();
foreach ($this->filters as $key => $label) {
$options[] = phutil_render_tag(
'option',
array(
'value' => $key,
'selected' => ($key == $this->selectedFilter)
? 'selected'
: null,
),
$label);
}
$options = implode("\n", $options);
$instructions = null;
if ($this->instructions) {
$instructions =
'<p class="phabricator-object-selector-instructions">'.
$this->instructions.
'</p>';
}
$search_box = phabricator_render_form(
$user,
array(
'method' => 'POST',
'action' => $this->submitURI,
'id' => $search_id,
),
'<table class="phabricator-object-selector-search">
<tr>
<td class="phabricator-object-selector-search-filter">
<select id="'.$filter_id.'">'.
$options.
'</select>
</td>
<td class="phabricator-object-selector-search-text">
<input type="text" id="'.$query_id.'" />
</td>
</tr>
</table>');
$result_box =
'<div class="phabricator-object-selector-results" id="'.$results_id.'">'.
'</div>';
$attached_box =
'<div class="phabricator-object-selector-current">'.
'<div class="phabricator-object-selector-currently-attached">'.
'<div class="phabricator-object-selector-header">'.
phutil_escape_html($this->header).
'</div>'.
'<div id="'.$current_id.'">'.
'</div>'.
$instructions.
'</div>'.
'</div>';
$dialog = new AphrontDialogView();
$dialog
->setUser($this->user)
->setTitle($this->title)
->setClass('phabricator-object-selector-dialog')
->appendChild($search_box)
->appendChild($result_box)
->appendChild($attached_box)
->setRenderDialogAsDiv()
->setFormID($form_id)
->addSubmitButton($this->buttonText);
if ($this->cancelURI) {
$dialog->addCancelButton($this->cancelURI);
}
$handle_views = array();
foreach ($this->handles as $handle) {
$phid = $handle->getPHID();
$view = new PhabricatorHandleObjectSelectorDataView($handle);
$handle_views[$phid] = $view->renderData();
}
$dialog->addHiddenInput('phids', implode(';', array_keys($this->handles)));
Javelin::initBehavior(
'phabricator-object-selector',
array(
'filter' => $filter_id,
'query' => $query_id,
'search' => $search_id,
'results' => $results_id,
'current' => $current_id,
'form' => $form_id,
'uri' => $this->searchURI,
'handles' => $handle_views,
));
return $dialog;
}
}
diff --git a/src/view/control/table/AphrontTableView.php b/src/view/control/table/AphrontTableView.php
index 8e4de285c5..3f70c295fe 100644
--- a/src/view/control/table/AphrontTableView.php
+++ b/src/view/control/table/AphrontTableView.php
@@ -1,182 +1,182 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class AphrontTableView extends AphrontView {
+final class AphrontTableView extends AphrontView {
protected $data;
protected $headers;
protected $rowClasses = array();
protected $columnClasses = array();
protected $zebraStripes = true;
protected $noDataString;
protected $className;
protected $columnVisibility = array();
public function __construct(array $data) {
$this->data = $data;
}
public function setHeaders(array $headers) {
$this->headers = $headers;
return $this;
}
public function setColumnClasses(array $column_classes) {
$this->columnClasses = $column_classes;
return $this;
}
public function setRowClasses(array $row_classes) {
$this->rowClasses = $row_classes;
return $this;
}
public function setNoDataString($no_data_string) {
$this->noDataString = $no_data_string;
return $this;
}
public function setClassName($class_name) {
$this->className = $class_name;
return $this;
}
public function setZebraStripes($zebra_stripes) {
$this->zebraStripes = $zebra_stripes;
return $this;
}
public function setColumnVisibility(array $visibility) {
$this->columnVisibility = $visibility;
return $this;
}
public function render() {
require_celerity_resource('aphront-table-view-css');
$class = $this->className;
if ($class !== null) {
$class = ' class="aphront-table-view '.$class.'"';
} else {
$class = ' class="aphront-table-view"';
}
$table = array('<table'.$class.'>');
$col_classes = array();
foreach ($this->columnClasses as $key => $class) {
if (strlen($class)) {
$col_classes[] = ' class="'.$class.'"';
} else {
$col_classes[] = null;
}
}
$visibility = array_values($this->columnVisibility);
$headers = $this->headers;
if ($headers) {
while (count($headers) > count($visibility)) {
$visibility[] = true;
}
$table[] = '<tr>';
foreach ($headers as $col_num => $header) {
if (!$visibility[$col_num]) {
continue;
}
$class = idx($col_classes, $col_num);
$table[] = '<th'.$class.'>'.$header.'</th>';
}
$table[] = '</tr>';
}
$data = $this->data;
if ($data) {
$row_num = 0;
foreach ($data as $row) {
while (count($row) > count($col_classes)) {
$col_classes[] = null;
}
while (count($row) > count($visibility)) {
$visibility[] = true;
}
$class = idx($this->rowClasses, $row_num);
if ($this->zebraStripes && ($row_num % 2)) {
if ($class !== null) {
$class = 'alt alt-'.$class;
} else {
$class = 'alt';
}
}
if ($class !== null) {
$class = ' class="'.$class.'"';
}
$table[] = '<tr'.$class.'>';
// NOTE: Use of a separate column counter is to allow this to work
// correctly if the row data has string or non-sequential keys.
$col_num = 0;
foreach ($row as $value) {
if (!$visibility[$col_num]) {
++$col_num;
continue;
}
$class = $col_classes[$col_num];
if ($class !== null) {
$table[] = '<td'.$class.'>';
} else {
$table[] = '<td>';
}
$table[] = $value.'</td>';
++$col_num;
}
++$row_num;
}
} else {
$colspan = max(count($headers), 1);
$table[] =
'<tr class="no-data"><td colspan="'.$colspan.'">'.
coalesce($this->noDataString, 'No data available.').
'</td></tr>';
}
$table[] = '</table>';
return implode('', $table);
}
public static function renderSingleDisplayLine($line) {
// TODO: Is there a cleaner way to do this? We use a relative div with
// overflow hidden to provide the bounds, and an absolute span with
// white-space: pre to prevent wrapping. We need to append a character
// (&nbsp; -- nonbreaking space) afterward to give the bounds div height
// (alternatively, we could hard-code the line height). This is gross but
// it's not clear that there's a better appraoch.
return phutil_render_tag(
'div',
array(
'class' => 'single-display-line-bounds',
),
phutil_render_tag(
'span',
array(
'class' => 'single-display-line-content',
),
$line).'&nbsp;');
}
}
diff --git a/src/view/control/tokenizer/AphrontTokenizerTemplateView.php b/src/view/control/tokenizer/AphrontTokenizerTemplateView.php
index 49ec603754..65c0f225d7 100644
--- a/src/view/control/tokenizer/AphrontTokenizerTemplateView.php
+++ b/src/view/control/tokenizer/AphrontTokenizerTemplateView.php
@@ -1,104 +1,104 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class AphrontTokenizerTemplateView extends AphrontView {
+final class AphrontTokenizerTemplateView extends AphrontView {
private $value;
private $name;
private $id;
public function setID($id) {
$this->id = $id;
return $this;
}
public function setValue(array $value) {
$this->value = $value;
return $this;
}
public function getValue() {
return $this->value;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function render() {
require_celerity_resource('aphront-tokenizer-control-css');
$id = $this->id;
$name = $this->getName();
$values = nonempty($this->getValue(), array());
$tokens = array();
foreach ($values as $key => $value) {
$tokens[] = $this->renderToken($key, $value);
}
$input = javelin_render_tag(
'input',
array(
'mustcapture' => true,
'name' => $name,
'class' => 'jx-tokenizer-input',
'sigil' => 'tokenizer-input',
'style' => 'width: 0px;',
'disabled' => 'disabled',
'type' => 'text',
));
return phutil_render_tag(
'div',
array(
'id' => $id,
'class' => 'jx-tokenizer-container',
),
implode('', $tokens).
$input.
'<div style="clear: both;"></div>');
}
private function renderToken($key, $value) {
$input_name = $this->getName();
if ($input_name) {
$input_name .= '[]';
}
return phutil_render_tag(
'a',
array(
'class' => 'jx-tokenizer-token',
),
phutil_escape_html($value).
phutil_render_tag(
'input',
array(
'type' => 'hidden',
'name' => $input_name,
'value' => $key,
)).
'<span class="jx-tokenizer-x-placeholder"></span>');
}
}
diff --git a/src/view/control/typeahead/AphrontTypeaheadTemplateView.php b/src/view/control/typeahead/AphrontTypeaheadTemplateView.php
index a67b8acea1..1a141b0fa2 100644
--- a/src/view/control/typeahead/AphrontTypeaheadTemplateView.php
+++ b/src/view/control/typeahead/AphrontTypeaheadTemplateView.php
@@ -1,81 +1,81 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class AphrontTypeaheadTemplateView extends AphrontView {
+final class AphrontTypeaheadTemplateView extends AphrontView {
private $value;
private $name;
private $id;
public function setID($id) {
$this->id = $id;
return $this;
}
public function setValue(array $value) {
$this->value = $value;
return $this;
}
public function getValue() {
return $this->value;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function render() {
require_celerity_resource('aphront-typeahead-control-css');
$id = $this->id;
$name = $this->getName();
$values = nonempty($this->getValue(), array());
$tokens = array();
foreach ($values as $key => $value) {
$tokens[] = $this->renderToken($key, $value);
}
$input = javelin_render_tag(
'input',
array(
'name' => $name,
'class' => 'jx-typeahead-input',
'sigil' => 'typeahead',
'type' => 'text',
'value' => $this->value,
'autocomplete' => 'off',
));
return javelin_render_tag(
'div',
array(
'id' => $id,
'sigil' => 'typeahead-hardpoint',
'class' => 'jx-typeahead-hardpoint',
),
$input.
'<div style="clear: both;"></div>');
}
}
diff --git a/src/view/dialog/AphrontDialogView.php b/src/view/dialog/AphrontDialogView.php
index b0d598336b..4fed1fe710 100644
--- a/src/view/dialog/AphrontDialogView.php
+++ b/src/view/dialog/AphrontDialogView.php
@@ -1,204 +1,204 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class AphrontDialogView extends AphrontView {
+final class AphrontDialogView extends AphrontView {
private $title;
private $submitButton;
private $cancelURI;
private $cancelText = 'Cancel';
private $submitURI;
private $user;
private $hidden = array();
private $class;
private $renderAsForm = true;
private $formID;
private $width = 'default';
const WIDTH_DEFAULT = 'default';
const WIDTH_FORM = 'form';
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function setSubmitURI($uri) {
$this->submitURI = $uri;
return $this;
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function getTitle() {
return $this->title;
}
public function addSubmitButton($text = 'Okay') {
$this->submitButton = $text;
return $this;
}
public function addCancelButton($uri, $text = 'Cancel') {
$this->cancelURI = $uri;
$this->cancelText = $text;
return $this;
}
public function addHiddenInput($key, $value) {
if (is_array($value)) {
foreach ($value as $hidden_key => $hidden_value) {
$this->hidden[] = array($key.'['.$hidden_key.']', $hidden_value);
}
} else {
$this->hidden[] = array($key, $value);
}
return $this;
}
public function setClass($class) {
$this->class = $class;
return $this;
}
public function setRenderDialogAsDiv() {
// TODO: This API is awkward.
$this->renderAsForm = false;
return $this;
}
public function setFormID($id) {
$this->formID = $id;
return $this;
}
public function setWidth($width) {
$this->width = $width;
return $this;
}
final public function render() {
require_celerity_resource('aphront-dialog-view-css');
$buttons = array();
if ($this->submitButton) {
$buttons[] = javelin_render_tag(
'button',
array(
'name' => '__submit__',
'sigil' => '__default__',
),
phutil_escape_html($this->submitButton));
}
if ($this->cancelURI) {
$buttons[] = javelin_render_tag(
'a',
array(
'href' => $this->cancelURI,
'class' => 'button grey',
'name' => '__cancel__',
'sigil' => 'jx-workflow-button',
),
phutil_escape_html($this->cancelText));
}
$buttons = implode('', $buttons);
if (!$this->user) {
throw new Exception(
"You must call setUser() when rendering an AphrontDialogView.");
}
$more = $this->class;
switch ($this->width) {
case self::WIDTH_FORM:
$more .= ' aphront-dialog-view-width-'.$this->width;
break;
case self::WIDTH_DEFAULT:
break;
default:
throw new Exception("Unknown dialog width '{$this->width}'!");
}
$attributes = array(
'class' => 'aphront-dialog-view '.$more,
'sigil' => 'jx-dialog',
);
$form_attributes = array(
'action' => $this->submitURI,
'method' => 'post',
'id' => $this->formID,
);
$hidden_inputs = array();
foreach ($this->hidden as $desc) {
list($key, $value) = $desc;
$hidden_inputs[] = javelin_render_tag(
'input',
array(
'type' => 'hidden',
'name' => $key,
'value' => $value,
'sigil' => 'aphront-dialog-application-input'
));
}
$hidden_inputs = implode("\n", $hidden_inputs);
$hidden_inputs =
'<input type="hidden" name="__dialog__" value="1" />'.
$hidden_inputs;
if (!$this->renderAsForm) {
$buttons = phabricator_render_form(
$this->user,
$form_attributes,
$hidden_inputs.$buttons);
}
$content =
'<div class="aphront-dialog-head">'.
phutil_escape_html($this->title).
'</div>'.
'<div class="aphront-dialog-body">'.
$this->renderChildren().
'</div>'.
'<div class="aphront-dialog-tail">'.
$buttons.
'<div style="clear: both;"></div>'.
'</div>';
if ($this->renderAsForm) {
return phabricator_render_form(
$this->user,
$form_attributes + $attributes,
$hidden_inputs.
$content);
} else {
return javelin_render_tag(
'div',
$attributes,
$content);
}
}
}
diff --git a/src/view/form/control/checkbox/AphrontFormCheckboxControl.php b/src/view/form/control/checkbox/AphrontFormCheckboxControl.php
index f117026cbd..a33a8b2401 100644
--- a/src/view/form/control/checkbox/AphrontFormCheckboxControl.php
+++ b/src/view/form/control/checkbox/AphrontFormCheckboxControl.php
@@ -1,69 +1,69 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class AphrontFormCheckboxControl extends AphrontFormControl {
+final class AphrontFormCheckboxControl extends AphrontFormControl {
private $boxes = array();
public function addCheckbox($name, $value, $label, $checked = false) {
$this->boxes[] = array(
'name' => $name,
'value' => $value,
'label' => $label,
'checked' => $checked,
);
return $this;
}
protected function getCustomControlClass() {
return 'aphront-form-control-checkbox';
}
protected function renderInput() {
$rows = array();
foreach ($this->boxes as $box) {
$id = celerity_generate_unique_node_id();
$checkbox = phutil_render_tag(
'input',
array(
'id' => $id,
'type' => 'checkbox',
'name' => $box['name'],
'value' => $box['value'],
'checked' => $box['checked'] ? 'checked' : null,
'disabled' => $this->getDisabled() ? 'disabled' : null,
));
$label = phutil_render_tag(
'label',
array(
'for' => $id,
),
phutil_escape_html($box['label']));
$rows[] =
'<tr>'.
'<td>'.$checkbox.'</td>'.
'<th>'.$label.'</th>'.
'</tr>';
}
return
'<table class="aphront-form-control-checkbox-layout">'.
implode("\n", $rows).
'</table>';
}
}
diff --git a/src/view/form/control/divider/AphrontFormDividerControl.php b/src/view/form/control/divider/AphrontFormDividerControl.php
index 31c6dd81fc..5a102c2516 100644
--- a/src/view/form/control/divider/AphrontFormDividerControl.php
+++ b/src/view/form/control/divider/AphrontFormDividerControl.php
@@ -1,29 +1,29 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class AphrontFormDividerControl extends AphrontFormControl {
+final class AphrontFormDividerControl extends AphrontFormControl {
protected function getCustomControlClass() {
return 'aphront-form-control-divider';
}
protected function renderInput() {
return '<hr />';
}
}
diff --git a/src/view/form/control/draganddropupload/AphrontFormDragAndDropUploadControl.php b/src/view/form/control/draganddropupload/AphrontFormDragAndDropUploadControl.php
index 61976aed4d..47318704d7 100644
--- a/src/view/form/control/draganddropupload/AphrontFormDragAndDropUploadControl.php
+++ b/src/view/form/control/draganddropupload/AphrontFormDragAndDropUploadControl.php
@@ -1,82 +1,82 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class AphrontFormDragAndDropUploadControl extends AphrontFormControl {
+final class AphrontFormDragAndDropUploadControl extends AphrontFormControl {
private $dragAndDropTarget;
private $activatedClass;
public function __construct() {
$this->setControlID(celerity_generate_unique_node_id());
$this->setControlStyle('display: none;');
}
protected function getCustomControlClass() {
return 'aphront-form-drag-and-drop-upload';
}
public function setDragAndDropTarget($id) {
$this->dragAndDropTarget = $id;
return $this;
}
public function setActivatedClass($class) {
$this->activatedClass = $class;
return $this;
}
protected function renderInput() {
require_celerity_resource('aphront-attached-file-view-css');
$list_id = celerity_generate_unique_node_id();
$files = $this->getValue();
$value = array();
if ($files) {
foreach ($files as $file) {
$view = new AphrontAttachedFileView();
$view->setFile($file);
$value[$file->getPHID()] = array(
'phid' => $file->getPHID(),
'html' => $view->render(),
);
}
}
Javelin::initBehavior(
'aphront-drag-and-drop',
array(
'control' => $this->getControlID(),
'name' => $this->getName(),
'value' => nonempty($value, null),
'list' => $list_id,
'uri' => '/file/dropupload/',
'target' => $this->dragAndDropTarget,
'activatedClass' => $this->activatedClass,
));
return phutil_render_tag(
'div',
array(
'id' => $list_id,
'class' => 'aphront-form-drag-and-drop-file-list',
),
'');
}
}
diff --git a/src/view/form/control/file/AphrontFormFileControl.php b/src/view/form/control/file/AphrontFormFileControl.php
index 9c83ceaaf9..0840da3535 100644
--- a/src/view/form/control/file/AphrontFormFileControl.php
+++ b/src/view/form/control/file/AphrontFormFileControl.php
@@ -1,35 +1,35 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class AphrontFormFileControl extends AphrontFormControl {
+final class AphrontFormFileControl extends AphrontFormControl {
protected function getCustomControlClass() {
return 'aphront-form-file-text';
}
protected function renderInput() {
return phutil_render_tag(
'input',
array(
'type' => 'file',
'name' => $this->getName(),
'disabled' => $this->getDisabled() ? 'disabled' : null,
));
}
}
diff --git a/src/view/form/control/markup/AphrontFormMarkupControl.php b/src/view/form/control/markup/AphrontFormMarkupControl.php
index e85e1c416a..eec3dbdfba 100644
--- a/src/view/form/control/markup/AphrontFormMarkupControl.php
+++ b/src/view/form/control/markup/AphrontFormMarkupControl.php
@@ -1,29 +1,29 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class AphrontFormMarkupControl extends AphrontFormControl {
+final class AphrontFormMarkupControl extends AphrontFormControl {
protected function getCustomControlClass() {
return 'aphront-form-control-markup';
}
protected function renderInput() {
return $this->getValue();
}
}
diff --git a/src/view/form/control/password/AphrontFormPasswordControl.php b/src/view/form/control/password/AphrontFormPasswordControl.php
index ae02ea73f4..5a3bc4f775 100644
--- a/src/view/form/control/password/AphrontFormPasswordControl.php
+++ b/src/view/form/control/password/AphrontFormPasswordControl.php
@@ -1,37 +1,37 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class AphrontFormPasswordControl extends AphrontFormControl {
+final class AphrontFormPasswordControl extends AphrontFormControl {
protected function getCustomControlClass() {
return 'aphront-form-control-password';
}
protected function renderInput() {
return phutil_render_tag(
'input',
array(
'type' => 'password',
'name' => $this->getName(),
'value' => $this->getValue(),
'disabled' => $this->getDisabled() ? 'disabled' : null,
'id' => $this->getID(),
));
}
}
diff --git a/src/view/form/control/recaptcha/AphrontFormRecaptchaControl.php b/src/view/form/control/recaptcha/AphrontFormRecaptchaControl.php
index 7d2c0e715f..f56dceb50d 100644
--- a/src/view/form/control/recaptcha/AphrontFormRecaptchaControl.php
+++ b/src/view/form/control/recaptcha/AphrontFormRecaptchaControl.php
@@ -1,74 +1,74 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
*
* @phutil-external-symbol function recaptcha_get_html
* @phutil-external-symbol function recaptcha_check_answer
*/
-class AphrontFormRecaptchaControl extends AphrontFormControl {
+final class AphrontFormRecaptchaControl extends AphrontFormControl {
protected function getCustomControlClass() {
return 'aphront-form-control-recaptcha';
}
protected function shouldRender() {
return self::isRecaptchaEnabled();
}
public static function isRecaptchaEnabled() {
return PhabricatorEnv::getEnvConfig('recaptcha.enabled');
}
private static function requireLib() {
$root = phutil_get_library_root('phabricator');
require_once dirname($root).'/externals/recaptcha/recaptchalib.php';
}
public static function hasCaptchaResponse(AphrontRequest $request) {
return $request->getBool('recaptcha_response_field');
}
public static function processCaptcha(AphrontRequest $request) {
if (!self::isRecaptchaEnabled()) {
return true;
}
self::requireLib();
$challenge = $request->getStr('recaptcha_challenge_field');
$response = $request->getStr('recaptcha_response_field');
$resp = recaptcha_check_answer(
PhabricatorEnv::getEnvConfig('recaptcha.private-key'),
$_SERVER['REMOTE_ADDR'],
$challenge,
$response);
return (bool)@$resp->is_valid;
}
protected function renderInput() {
self::requireLib();
return recaptcha_get_html(
PhabricatorEnv::getEnvConfig('recaptcha.public-key'),
$error = null,
$use_ssl = false);
}
}
diff --git a/src/view/form/control/select/AphrontFormSelectControl.php b/src/view/form/control/select/AphrontFormSelectControl.php
index 0290a027ac..6162e4095b 100644
--- a/src/view/form/control/select/AphrontFormSelectControl.php
+++ b/src/view/form/control/select/AphrontFormSelectControl.php
@@ -1,69 +1,69 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class AphrontFormSelectControl extends AphrontFormControl {
+final class AphrontFormSelectControl extends AphrontFormControl {
protected function getCustomControlClass() {
return 'aphront-form-control-select';
}
private $options;
public function setOptions(array $options) {
$this->options = $options;
return $this;
}
public function getOptions() {
return $this->options;
}
protected function renderInput() {
return self::renderSelectTag(
$this->getValue(),
$this->getOptions(),
array(
'name' => $this->getName(),
'disabled' => $this->getDisabled() ? 'disabled' : null,
'id' => $this->getID(),
));
}
public static function renderSelectTag(
$selected,
array $options,
array $attrs = array()) {
$option_tags = array();
foreach ($options as $value => $label) {
$option_tags[] = phutil_render_tag(
'option',
array(
'selected' => ($value == $selected) ? 'selected' : null,
'value' => $value,
),
phutil_escape_html($label));
}
return phutil_render_tag(
'select',
$attrs,
implode("\n", $option_tags));
}
}
diff --git a/src/view/form/control/static/AphrontFormStaticControl.php b/src/view/form/control/static/AphrontFormStaticControl.php
index 0f011e0248..c2d1bdde1e 100644
--- a/src/view/form/control/static/AphrontFormStaticControl.php
+++ b/src/view/form/control/static/AphrontFormStaticControl.php
@@ -1,29 +1,29 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class AphrontFormStaticControl extends AphrontFormControl {
+final class AphrontFormStaticControl extends AphrontFormControl {
protected function getCustomControlClass() {
return 'aphront-form-control-static';
}
protected function renderInput() {
return phutil_escape_html($this->getValue());
}
}
diff --git a/src/view/form/control/submit/AphrontFormSubmitControl.php b/src/view/form/control/submit/AphrontFormSubmitControl.php
index 3cf8c59823..6394c99d46 100644
--- a/src/view/form/control/submit/AphrontFormSubmitControl.php
+++ b/src/view/form/control/submit/AphrontFormSubmitControl.php
@@ -1,52 +1,52 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class AphrontFormSubmitControl extends AphrontFormControl {
+final class AphrontFormSubmitControl extends AphrontFormControl {
protected $cancelButton;
public function addCancelButton($href, $label = 'Cancel') {
$this->cancelButton = phutil_render_tag(
'a',
array(
'href' => $href,
'class' => 'button grey',
),
phutil_escape_html($label));
return $this;
}
protected function getCustomControlClass() {
return 'aphront-form-control-submit';
}
protected function renderInput() {
$submit_button = null;
if ($this->getValue()) {
$submit_button = phutil_render_tag(
'button',
array(
'name' => '__submit__',
'disabled' => $this->getDisabled() ? 'disabled' : null,
),
phutil_escape_html($this->getValue()));
}
return $submit_button.$this->cancelButton;
}
}
diff --git a/src/view/form/control/text/AphrontFormTextControl.php b/src/view/form/control/text/AphrontFormTextControl.php
index 185ffd6a7e..5710b27a87 100644
--- a/src/view/form/control/text/AphrontFormTextControl.php
+++ b/src/view/form/control/text/AphrontFormTextControl.php
@@ -1,37 +1,37 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class AphrontFormTextControl extends AphrontFormControl {
+final class AphrontFormTextControl extends AphrontFormControl {
protected function getCustomControlClass() {
return 'aphront-form-control-text';
}
protected function renderInput() {
return phutil_render_tag(
'input',
array(
'type' => 'text',
'name' => $this->getName(),
'value' => $this->getValue(),
'disabled' => $this->getDisabled() ? 'disabled' : null,
'id' => $this->getID(),
));
}
}
diff --git a/src/view/form/control/textarea/AphrontFormTextAreaControl.php b/src/view/form/control/textarea/AphrontFormTextAreaControl.php
index 59d4e3f4d3..bcd5ebd6d4 100644
--- a/src/view/form/control/textarea/AphrontFormTextAreaControl.php
+++ b/src/view/form/control/textarea/AphrontFormTextAreaControl.php
@@ -1,91 +1,91 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class AphrontFormTextAreaControl extends AphrontFormControl {
+final class AphrontFormTextAreaControl extends AphrontFormControl {
const HEIGHT_VERY_SHORT = 'very-short';
const HEIGHT_SHORT = 'short';
const HEIGHT_VERY_TALL = 'very-tall';
private $height;
private $readOnly;
private $enableDragAndDropFileUploads;
public function setHeight($height) {
$this->height = $height;
return $this;
}
public function setReadOnly($read_only) {
$this->readOnly = $read_only;
return $this;
}
protected function getReadOnly() {
return $this->readOnly;
}
protected function getCustomControlClass() {
return 'aphront-form-control-textarea';
}
public function setEnableDragAndDropFileUploads($enable) {
$this->enableDragAndDropFileUploads = $enable;
return $this;
}
protected function renderInput() {
$height_class = null;
switch ($this->height) {
case self::HEIGHT_VERY_SHORT:
case self::HEIGHT_SHORT:
case self::HEIGHT_VERY_TALL:
$height_class = 'aphront-textarea-'.$this->height;
break;
}
$id = $this->getID();
if ($this->enableDragAndDropFileUploads) {
if (!$id) {
$id = celerity_generate_unique_node_id();
}
Javelin::initBehavior(
'aphront-drag-and-drop-textarea',
array(
'target' => $id,
'activatedClass' => 'aphront-textarea-drag-and-drop',
'uri' => '/file/dropupload/',
));
}
return phutil_render_tag(
'textarea',
array(
'name' => $this->getName(),
'disabled' => $this->getDisabled() ? 'disabled' : null,
'readonly' => $this->getReadonly() ? 'readonly' : null,
'class' => $height_class,
'style' => $this->getControlStyle(),
'id' => $id,
),
phutil_escape_html($this->getValue()));
}
}
diff --git a/src/view/form/control/togglebuttons/AphrontFormToggleButtonsControl.php b/src/view/form/control/togglebuttons/AphrontFormToggleButtonsControl.php
index 48fc3c2c61..fdb9e15495 100644
--- a/src/view/form/control/togglebuttons/AphrontFormToggleButtonsControl.php
+++ b/src/view/form/control/togglebuttons/AphrontFormToggleButtonsControl.php
@@ -1,68 +1,68 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class AphrontFormToggleButtonsControl extends AphrontFormControl {
+final class AphrontFormToggleButtonsControl extends AphrontFormControl {
private $baseURI;
private $param;
private $buttons;
public function setBaseURI(PhutilURI $uri, $param) {
$this->baseURI = $uri;
$this->param = $param;
return $this;
}
public function setButtons(array $buttons) {
$this->buttons = $buttons;
return $this;
}
protected function getCustomControlClass() {
return 'aphront-form-control-togglebuttons';
}
protected function renderInput() {
if (!$this->baseURI) {
throw new Exception('Call setBaseURI() before render()!');
}
$selected = $this->getValue();
$out = array();
foreach ($this->buttons as $value => $label) {
if ($value == $selected) {
$more = ' toggle-selected toggle-fixed';
} else {
$more = null;
}
$out[] = phutil_render_tag(
'a',
array(
'class' => 'toggle'.$more,
'href' => $this->baseURI->alter($this->param, $value),
),
phutil_escape_html($label));
}
return implode('', $out);
}
}
diff --git a/src/view/form/control/tokenizer/AphrontFormTokenizerControl.php b/src/view/form/control/tokenizer/AphrontFormTokenizerControl.php
index 70d0ca7913..06762d7feb 100644
--- a/src/view/form/control/tokenizer/AphrontFormTokenizerControl.php
+++ b/src/view/form/control/tokenizer/AphrontFormTokenizerControl.php
@@ -1,122 +1,122 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class AphrontFormTokenizerControl extends AphrontFormControl {
+final class AphrontFormTokenizerControl extends AphrontFormControl {
private $datasource;
private $disableBehavior;
private $limit;
private $user;
private $placeholder;
public function setDatasource($datasource) {
$this->datasource = $datasource;
return $this;
}
public function setDisableBehavior($disable) {
$this->disableBehavior = $disable;
return $this;
}
protected function getCustomControlClass() {
return 'aphront-form-control-tokenizer';
}
public function setLimit($limit) {
$this->limit = $limit;
return $this;
}
public function setUser($user) {
$this->user = $user;
return $this;
}
public function setPlaceholder($placeholder) {
$this->placeholder = $placeholder;
return $this;
}
protected function renderInput() {
$name = $this->getName();
$values = nonempty($this->getValue(), array());
if ($this->getID()) {
$id = $this->getID();
} else {
$id = celerity_generate_unique_node_id();
}
$placeholder = null;
if (!$this->placeholder) {
$placeholder = $this->getDefaultPlaceholder();
}
$template = new AphrontTokenizerTemplateView();
$template->setName($name);
$template->setID($id);
$template->setValue($values);
$username = null;
if ($this->user) {
$username = $this->user->getUsername();
}
if (!$this->disableBehavior) {
Javelin::initBehavior('aphront-basic-tokenizer', array(
'id' => $id,
'src' => $this->datasource,
'value' => $values,
'limit' => $this->limit,
'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
'username' => $username,
'placeholder' => $placeholder,
));
}
return $template->render();
}
private function getDefaultPlaceholder() {
$datasource = $this->datasource;
$matches = null;
if (!preg_match('@^/typeahead/common/(.*)/$@', $datasource, $matches)) {
return null;
}
$request = $matches[1];
$map = array(
'users' => 'Type a user name...',
'searchowner' => 'Type a user name...',
'accounts' => 'Type a user name...',
'mailable' => 'Type a user or mailing list...',
'searchproject' => 'Type a project name...',
'projects' => 'Type a project name...',
'repositories' => 'Type a repository name...',
'packages' => 'Type a package name...',
'arcanistproject' => 'Type an arc project name...',
);
return idx($map, $request);
}
}
diff --git a/src/view/javelin-view/AphrontJavelinView.php b/src/view/javelin-view/AphrontJavelinView.php
index a69789753d..ea17718ee6 100644
--- a/src/view/javelin-view/AphrontJavelinView.php
+++ b/src/view/javelin-view/AphrontJavelinView.php
@@ -1,87 +1,87 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class AphrontJavelinView extends AphrontView {
+final class AphrontJavelinView extends AphrontView {
private static $renderContext = array();
private static function peekRenderContext() {
return nonempty(end(self::$renderContext), null);
}
private static function popRenderContext() {
return array_pop(self::$renderContext);
}
private static function pushRenderContext($token) {
self::$renderContext[] = $token;
}
private $name;
private $parameters;
private $celerityResource;
public function render() {
$id = celerity_generate_unique_node_id();
$placeholder = "<span id={$id} />";
require_celerity_resource($this->getCelerityResource());
$render_context = self::peekRenderContext();
self::pushRenderContext($id);
Javelin::initBehavior('view-placeholder', array(
'id' => $id,
'view' => $this->getName(),
'params' => $this->getParameters(),
'children' => $this->renderChildren(),
'trigger_id' => $render_context,
));
self::popRenderContext();
return $placeholder;
}
protected function getName() {
return $this->name;
}
final public function setName($template_name) {
$this->name = $template_name;
return $this;
}
protected function getParameters() {
return $this->parameters;
}
final public function setParameters($template_parameters) {
$this->parameters = $template_parameters;
return $this;
}
protected function getCelerityResource() {
return $this->celerityResource;
}
final public function setCelerityResource($celerity_resource) {
$this->celerityResource = $celerity_resource;
return $this;
}
}
diff --git a/src/view/page/base/AphrontPageView.php b/src/view/page/base/AphrontPageView.php
index 3af1881b3e..34494b0497 100644
--- a/src/view/page/base/AphrontPageView.php
+++ b/src/view/page/base/AphrontPageView.php
@@ -1,88 +1,88 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class AphrontPageView extends AphrontView {
+abstract class AphrontPageView extends AphrontView {
private $title;
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function getTitle() {
return $this->title;
}
protected function getHead() {
return '';
}
protected function getBody() {
return $this->renderChildren();
}
protected function getTail() {
return '';
}
protected function willRenderPage() {
return;
}
protected function willSendResponse($response) {
return $response;
}
protected function getBodyClasses() {
return null;
}
public function render() {
$this->willRenderPage();
$title = phutil_escape_html($this->getTitle());
$head = $this->getHead();
$body = $this->getBody();
$tail = $this->getTail();
$body_classes = $this->getBodyClasses();
$response = <<<EOHTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>{$title}</title>
{$head}
</head>
<body class="{$body_classes}">
{$body}
{$tail}
</body>
</html>
EOHTML;
$response = $this->willSendResponse($response);
return $response;
}
}
diff --git a/src/view/page/standard/PhabricatorStandardPageView.php b/src/view/page/standard/PhabricatorStandardPageView.php
index 370529a76d..f2a127b8c1 100644
--- a/src/view/page/standard/PhabricatorStandardPageView.php
+++ b/src/view/page/standard/PhabricatorStandardPageView.php
@@ -1,429 +1,429 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorStandardPageView extends AphrontPageView {
+final class PhabricatorStandardPageView extends AphrontPageView {
private $baseURI;
private $applicationName;
private $tabs = array();
private $selectedTab;
private $glyph;
private $bodyContent;
private $request;
private $isAdminInterface;
private $showChrome = true;
private $isFrameable = false;
private $disableConsole;
private $searchDefaultScope;
public function setIsAdminInterface($is_admin_interface) {
$this->isAdminInterface = $is_admin_interface;
return $this;
}
public function setIsLoggedOut($is_logged_out) {
if ($is_logged_out) {
$this->tabs = array_merge($this->tabs, array(
'login' => array(
'name' => 'Login',
'href' => '/login/'
)
));
}
return $this;
}
public function getIsAdminInterface() {
return $this->isAdminInterface;
}
public function setRequest($request) {
$this->request = $request;
return $this;
}
public function getRequest() {
return $this->request;
}
public function setApplicationName($application_name) {
$this->applicationName = $application_name;
return $this;
}
public function setFrameable($frameable) {
$this->isFrameable = $frameable;
return $this;
}
public function setDisableConsole($disable) {
$this->disableConsole = $disable;
return $this;
}
public function getApplicationName() {
return $this->applicationName;
}
public function setBaseURI($base_uri) {
$this->baseURI = $base_uri;
return $this;
}
public function getBaseURI() {
return $this->baseURI;
}
public function setTabs(array $tabs, $selected_tab) {
$this->tabs = $tabs;
$this->selectedTab = $selected_tab;
return $this;
}
public function setShowChrome($show_chrome) {
$this->showChrome = $show_chrome;
return $this;
}
public function getShowChrome() {
return $this->showChrome;
}
public function setSearchDefaultScope($search_default_scope) {
$this->searchDefaultScope = $search_default_scope;
return $this;
}
public function getSearchDefaultScope() {
return $this->searchDefaultScope;
}
public function getTitle() {
$use_glyph = true;
$request = $this->getRequest();
if ($request) {
$user = $request->getUser();
if ($user && $user->loadPreferences()->getPreference(
PhabricatorUserPreferences::PREFERENCE_TITLES) !== 'glyph') {
$use_glyph = false;
}
}
return ($use_glyph ?
$this->getGlyph() : '['.$this->getApplicationName().']').
' '.parent::getTitle();
}
protected function willRenderPage() {
if (!$this->getRequest()) {
throw new Exception(
"You must set the Request to render a PhabricatorStandardPageView.");
}
$console = $this->getConsole();
require_celerity_resource('phabricator-core-css');
require_celerity_resource('phabricator-core-buttons-css');
require_celerity_resource('phabricator-standard-page-view');
$current_token = null;
$request = $this->getRequest();
if ($request) {
$user = $request->getUser();
if ($user) {
$current_token = $user->getCSRFToken();
}
}
Javelin::initBehavior('workflow', array());
Javelin::initBehavior(
'refresh-csrf',
array(
'tokenName' => AphrontRequest::getCSRFTokenName(),
'header' => AphrontRequest::getCSRFHeaderName(),
'current' => $current_token,
));
Javelin::initBehavior(
'phabricator-keyboard-shortcuts',
array(
'helpURI' => '/help/keyboardshortcut/',
));
if ($console) {
require_celerity_resource('aphront-dark-console-css');
Javelin::initBehavior(
'dark-console',
array(
'uri' => '/~/',
));
// Change this to initBehavior when there is some behavior to initialize
require_celerity_resource('javelin-behavior-error-log');
}
$this->bodyContent = $this->renderChildren();
}
protected function getHead() {
$framebust = null;
if (!$this->isFrameable) {
$framebust = '(top != self) && top.location.replace(self.location.href);';
}
$response = CelerityAPI::getStaticResourceResponse();
$head =
'<script type="text/javascript">'.
$framebust.
'window.__DEV__=1;'.
'</script>'.
$response->renderResourcesOfType('css').
$response->renderSingleResource('javelin-magical-init');
$request = $this->getRequest();
if ($request) {
$user = $request->getUser();
if ($user) {
$monospaced = $user->loadPreferences()->getPreference(
PhabricatorUserPreferences::PREFERENCE_MONOSPACED
);
if (strlen($monospaced)) {
$head .=
'<style type="text/css">'.
'.PhabricatorMonospaced { font: '.
$monospaced.
' !important; }'.
'</style>';
}
}
}
return $head;
}
public function setGlyph($glyph) {
$this->glyph = $glyph;
return $this;
}
public function getGlyph() {
return $this->glyph;
}
protected function willSendResponse($response) {
$console = $this->getRequest()->getApplicationConfiguration()->getConsole();
if ($console) {
$response = str_replace(
'<darkconsole />',
$console->render($this->getRequest()),
$response);
}
return $response;
}
protected function getBody() {
$console = $this->getConsole();
$tabs = array();
foreach ($this->tabs as $name => $tab) {
$tab_markup = phutil_render_tag(
'a',
array(
'href' => idx($tab, 'href'),
),
phutil_escape_html(idx($tab, 'name')));
$tab_markup = phutil_render_tag(
'td',
array(
'class' => ($name == $this->selectedTab)
? 'phabricator-selected-tab'
: null,
),
$tab_markup);
$tabs[] = $tab_markup;
}
$tabs = implode('', $tabs);
$login_stuff = null;
$request = $this->getRequest();
$user = null;
if ($request) {
$user = $request->getUser();
// NOTE: user may not be set here if we caught an exception early
// in the execution workflow.
if ($user && $user->getPHID()) {
$login_stuff =
phutil_render_tag(
'a',
array(
'href' => '/p/'.$user->getUsername().'/',
),
phutil_escape_html($user->getUsername())).
' &middot; '.
'<a href="/settings/">Settings</a>'.
' &middot; '.
phabricator_render_form(
$user,
array(
'action' => '/search/',
'method' => 'post',
'style' => 'display: inline',
),
'<input type="text" name="query" id="standard-search-box" />'.
' in '.
AphrontFormSelectControl::renderSelectTag(
$this->getSearchDefaultScope(),
PhabricatorSearchScope::getScopeOptions(),
array(
'name' => 'scope',
)).
' '.
'<button>Search</button>');
}
}
$foot_links = array();
$version = PhabricatorEnv::getEnvConfig('phabricator.version');
$foot_links[] = phutil_escape_html('Phabricator '.$version);
if (PhabricatorEnv::getEnvConfig('darkconsole.enabled') &&
!PhabricatorEnv::getEnvConfig('darkconsole.always-on')) {
if ($console) {
$link = javelin_render_tag(
'a',
array(
'href' => '/~/',
'sigil' => 'workflow',
),
'Disable DarkConsole');
} else {
$link = javelin_render_tag(
'a',
array(
'href' => '/~/',
'sigil' => 'workflow',
),
'Enable DarkConsole');
}
$foot_links[] = $link;
}
if ($user && $user->getPHID()) {
// This ends up very early in tab order at the top of the page and there's
// a bunch of junk up there anyway, just shove it down here.
$foot_links[] = phabricator_render_form(
$user,
array(
'action' => '/logout/',
'method' => 'post',
'style' => 'display: inline',
),
'<button class="link">Logout</button>');
}
$foot_links = implode(' &middot; ', $foot_links);
$admin_class = null;
if ($this->getIsAdminInterface()) {
$admin_class = 'phabricator-admin-page-view';
}
$header_chrome = null;
$footer_chrome = null;
if ($this->getShowChrome()) {
$header_chrome =
'<table class="phabricator-standard-header">'.
'<tr>'.
'<td class="phabricator-logo"><a href="/"> </a></td>'.
'<td>'.
'<table class="phabricator-primary-navigation">'.
'<tr>'.
'<th>'.
phutil_render_tag(
'a',
array(
'href' => $this->getBaseURI(),
'class' => 'phabricator-head-appname',
),
phutil_escape_html($this->getApplicationName())).
'</th>'.
$tabs.
'</tr>'.
'</table>'.
'</td>'.
'<td class="phabricator-login-details">'.
$login_stuff.
'</td>'.
'</tr>'.
'</table>';
$footer_chrome =
'<div class="phabricator-page-foot">'.
$foot_links.
'</div>';
}
$developer_warning = null;
if (PhabricatorEnv::getEnvConfig('phabricator.show-error-callout') &&
DarkConsoleErrorLogPluginAPI::getErrors()) {
$developer_warning =
'<div class="aphront-developer-error-callout">'.
'This page raised PHP errors. Find them in DarkConsole '.
'or the error log.'.
'</div>';
}
return
($console ? '<darkconsole />' : null).
$developer_warning.
'<div class="phabricator-standard-page '.$admin_class.'">'.
$header_chrome.
$this->bodyContent.
'<div style="clear: both;"></div>'.
'</div>'.
$footer_chrome;
}
protected function getTail() {
$response = CelerityAPI::getStaticResourceResponse();
return
$response->renderResourcesOfType('js').
$response->renderHTMLFooter();
}
protected function getBodyClasses() {
$classes = array();
if (!$this->getShowChrome()) {
$classes[] = 'phabricator-chromeless-page';
}
return implode(' ', $classes);
}
private function getConsole() {
if ($this->disableConsole) {
return null;
}
return $this->getRequest()->getApplicationConfiguration()->getConsole();
}
}
diff --git a/src/view/widget/keyboardshortcuts/AphrontKeyboardShortcutsAvailableView.php b/src/view/widget/keyboardshortcuts/AphrontKeyboardShortcutsAvailableView.php
index 5f0be97c5c..d1a641104a 100644
--- a/src/view/widget/keyboardshortcuts/AphrontKeyboardShortcutsAvailableView.php
+++ b/src/view/widget/keyboardshortcuts/AphrontKeyboardShortcutsAvailableView.php
@@ -1,31 +1,31 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class AphrontKeyboardShortcutsAvailableView extends AphrontView {
+final class AphrontKeyboardShortcutsAvailableView extends AphrontView {
public function render() {
return
'<table class="keyboard-shortcuts-available">'.
'<tr>'.
'<th>Press <strong>?</strong> to show keyboard shortcuts.</th>'.
'<td></td>'.
'</tr>'.
'</table>';
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Sep 20, 4:32 AM (1 d, 2 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
241247
Default Alt Text
(647 KB)

Event Timeline