Page MenuHomestyx hydra

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/conf/production.conf.php b/conf/production.conf.php
index e9a474ee11..c0a7bc2448 100644
--- a/conf/production.conf.php
+++ b/conf/production.conf.php
@@ -1,6 +1,5 @@
<?php
return array(
) + phabricator_read_config_file('default');
-
diff --git a/resources/sql/patches/000.project.sql b/resources/sql/patches/000.project.sql
index 6013c6c30d..62221ce871 100644
--- a/resources/sql/patches/000.project.sql
+++ b/resources/sql/patches/000.project.sql
@@ -1,31 +1,29 @@
-
create table {$NAMESPACE}_project.project (
id int unsigned not null auto_increment primary key,
name varchar(255) not null,
unique key (name),
phid varchar(64) binary not null,
authorPHID varchar(64) binary not null,
dateCreated int unsigned not null,
dateModified int unsigned not null
);
create table {$NAMESPACE}_project.project_profile (
id int unsigned not null auto_increment primary key,
projectPHID varchar(64) binary not null,
unique key (projectPHID),
blurb longtext not null,
profileImagePHID varchar(64) binary,
dateCreated int unsigned not null,
dateModified int unsigned not null
);
create table {$NAMESPACE}_project.project_affiliation (
id int unsigned not null auto_increment primary key,
projectPHID varchar(64) binary not null,
userPHID varchar(64) binary not null,
unique key (projectPHID, userPHID),
key (userPHID),
role varchar(255) not null,
status varchar(32) not null,
dateCreated int unsigned not null,
dateModified int unsigned not null
);
-
diff --git a/resources/sql/patches/0000.legacy.sql b/resources/sql/patches/0000.legacy.sql
index f1c179d3f1..fc02040ee3 100644
--- a/resources/sql/patches/0000.legacy.sql
+++ b/resources/sql/patches/0000.legacy.sql
@@ -1,731 +1,725 @@
-
-
-
-
-
-
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
USE `{$NAMESPACE}_conduit`;
DROP TABLE IF EXISTS `conduit_connectionlog`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `conduit_connectionlog` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`client` varchar(255) DEFAULT NULL,
`clientVersion` varchar(255) DEFAULT NULL,
`clientDescription` varchar(255) DEFAULT NULL,
`username` varchar(255) DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `conduit_methodcalllog`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `conduit_methodcalllog` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`connectionID` bigint(20) unsigned DEFAULT NULL,
`method` varchar(255) NOT NULL,
`error` varchar(255) NOT NULL,
`duration` bigint(20) unsigned NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
USE `{$NAMESPACE}_differential`;
DROP TABLE IF EXISTS `differential_changeset`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `differential_changeset` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`diffID` int(10) unsigned NOT NULL,
`oldFile` varchar(255) DEFAULT NULL,
`fileName` varchar(255) NOT NULL,
`awayPaths` longblob,
`changeType` int(10) unsigned NOT NULL,
`fileType` int(10) unsigned NOT NULL,
`metadata` longblob,
`oldProperties` longblob,
`newProperties` longblob,
`addLines` int(10) unsigned NOT NULL,
`delLines` int(10) unsigned NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `differential_changeset_parse_cache`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `differential_changeset_parse_cache` (
`id` int(10) unsigned NOT NULL,
`cache` longblob NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `differential_comment`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `differential_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`revisionID` int(10) unsigned NOT NULL,
`authorPHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`action` varchar(64) NOT NULL,
`content` longblob NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`cache` longblob,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `differential_diff`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `differential_diff` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`revisionID` int(10) unsigned DEFAULT NULL,
`authorPHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
`sourceMachine` varchar(255) DEFAULT NULL,
`sourcePath` varchar(255) DEFAULT NULL,
`sourceControlSystem` varchar(64) DEFAULT NULL,
`sourceControlBaseRevision` varchar(255) DEFAULT NULL,
`sourceControlpath` varchar(255) DEFAULT NULL,
`lintStatus` int(10) unsigned NOT NULL,
`unitStatus` int(10) unsigned NOT NULL,
`lineCount` int(10) unsigned NOT NULL,
`branch` varchar(255) DEFAULT NULL,
`parentRevisionID` int(10) unsigned DEFAULT NULL,
`arcanistProject` varchar(255) DEFAULT NULL,
`creationMethod` varchar(255) DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`description` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `differential_diffproperty`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `differential_diffproperty` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`diffID` int(10) unsigned NOT NULL,
`name` varchar(255) NOT NULL,
`data` longblob NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `differential_hunk`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `differential_hunk` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`changesetID` int(10) unsigned NOT NULL,
`changes` longblob,
`oldOffset` int(10) unsigned NOT NULL,
`oldLen` int(10) unsigned NOT NULL,
`newOffset` int(10) unsigned NOT NULL,
`newLen` int(10) unsigned NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `differential_inlinecomment`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `differential_inlinecomment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`revisionID` int(10) unsigned NOT NULL,
`commentID` int(10) unsigned DEFAULT NULL,
`authorPHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`changesetID` int(10) unsigned NOT NULL,
`isNewFile` tinyint(1) NOT NULL,
`lineNumber` int(10) unsigned NOT NULL,
`lineLength` int(10) unsigned NOT NULL,
`content` longblob NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`cache` longblob,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `differential_relationship`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `differential_relationship` (
`revisionID` int(10) unsigned NOT NULL,
`relation` varchar(4) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`objectPHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`sequence` int(10) unsigned NOT NULL,
`reasonPHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
PRIMARY KEY (`revisionID`,`relation`,`objectPHID`),
KEY `objectPHID` (`objectPHID`,`relation`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `differential_revision`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `differential_revision` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`phid` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`status` varchar(32) NOT NULL,
`summary` longtext NOT NULL,
`testPlan` text NOT NULL,
`revertPlan` text NOT NULL,
`blameRevision` varchar(255) NOT NULL,
`authorPHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
`dateCommitted` int(10) unsigned DEFAULT NULL,
`lineCount` int(10) unsigned DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`attached` longtext NOT NULL,
`unsubscribed` longblob NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
USE `{$NAMESPACE}_draft`;
DROP TABLE IF EXISTS `draft`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `draft` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`authorPHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`draftKey` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`draft` longblob NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `authorPHID` (`authorPHID`,`draftKey`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
USE `{$NAMESPACE}_file`;
DROP TABLE IF EXISTS `file`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `file` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`name` varchar(255) DEFAULT NULL,
`mimeType` varchar(255) DEFAULT NULL,
`byteSize` bigint(20) unsigned NOT NULL,
`storageEngine` varchar(32) NOT NULL,
`storageFormat` varchar(32) NOT NULL,
`storageHandle` varchar(255) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `file_storageblob`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `file_storageblob` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longblob NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
USE `{$NAMESPACE}_metamta`;
DROP TABLE IF EXISTS `metamta_mail`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `metamta_mail` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`parameters` longblob NOT NULL,
`status` varchar(255) NOT NULL,
`message` text,
`retryCount` int(10) unsigned NOT NULL,
`nextRetry` int(10) unsigned NOT NULL,
`relatedPHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `metamta_mailinglist`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `metamta_mailinglist` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`name` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
`uri` varchar(255) DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `phid_type`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `phid_type` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`type` varchar(4) NOT NULL,
`name` varchar(255) NOT NULL,
`description` text,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `type` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
USE `{$NAMESPACE}_user`;
DROP TABLE IF EXISTS `phabricator_session`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `phabricator_session` (
`userPHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`type` varchar(32) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`sessionKey` varchar(40) NOT NULL,
`sessionStart` int(10) unsigned NOT NULL,
PRIMARY KEY (`userPHID`,`type`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `user`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`userName` varchar(64) NOT NULL,
`realName` varchar(128) NOT NULL,
`email` varchar(255) NOT NULL,
`passwordSalt` varchar(32) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
`passwordHash` varchar(32) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`facebookUID` bigint(20) unsigned DEFAULT NULL,
`profileImagePHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
`consoleEnabled` tinyint(1) NOT NULL,
`consoleVisible` tinyint(1) NOT NULL,
`consoleTab` varchar(64) NOT NULL,
`conduitCertificate` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `userName` (`userName`),
UNIQUE KEY `email` (`email`),
UNIQUE KEY `facebookUID` (`facebookUID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `user_profile`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `user_profile` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`userPHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`title` varchar(255) NOT NULL,
`blurb` text NOT NULL,
`profileImagePHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `userPHID` (`userPHID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
USE `{$NAMESPACE}_file`;
DROP TABLE IF EXISTS `file`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `file` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`name` varchar(255) DEFAULT NULL,
`mimeType` varchar(255) DEFAULT NULL,
`byteSize` bigint(20) unsigned NOT NULL,
`storageEngine` varchar(32) NOT NULL,
`storageFormat` varchar(32) NOT NULL,
`storageHandle` varchar(255) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `file_storageblob`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `file_storageblob` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longblob NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
USE `{$NAMESPACE}_repository`;
DROP TABLE IF EXISTS `repository`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `repository` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`name` varchar(255) NOT NULL,
`callsign` varchar(32) NOT NULL,
`description` text,
`versionControlSystem` varchar(32) NOT NULL,
`details` longblob NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `callsign` (`callsign`),
UNIQUE KEY `phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `repository_githubnotification`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `repository_githubnotification` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`repositoryPHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`remoteAddress` varchar(32) NOT NULL,
`payload` longblob NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `repositoryPHID` (`repositoryPHID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
USE `{$NAMESPACE}_search`;
DROP TABLE IF EXISTS `search_document`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `search_document` (
`phid` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`documentType` varchar(4) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`documentTitle` varchar(255) NOT NULL,
`documentCreated` int(10) unsigned NOT NULL,
`documentModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `search_documentfield`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `search_documentfield` (
`phid` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`phidType` varchar(4) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`field` varchar(4) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`auxPHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
`corpus` text,
KEY `phid` (`phid`),
FULLTEXT KEY `corpus` (`corpus`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `search_documentrelationship`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `search_documentrelationship` (
`phid` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`relatedPHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`relation` varchar(4) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`relatedType` varchar(4) NOT NULL,
`relatedTime` int(10) unsigned NOT NULL,
KEY `phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `search_query`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `search_query` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`authorPHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
`query` varchar(255) NOT NULL,
`parameters` text NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
USE `{$NAMESPACE}_maniphest`;
DROP TABLE IF EXISTS `maniphest_task`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `maniphest_task` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`authorPHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`ownerPHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
`ccPHIDs` text,
`attached` longtext NOT NULL,
`status` int(10) unsigned NOT NULL,
`priority` int(10) unsigned NOT NULL,
`title` text NOT NULL,
`description` longtext NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `maniphest_touch`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `maniphest_touch` (
`userPHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`taskID` int(10) unsigned NOT NULL,
`touchedAt` int(10) unsigned NOT NULL,
PRIMARY KEY (`userPHID`,`taskID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `maniphest_transaction`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `maniphest_transaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`taskID` int(10) unsigned NOT NULL,
`authorPHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`transactionType` varchar(16) NOT NULL,
`oldValue` longblob,
`newValue` longblob,
`comments` longblob,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`cache` longblob,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
diff --git a/resources/sql/patches/002.oauth.sql b/resources/sql/patches/002.oauth.sql
index bacd1872a6..4f68261188 100644
--- a/resources/sql/patches/002.oauth.sql
+++ b/resources/sql/patches/002.oauth.sql
@@ -1,18 +1,18 @@
create table {$NAMESPACE}_user.user_oauthinfo (
id int unsigned not null auto_increment primary key,
userID int unsigned not null,
oauthProvider varchar(255) not null,
oauthUID varchar(255) not null,
unique key (userID, oauthProvider),
unique key (oauthProvider, oauthUID),
dateCreated int unsigned not null,
dateModified int unsigned not null
);
insert into {$NAMESPACE}_user.user_oauthinfo
(userID, oauthProvider, oauthUID, dateCreated, dateModified)
SELECT id, 'facebook', facebookUID, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
FROM {$NAMESPACE}_user.user
WHERE facebookUID is not null;
-alter table {$NAMESPACE}_user.user drop facebookUID;
\ No newline at end of file
+alter table {$NAMESPACE}_user.user drop facebookUID;
diff --git a/resources/sql/patches/004.daemonrepos.sql b/resources/sql/patches/004.daemonrepos.sql
index f3e472bc4b..e58aac7233 100644
--- a/resources/sql/patches/004.daemonrepos.sql
+++ b/resources/sql/patches/004.daemonrepos.sql
@@ -1,28 +1,28 @@
create table {$NAMESPACE}_repository.repository_commit (
id int unsigned not null auto_increment primary key,
repositoryPHID varchar(64) binary not null,
phid varchar(64) binary not null,
commitIdentifier varchar(40) binary not null,
epoch int unsigned not null,
unique key (phid),
unique key (repositoryPHID, commitIdentifier)
);
create table {$NAMESPACE}_timeline.timeline_event (
id int unsigned not null auto_increment primary key,
type char(4) binary not null,
key (type, id)
);
create table {$NAMESPACE}_timeline.timeline_eventdata (
id int unsigned not null auto_increment primary key,
eventID int unsigned not null,
eventData longblob not null,
unique key (eventID)
);
create table {$NAMESPACE}_timeline.timeline_cursor (
name varchar(255) not null primary key,
position int unsigned not null
-);
\ No newline at end of file
+);
diff --git a/resources/sql/patches/005.workers.sql b/resources/sql/patches/005.workers.sql
index 29d402f20f..c05718ebf7 100644
--- a/resources/sql/patches/005.workers.sql
+++ b/resources/sql/patches/005.workers.sql
@@ -1,20 +1,18 @@
-
-
create table {$NAMESPACE}_worker.worker_task (
id int unsigned not null auto_increment primary key,
taskClass varchar(255) not null,
leaseOwner varchar(255),
leaseExpires int unsigned,
priority bigint unsigned not null,
failureCount int unsigned not null,
key(taskClass),
key(leaseOwner),
key(leaseExpires)
);
create table {$NAMESPACE}_worker.worker_taskdata (
id int unsigned not null auto_increment primary key,
taskID int unsigned not null,
data longblob not null,
unique key (taskID)
);
diff --git a/resources/sql/patches/007.daemonlog.sql b/resources/sql/patches/007.daemonlog.sql
index 15529ee848..ce1daee490 100644
--- a/resources/sql/patches/007.daemonlog.sql
+++ b/resources/sql/patches/007.daemonlog.sql
@@ -1,20 +1,18 @@
-
-
create table {$NAMESPACE}_daemon.daemon_log (
id int unsigned not null auto_increment primary key,
daemon varchar(255) not null,
host varchar(255) not null,
pid int unsigned not null,
argv varchar(512) not null,
dateCreated int unsigned not null,
dateModified int unsigned not null
);
create table {$NAMESPACE}_daemon.daemon_logevent (
id int unsigned not null auto_increment primary key,
logID int unsigned not null,
logType varchar(4) not null,
message longblob not null,
epoch int unsigned not null,
key (logID, epoch)
);
diff --git a/resources/sql/patches/009.repo_summary.sql b/resources/sql/patches/009.repo_summary.sql
index c5ab4e3021..0509b68d80 100644
--- a/resources/sql/patches/009.repo_summary.sql
+++ b/resources/sql/patches/009.repo_summary.sql
@@ -1,7 +1,7 @@
CREATE TABLE {$NAMESPACE}_repository.`repository_summary` (
`repositoryID` int(10) unsigned NOT NULL,
`size` int(10) unsigned NOT NULL,
`lastCommitID` int(10) unsigned NOT NULL,
`epoch` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`repositoryID`)
-);
\ No newline at end of file
+);
diff --git a/resources/sql/patches/010.herald.sql b/resources/sql/patches/010.herald.sql
index 59deb29485..57f86acc03 100644
--- a/resources/sql/patches/010.herald.sql
+++ b/resources/sql/patches/010.herald.sql
@@ -1,44 +1,42 @@
-
-
CREATE TABLE {$NAMESPACE}_herald.herald_action (
id int unsigned not null auto_increment primary key,
ruleID int unsigned not null,
action varchar(255) not null,
target text not null
);
CREATE TABLE {$NAMESPACE}_herald.herald_rule (
id int unsigned not null auto_increment primary key,
name varchar(255) not null,
authorPHID varchar(64) binary not null,
contentType varchar(255) not null,
mustMatchAll bool not null,
configVersion int unsigned not null default '1',
dateCreated int unsigned not null,
dateModified int unsigned not null,
unique key (authorPHID, name)
);
CREATE TABLE {$NAMESPACE}_herald.herald_condition (
id int unsigned not null auto_increment primary key,
ruleID int unsigned not null,
fieldName varchar(255) not null,
fieldCondition varchar(255) not null,
value text not null
);
CREATE TABLE {$NAMESPACE}_herald.herald_transcript (
id int unsigned not null auto_increment primary key,
phid varchar(64) binary not null,
time int unsigned not null,
host varchar(255) not null,
psth varchar(255) not null,
duration float not null,
objectPHID varchar(64) binary not null,
dryRun bool not null,
objectTranscript longblob not null,
ruleTranscripts longblob not null,
conditionTranscripts longblob not null,
applyTranscripts longblob not null,
unique key (phid)
);
diff --git a/resources/sql/patches/011.badcommit.sql b/resources/sql/patches/011.badcommit.sql
index 76a8255417..58dbc45993 100644
--- a/resources/sql/patches/011.badcommit.sql
+++ b/resources/sql/patches/011.badcommit.sql
@@ -1,4 +1,4 @@
CREATE TABLE {$NAMESPACE}_repository.repository_badcommit (
fullCommitName varchar(255) binary not null primary key,
description longblob not null
-);
\ No newline at end of file
+);
diff --git a/resources/sql/patches/012.dropphidtype.sql b/resources/sql/patches/012.dropphidtype.sql
index 340a64fccb..53e0e1e937 100644
--- a/resources/sql/patches/012.dropphidtype.sql
+++ b/resources/sql/patches/012.dropphidtype.sql
@@ -1 +1 @@
-/* This database was later removed entirely. */
\ No newline at end of file
+/* This database was later removed entirely. */
diff --git a/resources/sql/patches/016.userrealnameindex.sql b/resources/sql/patches/016.userrealnameindex.sql
index 616f062869..58f1235b9c 100644
--- a/resources/sql/patches/016.userrealnameindex.sql
+++ b/resources/sql/patches/016.userrealnameindex.sql
@@ -1 +1 @@
-ALTER TABLE {$NAMESPACE}_user.user ADD key (realName);
\ No newline at end of file
+ALTER TABLE {$NAMESPACE}_user.user ADD key (realName);
diff --git a/resources/sql/patches/018.owners.sql b/resources/sql/patches/018.owners.sql
index c5fe3b9251..c30eb010c5 100644
--- a/resources/sql/patches/018.owners.sql
+++ b/resources/sql/patches/018.owners.sql
@@ -1,27 +1,25 @@
-
-
CREATE TABLE {$NAMESPACE}_owners.owners_package (
id int unsigned not null auto_increment primary key,
phid varchar(64) binary not null,
unique key(phid),
name varchar(255) not null,
unique key(name),
description text not null,
primaryOwnerPHID varchar(64) binary
);
CREATE TABLE {$NAMESPACE}_owners.owners_owner (
id int unsigned not null auto_increment primary key,
packageID int unsigned not null,
userPHID varchar(64) binary not null,
UNIQUE KEY(packageID, userPHID),
KEY(userPHID)
);
CREATE TABLE {$NAMESPACE}_owners.owners_path (
id int unsigned not null auto_increment primary key,
packageID int unsigned not null,
key(packageID),
repositoryPHID varchar(64) binary not null,
path varchar(255) not null
);
diff --git a/resources/sql/patches/020.pathcapital.sql b/resources/sql/patches/020.pathcapital.sql
index 7c825ca5fb..fd4ffd6672 100644
--- a/resources/sql/patches/020.pathcapital.sql
+++ b/resources/sql/patches/020.pathcapital.sql
@@ -1,2 +1,2 @@
ALTER TABLE {$NAMESPACE}_differential.differential_diff
- CHANGE sourceControlpath sourceControlPath varchar(255);
\ No newline at end of file
+ CHANGE sourceControlpath sourceControlPath varchar(255);
diff --git a/resources/sql/patches/021.xhpastview.sql b/resources/sql/patches/021.xhpastview.sql
index bb517ed949..875fe0636d 100644
--- a/resources/sql/patches/021.xhpastview.sql
+++ b/resources/sql/patches/021.xhpastview.sql
@@ -1,9 +1,8 @@
-
CREATE TABLE {$NAMESPACE}_xhpastview.xhpastview_parsetree (
id int unsigned not null auto_increment primary key,
authorPHID varchar(64) binary,
input longblob not null,
stdout longblob not null,
dateCreated int unsigned not null,
dateModified int unsigned not null
);
diff --git a/resources/sql/patches/024.mlistkeys.sql b/resources/sql/patches/024.mlistkeys.sql
index 4ccf515d35..1063584dd5 100644
--- a/resources/sql/patches/024.mlistkeys.sql
+++ b/resources/sql/patches/024.mlistkeys.sql
@@ -1,6 +1,5 @@
ALTER TABLE {$NAMESPACE}_metamta.metamta_mailinglist
ADD UNIQUE KEY (email);
ALTER TABLE {$NAMESPACE}_metamta.metamta_mailinglist
ADD UNIQUE KEY (name);
-
diff --git a/resources/sql/patches/025.commentopt.sql b/resources/sql/patches/025.commentopt.sql
index 2c0ee9cd76..0892515aa3 100644
--- a/resources/sql/patches/025.commentopt.sql
+++ b/resources/sql/patches/025.commentopt.sql
@@ -1,2 +1,2 @@
ALTER TABLE {$NAMESPACE}_differential.differential_inlinecomment
- ADD KEY (revisionID, authorPHID);
\ No newline at end of file
+ ADD KEY (revisionID, authorPHID);
diff --git a/resources/sql/patches/026.diffpropkey.sql b/resources/sql/patches/026.diffpropkey.sql
index c4d3fb7ce5..40bb69aa94 100644
--- a/resources/sql/patches/026.diffpropkey.sql
+++ b/resources/sql/patches/026.diffpropkey.sql
@@ -1,3 +1,2 @@
ALTER TABLE {$NAMESPACE}_differential.differential_diffproperty
ADD UNIQUE KEY (diffID, name);
-
diff --git a/resources/sql/patches/028.systemagent.sql b/resources/sql/patches/028.systemagent.sql
index bce57aa443..e8e23618cc 100644
--- a/resources/sql/patches/028.systemagent.sql
+++ b/resources/sql/patches/028.systemagent.sql
@@ -1,2 +1,2 @@
ALTER TABLE {$NAMESPACE}_user.user
- ADD isSystemAgent bool not null default 0;
\ No newline at end of file
+ ADD isSystemAgent bool not null default 0;
diff --git a/resources/sql/patches/034.savedheader.sql b/resources/sql/patches/034.savedheader.sql
index d7425a5c3d..cb626f20f2 100644
--- a/resources/sql/patches/034.savedheader.sql
+++ b/resources/sql/patches/034.savedheader.sql
@@ -1,4 +1,4 @@
CREATE TABLE {$NAMESPACE}_herald.herald_savedheader (
phid varchar(64) binary not null primary key,
header varchar(255) not null
-) ENGINE=InnoDB;
\ No newline at end of file
+) ENGINE=InnoDB;
diff --git a/resources/sql/patches/036.mailkey.sql b/resources/sql/patches/036.mailkey.sql
index 6edf512a71..06e05ec64a 100644
--- a/resources/sql/patches/036.mailkey.sql
+++ b/resources/sql/patches/036.mailkey.sql
@@ -1,19 +1,19 @@
ALTER TABLE {$NAMESPACE}_differential.differential_revision
ADD mailKey VARCHAR(40) binary NOT NULL;
ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task
ADD mailKey VARCHAR(40) binary NOT NULL;
CREATE TABLE {$NAMESPACE}_metamta.metamta_receivedmail (
id int unsigned not null primary key auto_increment,
headers longblob not null,
bodies longblob not null,
attachments longblob not null,
relatedPHID varchar(64) binary,
key(relatedPHID),
authorPHID varchar(64) binary,
key(authorPHID),
message longblob,
dateCreated int unsigned not null,
dateModified int unsigned not null
-) engine=innodb;
\ No newline at end of file
+) engine=innodb;
diff --git a/resources/sql/patches/043.pastebin.sql b/resources/sql/patches/043.pastebin.sql
index 5e62159474..7d4de868cb 100644
--- a/resources/sql/patches/043.pastebin.sql
+++ b/resources/sql/patches/043.pastebin.sql
@@ -1,11 +1,9 @@
-
-
CREATE TABLE {$NAMESPACE}_pastebin.pastebin_paste (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
phid VARCHAR(64) BINARY NOT NULL,
authorPHID VARCHAR(64) BINARY NOT NULL,
filePHID VARCHAR(64) BINARY NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL
);
diff --git a/resources/sql/patches/044.countdown.sql b/resources/sql/patches/044.countdown.sql
index 540ab236d0..e25bb876b2 100644
--- a/resources/sql/patches/044.countdown.sql
+++ b/resources/sql/patches/044.countdown.sql
@@ -1,10 +1,8 @@
-
-
CREATE TABLE {$NAMESPACE}_countdown.countdown_timer (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
authorPHID VARCHAR(64) BINARY NOT NULL,
datepoint INT UNSIGNED NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL
);
diff --git a/resources/sql/patches/047.projectstatus.sql b/resources/sql/patches/047.projectstatus.sql
index 56a19a4a98..b0e563cf77 100644
--- a/resources/sql/patches/047.projectstatus.sql
+++ b/resources/sql/patches/047.projectstatus.sql
@@ -1,2 +1,2 @@
ALTER TABLE {$NAMESPACE}_project.project
- ADD status varchar(32) not null;
\ No newline at end of file
+ ADD status varchar(32) not null;
diff --git a/resources/sql/patches/049.projectowner.sql b/resources/sql/patches/049.projectowner.sql
index 98bf55bf57..894166b13a 100644
--- a/resources/sql/patches/049.projectowner.sql
+++ b/resources/sql/patches/049.projectowner.sql
@@ -1,2 +1,2 @@
ALTER TABLE {$NAMESPACE}_project.project_affiliation
- ADD isOwner bool NOT NULL;
\ No newline at end of file
+ ADD isOwner bool NOT NULL;
diff --git a/resources/sql/patches/052.pastelanguage.sql b/resources/sql/patches/052.pastelanguage.sql
index 1bfdac0b1f..b517f2dd80 100644
--- a/resources/sql/patches/052.pastelanguage.sql
+++ b/resources/sql/patches/052.pastelanguage.sql
@@ -1,2 +1,2 @@
ALTER TABLE {$NAMESPACE}_pastebin.pastebin_paste
- ADD COLUMN language VARCHAR(64) NOT NULL;
\ No newline at end of file
+ ADD COLUMN language VARCHAR(64) NOT NULL;
diff --git a/resources/sql/patches/053.feed.sql b/resources/sql/patches/053.feed.sql
index 96ae6ed0f7..46ad0b4042 100644
--- a/resources/sql/patches/053.feed.sql
+++ b/resources/sql/patches/053.feed.sql
@@ -1,21 +1,19 @@
-
-
CREATE TABLE {$NAMESPACE}_feed.feed_storydata (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARCHAR(64) BINARY NOT NULL,
UNIQUE KEY (phid),
chronologicalKey BIGINT UNSIGNED NOT NULL,
UNIQUE KEY (chronologicalKey),
storyType varchar(64) NOT NULL,
storyData LONGBLOB NOT NULL,
authorPHID varchar(64) BINARY NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL
);
CREATE TABLE {$NAMESPACE}_feed.feed_storyreference (
objectPHID varchar(64) BINARY NOT NULL,
chronologicalKey BIGINT UNSIGNED NOT NULL,
UNIQUE KEY (objectPHID, chronologicalKey),
KEY (chronologicalKey)
);
diff --git a/resources/sql/patches/055.add_author_to_files.sql b/resources/sql/patches/055.add_author_to_files.sql
index b43a1c01d9..5c0f541c57 100644
--- a/resources/sql/patches/055.add_author_to_files.sql
+++ b/resources/sql/patches/055.add_author_to_files.sql
@@ -1,3 +1,3 @@
ALTER TABLE {$NAMESPACE}_file.file
ADD COLUMN authorPHID VARCHAR(64) BINARY,
- ADD KEY (authorPHID);
\ No newline at end of file
+ ADD KEY (authorPHID);
diff --git a/resources/sql/patches/056.slowvote.sql b/resources/sql/patches/056.slowvote.sql
index 8a2912bd47..1d9064118d 100644
--- a/resources/sql/patches/056.slowvote.sql
+++ b/resources/sql/patches/056.slowvote.sql
@@ -1,44 +1,42 @@
-
-
CREATE TABLE {$NAMESPACE}_slowvote.slowvote_poll (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
question VARCHAR(255) NOT NULL,
phid VARCHAR(64) BINARY NOT NULL,
UNIQUE KEY (phid),
authorPHID VARCHAR(64) BINARY NOT NULL,
responseVisibility INT UNSIGNED NOT NULL,
shuffle INT UNSIGNED NOT NULL,
method INT UNSIGNED NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL
);
CREATE TABLE {$NAMESPACE}_slowvote.slowvote_option (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
pollID INT UNSIGNED NOT NULL,
KEY (pollID),
name VARCHAR(255) NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL
);
CREATE TABLE {$NAMESPACE}_slowvote.slowvote_comment (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
pollID INT UNSIGNED NOT NULL,
UNIQUE KEY (pollID, authorPHID),
authorPHID VARCHAR(64) BINARY NOT NULL,
commentText LONGBLOB NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL
);
CREATE TABLE {$NAMESPACE}_slowvote.slowvote_choice (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
pollID INT UNSIGNED NOT NULL,
KEY (pollID),
optionID INT UNSIGNED NOT NULL,
authorPHID VARCHAR(64) BINARY NOT NULL,
KEY (authorPHID),
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL
-);
\ No newline at end of file
+);
diff --git a/resources/sql/patches/057.parsecache.sql b/resources/sql/patches/057.parsecache.sql
index 796fa4592d..5254e3e6e1 100644
--- a/resources/sql/patches/057.parsecache.sql
+++ b/resources/sql/patches/057.parsecache.sql
@@ -1,7 +1,7 @@
TRUNCATE {$NAMESPACE}_differential.differential_changeset_parse_cache;
ALTER TABLE {$NAMESPACE}_differential.differential_changeset_parse_cache
ADD dateCreated INT UNSIGNED NOT NULL;
ALTER TABLE {$NAMESPACE}_differential.differential_changeset_parse_cache
- ADD KEY (dateCreated);
\ No newline at end of file
+ ADD KEY (dateCreated);
diff --git a/resources/sql/patches/058.missingkeys.sql b/resources/sql/patches/058.missingkeys.sql
index 51d2e10aa2..dbb7f8404d 100644
--- a/resources/sql/patches/058.missingkeys.sql
+++ b/resources/sql/patches/058.missingkeys.sql
@@ -1,11 +1,11 @@
ALTER TABLE {$NAMESPACE}_file.file
ADD UNIQUE KEY (phid);
ALTER TABLE {$NAMESPACE}_project.project
ADD UNIQUE KEY (phid);
ALTER TABLE {$NAMESPACE}_herald.herald_condition
ADD KEY (ruleID);
ALTER TABLE {$NAMESPACE}_herald.herald_action
- ADD KEY (ruleID);
\ No newline at end of file
+ ADD KEY (ruleID);
diff --git a/resources/sql/patches/060.phriction.sql b/resources/sql/patches/060.phriction.sql
index 97a330575c..1e352caea9 100644
--- a/resources/sql/patches/060.phriction.sql
+++ b/resources/sql/patches/060.phriction.sql
@@ -1,12 +1,10 @@
-
-
CREATE TABLE {$NAMESPACE}_phriction.phriction_document (
id INT UNSIGNED NOT NULL,
phid VARCHAR(64) BINARY NOT NULL,
UNIQUE KEY (phid),
slug VARCHAR(128) NOT NULL,
UNIQUE KEY (slug),
depth INT UNSIGNED NOT NULL,
UNIQUE KEY (depth, slug),
contentID INT UNSIGNED NOT NULL
) ENGINE=InnoDB;
diff --git a/resources/sql/patches/061.phrictioncontent.sql b/resources/sql/patches/061.phrictioncontent.sql
index 958bba3f86..2209068926 100644
--- a/resources/sql/patches/061.phrictioncontent.sql
+++ b/resources/sql/patches/061.phrictioncontent.sql
@@ -1,22 +1,22 @@
/* Patch 060 neglected to make this an AUTO_INCREMENT PRIMARY KEY */
ALTER TABLE {$NAMESPACE}_phriction.phriction_document
CHANGE id id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY;
/* Needs to be initially nullable for insert when documents are created. */
ALTER TABLE {$NAMESPACE}_phriction.phriction_document
CHANGE contentID contentID INT UNSIGNED;
CREATE TABLE {$NAMESPACE}_phriction.phriction_content (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
documentID INT UNSIGNED NOT NULL,
version INT UNSIGNED NOT NULL,
UNIQUE KEY (documentID, version),
authorPHID VARCHAR(64) BINARY NOT NULL,
KEY (authorPHID),
title VARCHAR(512) NOT NULL,
slug VARCHAR(512) NOT NULL,
KEY (slug),
content LONGBLOB NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL
-) ENGINE=InnoDB;
\ No newline at end of file
+) ENGINE=InnoDB;
diff --git a/resources/sql/patches/062.phrictionmenu.sql b/resources/sql/patches/062.phrictionmenu.sql
index 9ce4021be1..bcc80bf648 100644
--- a/resources/sql/patches/062.phrictionmenu.sql
+++ b/resources/sql/patches/062.phrictionmenu.sql
@@ -1,3 +1,3 @@
/* Older versions incorrectly computed the depth for the root page. */
UPDATE {$NAMESPACE}_phriction.phriction_document
- SET depth = 0 where slug = '/';
\ No newline at end of file
+ SET depth = 0 where slug = '/';
diff --git a/resources/sql/patches/063.pasteforks.sql b/resources/sql/patches/063.pasteforks.sql
index 978b2c16e4..63a5ef70b5 100644
--- a/resources/sql/patches/063.pasteforks.sql
+++ b/resources/sql/patches/063.pasteforks.sql
@@ -1,3 +1,3 @@
ALTER TABLE {$NAMESPACE}_pastebin.pastebin_paste
ADD COLUMN parentPHID VARCHAR(64) BINARY,
- ADD KEY (parentPHID);
\ No newline at end of file
+ ADD KEY (parentPHID);
diff --git a/resources/sql/patches/064.subprojects.sql b/resources/sql/patches/064.subprojects.sql
index 0baebc8ae8..688e8baaac 100644
--- a/resources/sql/patches/064.subprojects.sql
+++ b/resources/sql/patches/064.subprojects.sql
@@ -1,11 +1,11 @@
ALTER TABLE {$NAMESPACE}_project.project
ADD subprojectPHIDs longblob NOT NULL;
UPDATE {$NAMESPACE}_project.project
SET subprojectPHIDs = '[]';
CREATE TABLE {$NAMESPACE}_project.project_subproject (
projectPHID varchar(64) BINARY NOT NULL,
subprojectPHID varchar(64) BINARY NOT NULL,
PRIMARY KEY (subprojectPHID, projectPHID),
UNIQUE KEY (projectPHID, subprojectPHID)
-) ENGINE=InnoDB;
\ No newline at end of file
+) ENGINE=InnoDB;
diff --git a/resources/sql/patches/065.sshkeys.sql b/resources/sql/patches/065.sshkeys.sql
index 0fb7e1d33b..766e4affba 100644
--- a/resources/sql/patches/065.sshkeys.sql
+++ b/resources/sql/patches/065.sshkeys.sql
@@ -1,12 +1,12 @@
CREATE TABLE {$NAMESPACE}_user.user_sshkey (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
userPHID varchar(64) BINARY NOT NULL,
key (userPHID),
name varchar(255),
keyType varchar(255),
keyBody varchar(32768) BINARY,
unique key (keyBody(128)),
keyComment varchar(255),
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL
-) ENGINE=InnoDB;
\ No newline at end of file
+) ENGINE=InnoDB;
diff --git a/resources/sql/patches/067.preferences.sql b/resources/sql/patches/067.preferences.sql
index 4623688e52..9db23c5475 100644
--- a/resources/sql/patches/067.preferences.sql
+++ b/resources/sql/patches/067.preferences.sql
@@ -1 +1 @@
-/* This used to be a "directory" update. */;
\ No newline at end of file
+/* This used to be a "directory" update. */;
diff --git a/resources/sql/patches/068.maniphestauxiliarystorage.sql b/resources/sql/patches/068.maniphestauxiliarystorage.sql
index 429d0dac97..bd7576b186 100644
--- a/resources/sql/patches/068.maniphestauxiliarystorage.sql
+++ b/resources/sql/patches/068.maniphestauxiliarystorage.sql
@@ -1,9 +1,9 @@
-create table {$NAMESPACE}_maniphest.maniphest_taskauxiliarystorage
+create table {$NAMESPACE}_maniphest.maniphest_taskauxiliarystorage
(id int unsigned not null auto_increment primary key,
- taskPHID varchar(64) binary not null,
- name varchar(255) not null,
- value varchar(255) not null,
+ taskPHID varchar(64) binary not null,
+ name varchar(255) not null,
+ value varchar(255) not null,
unique key (taskPHID,name),
dateCreated int unsigned not null,
dateModified int unsigned not null)
- ENGINE = InnoDB;
\ No newline at end of file
+ ENGINE = InnoDB;
diff --git a/resources/sql/patches/076.indexedlanguages.sql b/resources/sql/patches/076.indexedlanguages.sql
index 1d3069f1d9..6ff83f73ad 100644
--- a/resources/sql/patches/076.indexedlanguages.sql
+++ b/resources/sql/patches/076.indexedlanguages.sql
@@ -1,4 +1,4 @@
ALTER TABLE {$NAMESPACE}_repository.repository_arcanistproject
ADD symbolIndexLanguages LONGBLOB NOT NULL;
ALTER TABLE {$NAMESPACE}_repository.repository_arcanistproject
- ADD symbolIndexProjects LONGBLOB NOT NULL;
\ No newline at end of file
+ ADD symbolIndexProjects LONGBLOB NOT NULL;
diff --git a/resources/sql/patches/080.filekeys.sql b/resources/sql/patches/080.filekeys.sql
index 2d8a35a02f..31e559012a 100644
--- a/resources/sql/patches/080.filekeys.sql
+++ b/resources/sql/patches/080.filekeys.sql
@@ -1,2 +1,2 @@
ALTER TABLE {$NAMESPACE}_file.file
- ADD secretKey VARCHAR(20) BINARY;
\ No newline at end of file
+ ADD secretKey VARCHAR(20) BINARY;
diff --git a/resources/sql/patches/086.formeraffil.sql b/resources/sql/patches/086.formeraffil.sql
index 2394a10aa9..b2f44a5b65 100644
--- a/resources/sql/patches/086.formeraffil.sql
+++ b/resources/sql/patches/086.formeraffil.sql
@@ -1 +1 @@
-ALTER TABLE {$NAMESPACE}_project.project_affiliation DROP status;
\ No newline at end of file
+ALTER TABLE {$NAMESPACE}_project.project_affiliation DROP status;
diff --git a/resources/sql/patches/087.phrictiondelete.sql b/resources/sql/patches/087.phrictiondelete.sql
index b07c5358ff..384960bc87 100644
--- a/resources/sql/patches/087.phrictiondelete.sql
+++ b/resources/sql/patches/087.phrictiondelete.sql
@@ -1,8 +1,8 @@
ALTER TABLE {$NAMESPACE}_phriction.phriction_document
ADD status INT UNSIGNED NOT NULL DEFAULT 0;
ALTER TABLE {$NAMESPACE}_phriction.phriction_content
ADD changeType INT UNSIGNED NOT NULL DEFAULT 0;
ALTER TABLE {$NAMESPACE}_phriction.phriction_content
- ADD changeRef INT UNSIGNED DEFAULT NULL;
\ No newline at end of file
+ ADD changeRef INT UNSIGNED DEFAULT NULL;
diff --git a/resources/sql/patches/088.audit.sql b/resources/sql/patches/088.audit.sql
index ea93e1c928..63ace7f88c 100644
--- a/resources/sql/patches/088.audit.sql
+++ b/resources/sql/patches/088.audit.sql
@@ -1,23 +1,21 @@
-
-
ALTER TABLE {$NAMESPACE}_owners.owners_packagecommitrelationship
ADD COLUMN `auditStatus` varchar(64) NOT NULL,
ADD COLUMN `auditReasons` longtext NOT NULL,
DROP KEY `packagePHID`,
ADD KEY `packagePHID` (`packagePHID`, `auditStatus`, `id`);
CREATE TABLE IF NOT EXISTs {$NAMESPACE}_audit.audit_comment (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`targetPHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`actorPHID` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`action` varchar(64) NOT NULL,
`content` longtext NOT NULL,
PRIMARY KEY `id` (`id`),
KEY `targetPHID` (`targetPHID`, `actorPHID`, `id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE {$NAMESPACE}_owners.owners_package
ADD COLUMN `auditingEnabled` tinyint(1) NOT NULL DEFAULT 0;
diff --git a/resources/sql/patches/092.dropgithubnotification.sql b/resources/sql/patches/092.dropgithubnotification.sql
index 9fcfa76629..95a24fa6b8 100644
--- a/resources/sql/patches/092.dropgithubnotification.sql
+++ b/resources/sql/patches/092.dropgithubnotification.sql
@@ -1 +1 @@
-DROP TABLE {$NAMESPACE}_repository.repository_githubnotification;
\ No newline at end of file
+DROP TABLE {$NAMESPACE}_repository.repository_githubnotification;
diff --git a/resources/sql/patches/095.directory.sql b/resources/sql/patches/095.directory.sql
index 4623688e52..9db23c5475 100644
--- a/resources/sql/patches/095.directory.sql
+++ b/resources/sql/patches/095.directory.sql
@@ -1 +1 @@
-/* This used to be a "directory" update. */;
\ No newline at end of file
+/* This used to be a "directory" update. */;
diff --git a/resources/sql/patches/097.heraldruletypes.sql b/resources/sql/patches/097.heraldruletypes.sql
index b975403737..837359f490 100644
--- a/resources/sql/patches/097.heraldruletypes.sql
+++ b/resources/sql/patches/097.heraldruletypes.sql
@@ -1,2 +1,2 @@
ALTER TABLE {$NAMESPACE}_herald.herald_rule ADD ruleType varchar(255) not null DEFAULT 'global';
-CREATE INDEX IDX_RULE_TYPE on {$NAMESPACE}_herald.herald_rule (ruleType);
\ No newline at end of file
+CREATE INDEX IDX_RULE_TYPE on {$NAMESPACE}_herald.herald_rule (ruleType);
diff --git a/resources/sql/patches/099.drydock.sql b/resources/sql/patches/099.drydock.sql
index 87187f8bf7..74ae726a25 100644
--- a/resources/sql/patches/099.drydock.sql
+++ b/resources/sql/patches/099.drydock.sql
@@ -1,29 +1,27 @@
-
-
CREATE TABLE {$NAMESPACE}_drydock.drydock_resource (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARCHAR(64) BINARY NOT NULL,
name VARCHAR(255) NOT NULL,
ownerPHID varchar(64) BINARY,
status INT UNSIGNED NOT NULL,
blueprintClass VARCHAR(255) NOT NULL,
type VARCHAR(64) NOT NULL,
attributes LONGBLOB NOT NULL,
capabilities LONGBLOB NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY (phid)
) ENGINE=InnoDB;
CREATE TABLE {$NAMESPACE}_drydock.drydock_lease (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARCHAR(64) BINARY NOT NULL,
resourceID INT UNSIGNED,
status INT UNSIGNED NOT NULL,
until INT UNSIGNED,
ownerPHID VARCHAR(64) BINARY,
attributes LONGBLOB NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY (phid)
) ENGINE=InnoDB;
diff --git a/resources/sql/patches/106.chatlog.sql b/resources/sql/patches/106.chatlog.sql
index 419420f410..bbb9be945e 100644
--- a/resources/sql/patches/106.chatlog.sql
+++ b/resources/sql/patches/106.chatlog.sql
@@ -1,11 +1,10 @@
-
CREATE TABLE {$NAMESPACE}_chatlog.chatlog_event (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
channel VARCHAR(64) BINARY NOT NULL,
epoch INT UNSIGNED NOT NULL,
author VARCHAR(64) BINARY NOT NULL,
type VARCHAR(4) NOT NULL,
message LONGBLOB NOT NULL,
loggedByPHID VARCHAR(64) BINARY NOT NULL,
KEY (channel, epoch)
);
diff --git a/resources/sql/patches/107.oauthserver.sql b/resources/sql/patches/107.oauthserver.sql
index c2f1ae92cc..2865535af1 100644
--- a/resources/sql/patches/107.oauthserver.sql
+++ b/resources/sql/patches/107.oauthserver.sql
@@ -1,51 +1,48 @@
-
-
CREATE TABLE `{$NAMESPACE}_oauth_server`.`oauth_server_oauthserverclient` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varchar(64) BINARY NOT NULL,
`name` varchar(255) NOT NULL,
`secret` varchar(32) NOT NULL,
`redirectURI` varchar(255) NOT NULL,
`creatorPHID` varchar(64) BINARY NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `phid` (`phid`)
) ENGINE=InnoDB;
CREATE TABLE `{$NAMESPACE}_oauth_server`.`oauth_server_oauthclientauthorization` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varchar(64) BINARY NOT NULL,
`userPHID` varchar(64) BINARY NOT NULL,
`clientPHID` varchar(64) BINARY NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `phid` (`phid`),
UNIQUE KEY `userPHID` (`userPHID`,`clientPHID`)
) ENGINE=InnoDB;
CREATE TABLE `{$NAMESPACE}_oauth_server`.`oauth_server_oauthserverauthorizationcode` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`code` varchar(32) NOT NULL,
`clientPHID` varchar(64) BINARY NOT NULL,
`clientSecret` varchar(32) NOT NULL,
`userPHID` varchar(64) BINARY NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `code` (`code`)
) ENGINE=InnoDB;
CREATE TABLE `{$NAMESPACE}_oauth_server`.`oauth_server_oauthserveraccesstoken` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`token` varchar(32) NOT NULL,
`userPHID` varchar(64) BINARY NOT NULL,
`clientPHID` varchar(64) BINARY NOT NULL,
`dateExpires` int(10) unsigned NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `token` (`token`)
) ENGINE=InnoDB;
-
diff --git a/resources/sql/patches/108.oauthscope.sql b/resources/sql/patches/108.oauthscope.sql
index f17a296956..e7d8155572 100644
--- a/resources/sql/patches/108.oauthscope.sql
+++ b/resources/sql/patches/108.oauthscope.sql
@@ -1,6 +1,5 @@
ALTER TABLE `{$NAMESPACE}_oauth_server`.`oauth_server_oauthclientauthorization`
ADD `scope` text NOT NULL;
ALTER TABLE `{$NAMESPACE}_oauth_server`.`oauth_server_oauthserveraccesstoken`
DROP `dateExpires`;
-
diff --git a/resources/sql/patches/109.oauthclientphidkey.sql b/resources/sql/patches/109.oauthclientphidkey.sql
index 5570043612..90ea7f4288 100644
--- a/resources/sql/patches/109.oauthclientphidkey.sql
+++ b/resources/sql/patches/109.oauthclientphidkey.sql
@@ -1,3 +1,2 @@
ALTER TABLE `{$NAMESPACE}_oauth_server`.`oauth_server_oauthserverclient`
ADD KEY `creatorPHID` (`creatorPHID`)
-
diff --git a/resources/sql/patches/110.commitaudit.sql b/resources/sql/patches/110.commitaudit.sql
index 4fffa055a8..9c051ee1ca 100644
--- a/resources/sql/patches/110.commitaudit.sql
+++ b/resources/sql/patches/110.commitaudit.sql
@@ -1,11 +1,11 @@
ALTER TABLE {$NAMESPACE}_repository.repository_commit
ADD mailKey VARCHAR(20) NOT NULL;
ALTER TABLE {$NAMESPACE}_repository.repository_commit
ADD authorPHID VARCHAR(64) BINARY;
ALTER TABLE {$NAMESPACE}_repository.repository_commit
ADD auditStatus INT UNSIGNED NOT NULL;
ALTER TABLE {$NAMESPACE}_repository.repository_commit
- ADD KEY (authorPHID, auditStatus, epoch);
\ No newline at end of file
+ ADD KEY (authorPHID, auditStatus, epoch);
diff --git a/resources/sql/patches/112.oauthaccesscoderedirecturi.sql b/resources/sql/patches/112.oauthaccesscoderedirecturi.sql
index 6131d63aca..56f258453e 100644
--- a/resources/sql/patches/112.oauthaccesscoderedirecturi.sql
+++ b/resources/sql/patches/112.oauthaccesscoderedirecturi.sql
@@ -1,3 +1,2 @@
ALTER TABLE `{$NAMESPACE}_oauth_server`.`oauth_server_oauthserverauthorizationcode`
ADD `redirectURI` varchar(255) NOT NULL
-
diff --git a/resources/sql/patches/116.utf8-backup-first-expect-wait.sql b/resources/sql/patches/116.utf8-backup-first-expect-wait.sql
index 7d463b6dc4..eda5573641 100644
--- a/resources/sql/patches/116.utf8-backup-first-expect-wait.sql
+++ b/resources/sql/patches/116.utf8-backup-first-expect-wait.sql
@@ -1,1128 +1,1125 @@
ALTER DATABASE `{$NAMESPACE}_audit` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_audit`.`audit_comment`
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `targetPHID` varchar(64) CHARACTER SET binary,
MODIFY `actorPHID` varchar(64) CHARACTER SET binary,
MODIFY `action` varchar(64) CHARACTER SET binary,
MODIFY `content` longtext CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_audit`.`audit_comment`
COLLATE utf8_general_ci,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `targetPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `actorPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `action` varchar(64) COLLATE utf8_general_ci NOT NULL,
MODIFY `content` longtext COLLATE utf8_general_ci NOT NULL;
ALTER DATABASE `{$NAMESPACE}_chatlog` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_chatlog`.`chatlog_event`
MODIFY `channel` varchar(64) CHARACTER SET binary,
MODIFY `author` varchar(64) CHARACTER SET binary,
MODIFY `type` varchar(4) CHARACTER SET binary,
MODIFY `message` longtext CHARACTER SET binary,
MODIFY `loggedByPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_chatlog`.`chatlog_event`
COLLATE utf8_general_ci,
MODIFY `channel` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `author` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `type` varchar(4) COLLATE utf8_general_ci NOT NULL,
MODIFY `message` longtext COLLATE utf8_bin NOT NULL,
MODIFY `loggedByPHID` varchar(64) COLLATE utf8_bin NOT NULL;
ALTER DATABASE `{$NAMESPACE}_conduit` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_conduit`.`conduit_certificatetoken`
MODIFY `userPHID` varchar(64) CHARACTER SET binary,
MODIFY `token` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_conduit`.`conduit_certificatetoken`
COLLATE utf8_general_ci,
MODIFY `userPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `token` varchar(64) COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_conduit`.`conduit_connectionlog`
MODIFY `client` varchar(255) CHARACTER SET binary,
MODIFY `clientVersion` varchar(255) CHARACTER SET binary,
MODIFY `clientDescription` varchar(255) CHARACTER SET binary,
MODIFY `username` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_conduit`.`conduit_connectionlog`
COLLATE utf8_general_ci,
MODIFY `client` varchar(255) COLLATE utf8_general_ci,
MODIFY `clientVersion` varchar(255) COLLATE utf8_general_ci,
MODIFY `clientDescription` varchar(255) COLLATE utf8_general_ci,
MODIFY `username` varchar(255) COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_conduit`.`conduit_methodcalllog`
MODIFY `method` varchar(255) CHARACTER SET binary,
MODIFY `error` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_conduit`.`conduit_methodcalllog`
COLLATE utf8_general_ci,
MODIFY `method` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `error` varchar(255) COLLATE utf8_general_ci NOT NULL;
ALTER DATABASE `{$NAMESPACE}_countdown` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_countdown`.`countdown_timer`
MODIFY `title` varchar(255) CHARACTER SET binary,
MODIFY `authorPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_countdown`.`countdown_timer`
COLLATE utf8_general_ci,
MODIFY `title` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `authorPHID` varchar(64) COLLATE utf8_bin NOT NULL;
ALTER DATABASE `{$NAMESPACE}_daemon` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_daemon`.`daemon_log`
MODIFY `daemon` varchar(255) CHARACTER SET binary,
MODIFY `host` varchar(255) CHARACTER SET binary,
MODIFY `argv` varchar(512) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_daemon`.`daemon_log`
COLLATE utf8_general_ci,
MODIFY `daemon` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `host` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `argv` varchar(512) COLLATE utf8_general_ci NOT NULL;
ALTER TABLE `{$NAMESPACE}_daemon`.`daemon_logevent`
MODIFY `logType` varchar(4) CHARACTER SET binary,
MODIFY `message` longtext CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_daemon`.`daemon_logevent`
COLLATE utf8_general_ci,
MODIFY `logType` varchar(4) COLLATE utf8_general_ci NOT NULL,
MODIFY `message` longtext COLLATE utf8_bin NOT NULL;
ALTER DATABASE `{$NAMESPACE}_differential` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_affectedpath`
COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_auxiliaryfield`
MODIFY `revisionPHID` varchar(64) CHARACTER SET binary,
MODIFY `name` varchar(32) CHARACTER SET binary,
MODIFY `value` longtext CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_auxiliaryfield`
COLLATE utf8_general_ci,
MODIFY `revisionPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `name` varchar(32) COLLATE utf8_bin NOT NULL,
MODIFY `value` longtext COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_changeset`
MODIFY `oldFile` varchar(255) CHARACTER SET binary,
MODIFY `filename` varchar(255) CHARACTER SET binary,
MODIFY `awayPaths` longtext CHARACTER SET binary,
MODIFY `metadata` longtext CHARACTER SET binary,
MODIFY `oldProperties` longtext CHARACTER SET binary,
MODIFY `newProperties` longtext CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_changeset`
COLLATE utf8_general_ci,
MODIFY `oldFile` varchar(255) COLLATE utf8_general_ci,
MODIFY `filename` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `awayPaths` longtext COLLATE utf8_bin,
MODIFY `metadata` longtext COLLATE utf8_bin,
MODIFY `oldProperties` longtext COLLATE utf8_bin,
MODIFY `newProperties` longtext COLLATE utf8_bin;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_changeset_parse_cache`
MODIFY `cache` longtext CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_changeset_parse_cache`
COLLATE utf8_general_ci,
MODIFY `cache` longtext COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_comment`
MODIFY `authorPHID` varchar(64) CHARACTER SET binary,
MODIFY `action` varchar(64) CHARACTER SET binary,
MODIFY `content` longtext CHARACTER SET binary,
MODIFY `cache` longtext CHARACTER SET binary,
MODIFY `metadata` longtext CHARACTER SET binary,
MODIFY `contentSource` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_comment`
COLLATE utf8_general_ci,
MODIFY `authorPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `action` varchar(64) COLLATE utf8_general_ci NOT NULL,
MODIFY `content` longtext COLLATE utf8_bin NOT NULL,
MODIFY `cache` longtext COLLATE utf8_bin,
MODIFY `metadata` longtext COLLATE utf8_bin NOT NULL,
MODIFY `contentSource` varchar(255) COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_commit`
MODIFY `commitPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_commit`
COLLATE utf8_general_ci,
MODIFY `commitPHID` varchar(64) COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_diff`
MODIFY `authorPHID` varchar(64) CHARACTER SET binary,
MODIFY `sourceMachine` varchar(255) CHARACTER SET binary,
MODIFY `sourcePath` varchar(255) CHARACTER SET binary,
MODIFY `sourceControlSystem` varchar(64) CHARACTER SET binary,
MODIFY `sourceControlBaseRevision` varchar(255) CHARACTER SET binary,
MODIFY `sourceControlPath` varchar(255) CHARACTER SET binary,
MODIFY `branch` varchar(255) CHARACTER SET binary,
MODIFY `arcanistProjectPHID` varchar(64) CHARACTER SET binary,
MODIFY `creationMethod` varchar(255) CHARACTER SET binary,
MODIFY `description` varchar(255) CHARACTER SET binary,
MODIFY `repositoryUUID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_diff`
COLLATE utf8_general_ci,
MODIFY `authorPHID` varchar(64) COLLATE utf8_bin,
MODIFY `sourceMachine` varchar(255) COLLATE utf8_general_ci,
MODIFY `sourcePath` varchar(255) COLLATE utf8_general_ci,
MODIFY `sourceControlSystem` varchar(64) COLLATE utf8_general_ci,
MODIFY `sourceControlBaseRevision` varchar(255) COLLATE utf8_general_ci,
MODIFY `sourceControlPath` varchar(255) COLLATE utf8_general_ci,
MODIFY `branch` varchar(255) COLLATE utf8_general_ci,
MODIFY `arcanistProjectPHID` varchar(64) COLLATE utf8_bin,
MODIFY `creationMethod` varchar(255) COLLATE utf8_general_ci,
MODIFY `description` varchar(255) COLLATE utf8_general_ci,
MODIFY `repositoryUUID` varchar(64) COLLATE utf8_bin;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_diffproperty`
MODIFY `name` varchar(255) CHARACTER SET binary,
MODIFY `data` longtext CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_diffproperty`
COLLATE utf8_general_ci,
MODIFY `name` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `data` longtext COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_hunk`
MODIFY `changes` longtext CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_hunk`
COLLATE utf8_general_ci,
MODIFY `changes` longtext COLLATE utf8_bin;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_inlinecomment`
MODIFY `authorPHID` varchar(64) CHARACTER SET binary,
MODIFY `content` longtext CHARACTER SET binary,
MODIFY `cache` longtext CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_inlinecomment`
COLLATE utf8_general_ci,
MODIFY `authorPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `content` longtext COLLATE utf8_bin NOT NULL,
MODIFY `cache` longtext COLLATE utf8_bin;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_relationship`
MODIFY `relation` varchar(4) CHARACTER SET binary,
MODIFY `objectPHID` varchar(64) CHARACTER SET binary,
MODIFY `reasonPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_relationship`
COLLATE utf8_general_ci,
MODIFY `relation` varchar(4) COLLATE utf8_bin NOT NULL,
MODIFY `objectPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `reasonPHID` varchar(64) COLLATE utf8_bin;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_revision`
MODIFY `title` varchar(255) CHARACTER SET binary,
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `status` varchar(32) CHARACTER SET binary,
MODIFY `summary` longtext CHARACTER SET binary,
MODIFY `testPlan` text CHARACTER SET binary,
MODIFY `authorPHID` varchar(64) CHARACTER SET binary,
MODIFY `lastReviewerPHID` varchar(64) CHARACTER SET binary,
MODIFY `attached` longtext CHARACTER SET binary,
MODIFY `unsubscribed` longtext CHARACTER SET binary,
MODIFY `mailKey` varchar(40) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_revision`
COLLATE utf8_general_ci,
MODIFY `title` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `status` varchar(32) COLLATE utf8_general_ci NOT NULL,
MODIFY `summary` longtext COLLATE utf8_general_ci NOT NULL,
MODIFY `testPlan` text COLLATE utf8_general_ci NOT NULL,
MODIFY `authorPHID` varchar(64) COLLATE utf8_bin,
MODIFY `lastReviewerPHID` varchar(64) COLLATE utf8_bin,
MODIFY `attached` longtext COLLATE utf8_general_ci NOT NULL,
MODIFY `unsubscribed` longtext COLLATE utf8_bin NOT NULL,
MODIFY `mailKey` varchar(40) COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_revisionhash`
MODIFY `type` char(4) CHARACTER SET binary,
MODIFY `hash` varchar(40) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_differential`.`differential_revisionhash`
COLLATE utf8_general_ci,
MODIFY `type` char(4) COLLATE utf8_bin NOT NULL,
MODIFY `hash` varchar(40) COLLATE utf8_bin NOT NULL;
ALTER DATABASE `{$NAMESPACE}_draft` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_draft`.`draft`
MODIFY `authorPHID` varchar(64) CHARACTER SET binary,
MODIFY `draftKey` varchar(64) CHARACTER SET binary,
MODIFY `draft` longtext CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_draft`.`draft`
COLLATE utf8_general_ci,
MODIFY `authorPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `draftKey` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `draft` longtext COLLATE utf8_bin NOT NULL;
ALTER DATABASE `{$NAMESPACE}_drydock` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_drydock`.`drydock_lease`
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `ownerPHID` varchar(64) CHARACTER SET binary,
MODIFY `attributes` longtext CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_drydock`.`drydock_lease`
COLLATE utf8_general_ci,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `ownerPHID` varchar(64) COLLATE utf8_bin,
MODIFY `attributes` longtext COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_drydock`.`drydock_resource`
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `name` varchar(255) CHARACTER SET binary,
MODIFY `ownerPHID` varchar(64) CHARACTER SET binary,
MODIFY `blueprintClass` varchar(255) CHARACTER SET binary,
MODIFY `type` varchar(64) CHARACTER SET binary,
MODIFY `attributes` longtext CHARACTER SET binary,
MODIFY `capabilities` longtext CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_drydock`.`drydock_resource`
COLLATE utf8_general_ci,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `name` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `ownerPHID` varchar(64) COLLATE utf8_bin,
MODIFY `blueprintClass` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `type` varchar(64) COLLATE utf8_general_ci NOT NULL,
MODIFY `attributes` longtext COLLATE utf8_bin NOT NULL,
MODIFY `capabilities` longtext COLLATE utf8_bin NOT NULL;
ALTER DATABASE `{$NAMESPACE}_feed` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_feed`.`feed_storydata`
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `storyType` varchar(64) CHARACTER SET binary,
MODIFY `storyData` longtext CHARACTER SET binary,
MODIFY `authorPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_feed`.`feed_storydata`
COLLATE utf8_general_ci,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `storyType` varchar(64) COLLATE utf8_general_ci NOT NULL,
MODIFY `storyData` longtext COLLATE utf8_bin NOT NULL,
MODIFY `authorPHID` varchar(64) COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_feed`.`feed_storyreference`
MODIFY `objectPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_feed`.`feed_storyreference`
COLLATE utf8_general_ci,
MODIFY `objectPHID` varchar(64) COLLATE utf8_bin NOT NULL;
ALTER DATABASE `{$NAMESPACE}_file` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_file`.`file`
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `name` varchar(255) CHARACTER SET binary,
MODIFY `mimeType` varchar(255) CHARACTER SET binary,
MODIFY `storageEngine` varchar(32) CHARACTER SET binary,
MODIFY `storageFormat` varchar(32) CHARACTER SET binary,
MODIFY `storageHandle` varchar(255) CHARACTER SET binary,
MODIFY `authorPHID` varchar(64) CHARACTER SET binary,
MODIFY `secretKey` varchar(20) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_file`.`file`
COLLATE utf8_general_ci,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `name` varchar(255) COLLATE utf8_general_ci,
MODIFY `mimeType` varchar(255) COLLATE utf8_general_ci,
MODIFY `storageEngine` varchar(32) COLLATE utf8_general_ci NOT NULL,
MODIFY `storageFormat` varchar(32) COLLATE utf8_general_ci NOT NULL,
MODIFY `storageHandle` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `authorPHID` varchar(64) COLLATE utf8_bin,
MODIFY `secretKey` varchar(20) COLLATE utf8_bin;
ALTER TABLE `{$NAMESPACE}_file`.`file_imagemacro`
MODIFY `filePHID` varchar(64) CHARACTER SET binary,
MODIFY `name` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_file`.`file_imagemacro`
COLLATE utf8_general_ci,
MODIFY `filePHID` varchar(64) COLLATE utf8_general_ci NOT NULL,
MODIFY `name` varchar(255) COLLATE utf8_general_ci NOT NULL;
ALTER TABLE `{$NAMESPACE}_file`.`file_proxyimage`
MODIFY `uri` varchar(255) CHARACTER SET binary,
MODIFY `filePHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_file`.`file_proxyimage`
COLLATE utf8_general_ci,
MODIFY `uri` varchar(255) COLLATE utf8_bin NOT NULL,
MODIFY `filePHID` varchar(64) COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_file`.`file_storageblob`
COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_file`.`file_transformedfile`
MODIFY `originalPHID` varchar(64) CHARACTER SET binary,
MODIFY `transform` varchar(255) CHARACTER SET binary,
MODIFY `transformedPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_file`.`file_transformedfile`
COLLATE utf8_general_ci,
MODIFY `originalPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `transform` varchar(255) COLLATE utf8_bin NOT NULL,
MODIFY `transformedPHID` varchar(64) COLLATE utf8_bin NOT NULL;
ALTER DATABASE `{$NAMESPACE}_herald` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_herald`.`herald_action`
MODIFY `action` varchar(255) CHARACTER SET binary,
MODIFY `target` text CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_herald`.`herald_action`
COLLATE utf8_general_ci,
MODIFY `action` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `target` text COLLATE utf8_general_ci NOT NULL;
ALTER TABLE `{$NAMESPACE}_herald`.`herald_condition`
MODIFY `fieldName` varchar(255) CHARACTER SET binary,
MODIFY `fieldCondition` varchar(255) CHARACTER SET binary,
MODIFY `value` text CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_herald`.`herald_condition`
COLLATE utf8_general_ci,
MODIFY `fieldName` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `fieldCondition` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `value` text COLLATE utf8_general_ci NOT NULL;
ALTER TABLE `{$NAMESPACE}_herald`.`herald_rule`
MODIFY `name` varchar(255) CHARACTER SET binary,
MODIFY `authorPHID` varchar(64) CHARACTER SET binary,
MODIFY `contentType` varchar(255) CHARACTER SET binary,
MODIFY `ruleType` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_herald`.`herald_rule`
COLLATE utf8_general_ci,
MODIFY `name` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `authorPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `contentType` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `ruleType` varchar(255) COLLATE utf8_general_ci NOT NULL DEFAULT 'global';
ALTER TABLE `{$NAMESPACE}_herald`.`herald_ruleapplied`
MODIFY `phid` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_herald`.`herald_ruleapplied`
COLLATE utf8_general_ci,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_herald`.`herald_ruleedit`
MODIFY `editorPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_herald`.`herald_ruleedit`
COLLATE utf8_general_ci,
MODIFY `editorPHID` varchar(64) COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_herald`.`herald_savedheader`
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `header` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_herald`.`herald_savedheader`
COLLATE utf8_general_ci,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `header` varchar(255) COLLATE utf8_general_ci NOT NULL;
ALTER TABLE `{$NAMESPACE}_herald`.`herald_transcript`
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `host` varchar(255) CHARACTER SET binary,
MODIFY `psth` varchar(255) CHARACTER SET binary,
MODIFY `objectPHID` varchar(64) CHARACTER SET binary,
MODIFY `objectTranscript` longtext CHARACTER SET binary,
MODIFY `ruleTranscripts` longtext CHARACTER SET binary,
MODIFY `conditionTranscripts` longtext CHARACTER SET binary,
MODIFY `applyTranscripts` longtext CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_herald`.`herald_transcript`
COLLATE utf8_general_ci,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `host` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `psth` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `objectPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `objectTranscript` longtext COLLATE utf8_bin NOT NULL,
MODIFY `ruleTranscripts` longtext COLLATE utf8_bin NOT NULL,
MODIFY `conditionTranscripts` longtext COLLATE utf8_bin NOT NULL,
MODIFY `applyTranscripts` longtext COLLATE utf8_bin NOT NULL;
ALTER DATABASE `{$NAMESPACE}_maniphest` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_maniphest`.`maniphest_task`
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `authorPHID` varchar(64) CHARACTER SET binary,
MODIFY `ownerPHID` varchar(64) CHARACTER SET binary,
MODIFY `ccPHIDs` text CHARACTER SET binary,
MODIFY `attached` longtext CHARACTER SET binary,
MODIFY `title` text CHARACTER SET binary,
MODIFY `description` longtext CHARACTER SET binary,
MODIFY `projectPHIDs` longtext CHARACTER SET binary,
MODIFY `mailKey` varchar(40) CHARACTER SET binary,
MODIFY `ownerOrdering` varchar(64) CHARACTER SET binary,
MODIFY `originalEmailSource` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_maniphest`.`maniphest_task`
COLLATE utf8_general_ci,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `authorPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `ownerPHID` varchar(64) COLLATE utf8_bin,
MODIFY `ccPHIDs` text COLLATE utf8_general_ci,
MODIFY `attached` longtext COLLATE utf8_general_ci NOT NULL,
MODIFY `title` text COLLATE utf8_general_ci NOT NULL,
MODIFY `description` longtext COLLATE utf8_general_ci NOT NULL,
MODIFY `projectPHIDs` longtext COLLATE utf8_bin NOT NULL,
MODIFY `mailKey` varchar(40) COLLATE utf8_bin NOT NULL,
MODIFY `ownerOrdering` varchar(64) COLLATE utf8_general_ci,
MODIFY `originalEmailSource` varchar(255) COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_maniphest`.`maniphest_taskauxiliarystorage`
MODIFY `taskPHID` varchar(64) CHARACTER SET binary,
MODIFY `name` varchar(255) CHARACTER SET binary,
MODIFY `value` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_maniphest`.`maniphest_taskauxiliarystorage`
COLLATE utf8_general_ci,
MODIFY `taskPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `name` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `value` varchar(255) COLLATE utf8_general_ci NOT NULL;
ALTER TABLE `{$NAMESPACE}_maniphest`.`maniphest_taskproject`
MODIFY `taskPHID` varchar(64) CHARACTER SET binary,
MODIFY `projectPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_maniphest`.`maniphest_taskproject`
COLLATE utf8_general_ci,
MODIFY `taskPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `projectPHID` varchar(64) COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_maniphest`.`maniphest_tasksubscriber`
MODIFY `taskPHID` varchar(64) CHARACTER SET binary,
MODIFY `subscriberPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_maniphest`.`maniphest_tasksubscriber`
COLLATE utf8_general_ci,
MODIFY `taskPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `subscriberPHID` varchar(64) COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_maniphest`.`maniphest_touch`
MODIFY `userPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_maniphest`.`maniphest_touch`
COLLATE utf8_general_ci,
MODIFY `userPHID` varchar(64) COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_maniphest`.`maniphest_transaction`
MODIFY `authorPHID` varchar(64) CHARACTER SET binary,
MODIFY `transactionType` varchar(16) CHARACTER SET binary,
MODIFY `oldValue` longtext CHARACTER SET binary,
MODIFY `newValue` longtext CHARACTER SET binary,
MODIFY `comments` longtext CHARACTER SET binary,
MODIFY `cache` longtext CHARACTER SET binary,
MODIFY `metadata` longtext CHARACTER SET binary,
MODIFY `contentSource` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_maniphest`.`maniphest_transaction`
COLLATE utf8_general_ci,
MODIFY `authorPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `transactionType` varchar(16) COLLATE utf8_general_ci NOT NULL,
MODIFY `oldValue` longtext COLLATE utf8_bin,
MODIFY `newValue` longtext COLLATE utf8_bin,
MODIFY `comments` longtext COLLATE utf8_bin,
MODIFY `cache` longtext COLLATE utf8_bin,
MODIFY `metadata` longtext COLLATE utf8_bin NOT NULL,
MODIFY `contentSource` varchar(255) COLLATE utf8_general_ci;
ALTER DATABASE `{$NAMESPACE}_meta_data` COLLATE utf8_general_ci;
ALTER DATABASE `{$NAMESPACE}_metamta` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_metamta`.`metamta_mail`
MODIFY `parameters` longtext CHARACTER SET binary,
MODIFY `status` varchar(255) CHARACTER SET binary,
MODIFY `message` text CHARACTER SET binary,
MODIFY `relatedPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_metamta`.`metamta_mail`
COLLATE utf8_general_ci,
MODIFY `parameters` longtext COLLATE utf8_bin NOT NULL,
MODIFY `status` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `message` text COLLATE utf8_general_ci,
MODIFY `relatedPHID` varchar(64) COLLATE utf8_bin;
ALTER TABLE `{$NAMESPACE}_metamta`.`metamta_mailinglist`
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `name` varchar(255) CHARACTER SET binary,
MODIFY `email` varchar(255) CHARACTER SET binary,
MODIFY `uri` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_metamta`.`metamta_mailinglist`
COLLATE utf8_general_ci,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `name` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `email` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `uri` varchar(255) COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_metamta`.`metamta_receivedmail`
MODIFY `headers` longtext CHARACTER SET binary,
MODIFY `bodies` longtext CHARACTER SET binary,
MODIFY `attachments` longtext CHARACTER SET binary,
MODIFY `relatedPHID` varchar(64) CHARACTER SET binary,
MODIFY `authorPHID` varchar(64) CHARACTER SET binary,
MODIFY `message` longtext CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_metamta`.`metamta_receivedmail`
COLLATE utf8_general_ci,
MODIFY `headers` longtext COLLATE utf8_bin NOT NULL,
MODIFY `bodies` longtext COLLATE utf8_bin NOT NULL,
MODIFY `attachments` longtext COLLATE utf8_bin NOT NULL,
MODIFY `relatedPHID` varchar(64) COLLATE utf8_bin,
MODIFY `authorPHID` varchar(64) COLLATE utf8_bin,
MODIFY `message` longtext COLLATE utf8_bin;
ALTER DATABASE `{$NAMESPACE}_oauth_server` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_oauth_server`.`oauth_server_oauthclientauthorization`
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `userPHID` varchar(64) CHARACTER SET binary,
MODIFY `clientPHID` varchar(64) CHARACTER SET binary,
MODIFY `scope` text CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_oauth_server`.`oauth_server_oauthclientauthorization`
COLLATE utf8_general_ci,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `userPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `clientPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `scope` text COLLATE utf8_general_ci NOT NULL;
ALTER TABLE `{$NAMESPACE}_oauth_server`.`oauth_server_oauthserveraccesstoken`
MODIFY `token` varchar(32) CHARACTER SET binary,
MODIFY `userPHID` varchar(64) CHARACTER SET binary,
MODIFY `clientPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_oauth_server`.`oauth_server_oauthserveraccesstoken`
COLLATE utf8_general_ci,
MODIFY `token` varchar(32) COLLATE utf8_general_ci NOT NULL,
MODIFY `userPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `clientPHID` varchar(64) COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_oauth_server`.`oauth_server_oauthserverauthorizationcode`
MODIFY `code` varchar(32) CHARACTER SET binary,
MODIFY `clientPHID` varchar(64) CHARACTER SET binary,
MODIFY `clientSecret` varchar(32) CHARACTER SET binary,
MODIFY `userPHID` varchar(64) CHARACTER SET binary,
MODIFY `redirectURI` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_oauth_server`.`oauth_server_oauthserverauthorizationcode`
COLLATE utf8_general_ci,
MODIFY `code` varchar(32) COLLATE utf8_general_ci NOT NULL,
MODIFY `clientPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `clientSecret` varchar(32) COLLATE utf8_general_ci NOT NULL,
MODIFY `userPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `redirectURI` varchar(255) COLLATE utf8_general_ci NOT NULL;
ALTER TABLE `{$NAMESPACE}_oauth_server`.`oauth_server_oauthserverclient`
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `name` varchar(255) CHARACTER SET binary,
MODIFY `secret` varchar(32) CHARACTER SET binary,
MODIFY `redirectURI` varchar(255) CHARACTER SET binary,
MODIFY `creatorPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_oauth_server`.`oauth_server_oauthserverclient`
COLLATE utf8_general_ci,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `name` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `secret` varchar(32) COLLATE utf8_general_ci NOT NULL,
MODIFY `redirectURI` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `creatorPHID` varchar(64) COLLATE utf8_bin NOT NULL;
ALTER DATABASE `{$NAMESPACE}_owners` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_owners`.`owners_owner`
MODIFY `userPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_owners`.`owners_owner`
COLLATE utf8_general_ci,
MODIFY `userPHID` varchar(64) COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_owners`.`owners_package`
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `name` varchar(255) CHARACTER SET binary,
MODIFY `description` text CHARACTER SET binary,
MODIFY `primaryOwnerPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_owners`.`owners_package`
COLLATE utf8_general_ci,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `name` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `description` text COLLATE utf8_general_ci NOT NULL,
MODIFY `primaryOwnerPHID` varchar(64) COLLATE utf8_bin;
ALTER TABLE `{$NAMESPACE}_owners`.`owners_path`
MODIFY `repositoryPHID` varchar(64) CHARACTER SET binary,
MODIFY `path` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_owners`.`owners_path`
COLLATE utf8_general_ci,
MODIFY `repositoryPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `path` varchar(255) COLLATE utf8_general_ci NOT NULL;
ALTER DATABASE `{$NAMESPACE}_pastebin` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_pastebin`.`pastebin_paste`
MODIFY `title` varchar(255) CHARACTER SET binary,
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `authorPHID` varchar(64) CHARACTER SET binary,
MODIFY `filePHID` varchar(64) CHARACTER SET binary,
MODIFY `language` varchar(64) CHARACTER SET binary,
MODIFY `parentPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_pastebin`.`pastebin_paste`
COLLATE utf8_general_ci,
MODIFY `title` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `authorPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `filePHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `language` varchar(64) COLLATE utf8_general_ci NOT NULL,
MODIFY `parentPHID` varchar(64) COLLATE utf8_bin;
ALTER DATABASE `{$NAMESPACE}_phriction` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_phriction`.`phriction_content`
MODIFY `authorPHID` varchar(64) CHARACTER SET binary,
MODIFY `title` varchar(512) CHARACTER SET binary,
MODIFY `slug` varchar(512) CHARACTER SET binary,
MODIFY `content` longtext CHARACTER SET binary,
MODIFY `description` varchar(512) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_phriction`.`phriction_content`
COLLATE utf8_general_ci,
MODIFY `authorPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `title` varchar(512) COLLATE utf8_general_ci NOT NULL,
MODIFY `slug` varchar(512) COLLATE utf8_general_ci NOT NULL,
MODIFY `content` longtext COLLATE utf8_bin NOT NULL,
MODIFY `description` varchar(512) COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_phriction`.`phriction_document`
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `slug` varchar(128) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_phriction`.`phriction_document`
COLLATE utf8_general_ci,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `slug` varchar(128) COLLATE utf8_general_ci NOT NULL;
ALTER DATABASE `{$NAMESPACE}_project` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_project`.`project`
MODIFY `name` varchar(255) CHARACTER SET binary,
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `authorPHID` varchar(64) CHARACTER SET binary,
MODIFY `status` varchar(32) CHARACTER SET binary,
MODIFY `subprojectPHIDs` longtext CHARACTER SET binary,
MODIFY `phrictionSlug` varchar(128) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_project`.`project`
COLLATE utf8_general_ci,
MODIFY `name` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `authorPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `status` varchar(32) COLLATE utf8_general_ci NOT NULL,
MODIFY `subprojectPHIDs` longtext COLLATE utf8_bin NOT NULL,
MODIFY `phrictionSlug` varchar(128) COLLATE utf8_bin;
ALTER TABLE `{$NAMESPACE}_project`.`project_affiliation`
MODIFY `projectPHID` varchar(64) CHARACTER SET binary,
MODIFY `userPHID` varchar(64) CHARACTER SET binary,
MODIFY `role` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_project`.`project_affiliation`
COLLATE utf8_general_ci,
MODIFY `projectPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `userPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `role` varchar(255) COLLATE utf8_general_ci NOT NULL;
ALTER TABLE `{$NAMESPACE}_project`.`project_profile`
MODIFY `projectPHID` varchar(64) CHARACTER SET binary,
MODIFY `blurb` longtext CHARACTER SET binary,
MODIFY `profileImagePHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_project`.`project_profile`
COLLATE utf8_general_ci,
MODIFY `projectPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `blurb` longtext COLLATE utf8_general_ci NOT NULL,
MODIFY `profileImagePHID` varchar(64) COLLATE utf8_bin;
ALTER TABLE `{$NAMESPACE}_project`.`project_subproject`
MODIFY `projectPHID` varchar(64) CHARACTER SET binary,
MODIFY `subprojectPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_project`.`project_subproject`
COLLATE utf8_general_ci,
MODIFY `projectPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `subprojectPHID` varchar(64) COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_project`.`project_transaction`
MODIFY `authorPHID` varchar(64) CHARACTER SET binary,
MODIFY `transactionType` varchar(32) CHARACTER SET binary,
MODIFY `oldValue` longtext CHARACTER SET binary,
MODIFY `newValue` longtext CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_project`.`project_transaction`
COLLATE utf8_general_ci,
MODIFY `authorPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `transactionType` varchar(32) COLLATE utf8_general_ci NOT NULL,
MODIFY `oldValue` longtext COLLATE utf8_bin NOT NULL,
MODIFY `newValue` longtext COLLATE utf8_bin NOT NULL;
ALTER DATABASE `{$NAMESPACE}_repository` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_repository`.`repository`
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `name` varchar(255) CHARACTER SET binary,
MODIFY `callsign` varchar(32) CHARACTER SET binary,
MODIFY `versionControlSystem` varchar(32) CHARACTER SET binary,
MODIFY `details` longtext CHARACTER SET binary,
MODIFY `uuid` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_repository`.`repository`
COLLATE utf8_general_ci,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `name` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `callsign` varchar(32) COLLATE utf8_general_ci NOT NULL,
MODIFY `versionControlSystem` varchar(32) COLLATE utf8_general_ci NOT NULL,
MODIFY `details` longtext COLLATE utf8_bin NOT NULL,
MODIFY `uuid` varchar(64) COLLATE utf8_bin;
ALTER TABLE `{$NAMESPACE}_repository`.`repository_arcanistproject`
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `name` varchar(255) CHARACTER SET binary,
MODIFY `symbolIndexLanguages` longtext CHARACTER SET binary,
MODIFY `symbolIndexProjects` longtext CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_repository`.`repository_arcanistproject`
COLLATE utf8_general_ci,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `name` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `symbolIndexLanguages` longtext COLLATE utf8_bin NOT NULL,
MODIFY `symbolIndexProjects` longtext COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_repository`.`repository_auditrequest`
MODIFY `auditorPHID` varchar(64) CHARACTER SET binary,
MODIFY `commitPHID` varchar(64) CHARACTER SET binary,
MODIFY `auditStatus` varchar(64) CHARACTER SET binary,
MODIFY `auditReasons` longtext CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_repository`.`repository_auditrequest`
COLLATE utf8_general_ci,
MODIFY `auditorPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `commitPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `auditStatus` varchar(64) COLLATE utf8_general_ci NOT NULL,
MODIFY `auditReasons` longtext COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_repository`.`repository_badcommit`
MODIFY `fullCommitName` varchar(255) CHARACTER SET binary,
MODIFY `description` longtext CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_repository`.`repository_badcommit`
COLLATE utf8_general_ci,
MODIFY `fullCommitName` varchar(255) COLLATE utf8_bin NOT NULL,
MODIFY `description` longtext COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_repository`.`repository_commit`
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `commitIdentifier` varchar(40) CHARACTER SET binary,
MODIFY `mailKey` varchar(20) CHARACTER SET binary,
MODIFY `authorPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_repository`.`repository_commit`
COLLATE utf8_general_ci,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `commitIdentifier` varchar(40) COLLATE utf8_bin NOT NULL,
MODIFY `mailKey` varchar(20) COLLATE utf8_general_ci NOT NULL,
MODIFY `authorPHID` varchar(64) COLLATE utf8_bin;
ALTER TABLE `{$NAMESPACE}_repository`.`repository_commitdata`
MODIFY `authorName` varchar(255) CHARACTER SET binary,
MODIFY `commitMessage` longtext CHARACTER SET binary,
MODIFY `commitDetails` longtext CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_repository`.`repository_commitdata`
COLLATE utf8_general_ci,
MODIFY `authorName` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `commitMessage` longtext COLLATE utf8_bin NOT NULL,
MODIFY `commitDetails` longtext COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_repository`.`repository_filesystem`
COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_repository`.`repository_path`
MODIFY `path` varchar(512) CHARACTER SET binary,
MODIFY `pathHash` varchar(32) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_repository`.`repository_path`
COLLATE utf8_general_ci,
MODIFY `path` varchar(512) COLLATE utf8_bin NOT NULL,
MODIFY `pathHash` varchar(32) COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_repository`.`repository_pathchange`
COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_repository`.`repository_shortcut`
MODIFY `name` varchar(255) CHARACTER SET binary,
MODIFY `href` varchar(255) CHARACTER SET binary,
MODIFY `description` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_repository`.`repository_shortcut`
COLLATE utf8_general_ci,
MODIFY `name` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `href` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `description` varchar(255) COLLATE utf8_general_ci NOT NULL;
ALTER TABLE `{$NAMESPACE}_repository`.`repository_summary`
COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_repository`.`repository_symbol`
MODIFY `symbolName` varchar(128) CHARACTER SET binary,
MODIFY `symbolType` varchar(12) CHARACTER SET binary,
MODIFY `symbolLanguage` varchar(32) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_repository`.`repository_symbol`
COLLATE utf8_general_ci,
MODIFY `symbolName` varchar(128) COLLATE utf8_general_ci NOT NULL,
MODIFY `symbolType` varchar(12) COLLATE utf8_bin NOT NULL,
MODIFY `symbolLanguage` varchar(32) COLLATE utf8_bin NOT NULL;
ALTER DATABASE `{$NAMESPACE}_search` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_search`.`search_document`
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `documentType` varchar(4) CHARACTER SET binary,
MODIFY `documentTitle` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_search`.`search_document`
COLLATE utf8_general_ci,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `documentType` varchar(4) COLLATE utf8_bin NOT NULL,
MODIFY `documentTitle` varchar(255) COLLATE utf8_general_ci NOT NULL;
ALTER TABLE `{$NAMESPACE}_search`.`search_documentfield`
DROP INDEX corpus,
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `phidType` varchar(4) CHARACTER SET binary,
MODIFY `field` varchar(4) CHARACTER SET binary,
MODIFY `auxPHID` varchar(64) CHARACTER SET binary,
MODIFY `corpus` text CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_search`.`search_documentfield`
COLLATE utf8_general_ci,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `phidType` varchar(4) COLLATE utf8_bin NOT NULL,
MODIFY `field` varchar(4) COLLATE utf8_bin NOT NULL,
MODIFY `auxPHID` varchar(64) COLLATE utf8_bin,
MODIFY `corpus` text COLLATE utf8_general_ci,
ADD FULLTEXT (corpus);
ALTER TABLE `{$NAMESPACE}_search`.`search_documentrelationship`
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `relatedPHID` varchar(64) CHARACTER SET binary,
MODIFY `relation` varchar(4) CHARACTER SET binary,
MODIFY `relatedType` varchar(4) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_search`.`search_documentrelationship`
COLLATE utf8_general_ci,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `relatedPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `relation` varchar(4) COLLATE utf8_bin NOT NULL,
MODIFY `relatedType` varchar(4) COLLATE utf8_general_ci NOT NULL;
ALTER TABLE `{$NAMESPACE}_search`.`search_query`
MODIFY `query` varchar(255) CHARACTER SET binary,
MODIFY `parameters` text CHARACTER SET binary,
MODIFY `queryKey` varchar(12) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_search`.`search_query`
COLLATE utf8_general_ci,
MODIFY `query` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `parameters` text COLLATE utf8_general_ci NOT NULL,
MODIFY `queryKey` varchar(12) COLLATE utf8_general_ci NOT NULL;
ALTER DATABASE `{$NAMESPACE}_slowvote` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_slowvote`.`slowvote_choice`
MODIFY `authorPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_slowvote`.`slowvote_choice`
COLLATE utf8_general_ci,
MODIFY `authorPHID` varchar(64) COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_slowvote`.`slowvote_comment`
MODIFY `authorPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_slowvote`.`slowvote_comment`
COLLATE utf8_general_ci,
MODIFY `authorPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `commentText` longtext COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_slowvote`.`slowvote_option`
MODIFY `name` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_slowvote`.`slowvote_option`
COLLATE utf8_general_ci,
MODIFY `name` varchar(255) COLLATE utf8_general_ci NOT NULL;
ALTER TABLE `{$NAMESPACE}_slowvote`.`slowvote_poll`
MODIFY `question` varchar(255) CHARACTER SET binary,
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `authorPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_slowvote`.`slowvote_poll`
COLLATE utf8_general_ci,
MODIFY `question` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `authorPHID` varchar(64) COLLATE utf8_bin NOT NULL;
ALTER DATABASE `{$NAMESPACE}_timeline` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_timeline`.`timeline_cursor`
MODIFY `name` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_timeline`.`timeline_cursor`
COLLATE utf8_general_ci,
MODIFY `name` varchar(255) COLLATE utf8_general_ci NOT NULL;
ALTER TABLE `{$NAMESPACE}_timeline`.`timeline_event`
MODIFY `type` char(4) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_timeline`.`timeline_event`
COLLATE utf8_general_ci,
MODIFY `type` char(4) COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_timeline`.`timeline_eventdata`
COLLATE utf8_general_ci,
MODIFY `eventData` longtext COLLATE utf8_bin NOT NULL;
ALTER DATABASE `{$NAMESPACE}_user` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_user`.`phabricator_session`
MODIFY `userPHID` varchar(64) CHARACTER SET binary,
MODIFY `type` varchar(32) CHARACTER SET binary,
MODIFY `sessionKey` varchar(40) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_user`.`phabricator_session`
COLLATE utf8_general_ci,
MODIFY `userPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `type` varchar(32) COLLATE utf8_bin NOT NULL,
MODIFY `sessionKey` varchar(40) COLLATE utf8_general_ci NOT NULL;
ALTER TABLE `{$NAMESPACE}_user`.`user`
MODIFY `phid` varchar(64) CHARACTER SET binary,
MODIFY `userName` varchar(64) CHARACTER SET binary,
MODIFY `realName` varchar(128) CHARACTER SET binary,
MODIFY `email` varchar(255) CHARACTER SET binary,
MODIFY `passwordSalt` varchar(32) CHARACTER SET binary,
MODIFY `passwordHash` varchar(32) CHARACTER SET binary,
MODIFY `profileImagePHID` varchar(64) CHARACTER SET binary,
MODIFY `consoleTab` varchar(64) CHARACTER SET binary,
MODIFY `conduitCertificate` varchar(255) CHARACTER SET binary,
MODIFY `timezoneIdentifier` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_user`.`user`
COLLATE utf8_general_ci,
MODIFY `phid` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `userName` varchar(64) COLLATE utf8_general_ci NOT NULL,
MODIFY `realName` varchar(128) COLLATE utf8_general_ci NOT NULL,
MODIFY `email` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `passwordSalt` varchar(32) COLLATE utf8_bin,
MODIFY `passwordHash` varchar(32) COLLATE utf8_bin,
MODIFY `profileImagePHID` varchar(64) COLLATE utf8_bin,
MODIFY `consoleTab` varchar(64) COLLATE utf8_general_ci NOT NULL,
MODIFY `conduitCertificate` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `timezoneIdentifier` varchar(255) COLLATE utf8_general_ci NOT NULL;
ALTER TABLE `{$NAMESPACE}_user`.`user_log`
MODIFY `actorPHID` varchar(64) CHARACTER SET binary,
MODIFY `userPHID` varchar(64) CHARACTER SET binary,
MODIFY `action` varchar(64) CHARACTER SET binary,
MODIFY `remoteAddr` varchar(16) CHARACTER SET binary,
MODIFY `session` varchar(40) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_user`.`user_log`
COLLATE utf8_general_ci,
MODIFY `actorPHID` varchar(64) COLLATE utf8_bin,
MODIFY `userPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `action` varchar(64) COLLATE utf8_general_ci NOT NULL,
MODIFY `oldValue` longtext COLLATE utf8_bin NOT NULL,
MODIFY `newValue` longtext COLLATE utf8_bin NOT NULL,
MODIFY `details` longtext COLLATE utf8_bin NOT NULL,
MODIFY `remoteAddr` varchar(16) COLLATE utf8_general_ci NOT NULL,
MODIFY `session` varchar(40) COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_user`.`user_nametoken`
MODIFY `token` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_user`.`user_nametoken`
COLLATE utf8_general_ci,
MODIFY `token` varchar(255) COLLATE utf8_general_ci NOT NULL;
ALTER TABLE `{$NAMESPACE}_user`.`user_oauthinfo`
MODIFY `oauthProvider` varchar(255) CHARACTER SET binary,
MODIFY `oauthUID` varchar(255) CHARACTER SET binary,
MODIFY `accountURI` varchar(255) CHARACTER SET binary,
MODIFY `accountName` varchar(255) CHARACTER SET binary,
MODIFY `token` varchar(255) CHARACTER SET binary,
MODIFY `tokenScope` varchar(255) CHARACTER SET binary,
MODIFY `tokenStatus` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_user`.`user_oauthinfo`
COLLATE utf8_general_ci,
MODIFY `oauthProvider` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `oauthUID` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `accountURI` varchar(255) COLLATE utf8_general_ci,
MODIFY `accountName` varchar(255) COLLATE utf8_general_ci,
MODIFY `token` varchar(255) COLLATE utf8_general_ci,
MODIFY `tokenScope` varchar(255) COLLATE utf8_general_ci,
MODIFY `tokenStatus` varchar(255) COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_user`.`user_preferences`
MODIFY `userPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_user`.`user_preferences`
COLLATE utf8_general_ci,
MODIFY `userPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `preferences` longtext COLLATE utf8_bin NOT NULL;
ALTER TABLE `{$NAMESPACE}_user`.`user_profile`
MODIFY `userPHID` varchar(64) CHARACTER SET binary,
MODIFY `title` varchar(255) CHARACTER SET binary,
MODIFY `blurb` text CHARACTER SET binary,
MODIFY `profileImagePHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_user`.`user_profile`
COLLATE utf8_general_ci,
MODIFY `userPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `title` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `blurb` text COLLATE utf8_general_ci NOT NULL,
MODIFY `profileImagePHID` varchar(64) COLLATE utf8_bin;
ALTER TABLE `{$NAMESPACE}_user`.`user_sshkey`
MODIFY `userPHID` varchar(64) CHARACTER SET binary,
MODIFY `name` varchar(255) CHARACTER SET binary,
MODIFY `keyType` varchar(255) CHARACTER SET binary,
MODIFY `keyBody` text CHARACTER SET binary,
MODIFY `keyHash` varchar(32) CHARACTER SET binary,
MODIFY `keyComment` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_user`.`user_sshkey`
COLLATE utf8_general_ci,
MODIFY `userPHID` varchar(64) COLLATE utf8_bin NOT NULL,
MODIFY `name` varchar(255) COLLATE utf8_general_ci,
MODIFY `keyType` varchar(255) COLLATE utf8_general_ci,
MODIFY `keyBody` text COLLATE utf8_bin,
MODIFY `keyHash` varchar(32) COLLATE utf8_bin NOT NULL,
MODIFY `keyComment` varchar(255) COLLATE utf8_general_ci;
ALTER DATABASE `{$NAMESPACE}_worker` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_worker`.`worker_task`
MODIFY `taskClass` varchar(255) CHARACTER SET binary,
MODIFY `leaseOwner` varchar(255) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_worker`.`worker_task`
COLLATE utf8_general_ci,
MODIFY `taskClass` varchar(255) COLLATE utf8_general_ci NOT NULL,
MODIFY `leaseOwner` varchar(255) COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_worker`.`worker_taskdata`
COLLATE utf8_general_ci,
MODIFY `data` longtext COLLATE utf8_bin NOT NULL;
ALTER DATABASE `{$NAMESPACE}_xhpastview` COLLATE utf8_general_ci;
ALTER TABLE `{$NAMESPACE}_xhpastview`.`xhpastview_parsetree`
MODIFY `authorPHID` varchar(64) CHARACTER SET binary;
ALTER TABLE `{$NAMESPACE}_xhpastview`.`xhpastview_parsetree`
COLLATE utf8_general_ci,
MODIFY `authorPHID` varchar(64) COLLATE utf8_bin,
MODIFY `input` longtext COLLATE utf8_bin NOT NULL,
MODIFY `stdout` longtext COLLATE utf8_bin NOT NULL;
-
-
-
diff --git a/resources/sql/patches/120.noop.sql b/resources/sql/patches/120.noop.sql
index b730eaa0d3..a8ab6db8fd 100644
--- a/resources/sql/patches/120.noop.sql
+++ b/resources/sql/patches/120.noop.sql
@@ -1,2 +1,2 @@
/* Do nothing, patch 121 got committed before there was a patch 120. */
-SELECT 1;
\ No newline at end of file
+SELECT 1;
diff --git a/resources/sql/patches/122.flag.sql b/resources/sql/patches/122.flag.sql
index 6b666a6855..7f66669930 100644
--- a/resources/sql/patches/122.flag.sql
+++ b/resources/sql/patches/122.flag.sql
@@ -1,16 +1,14 @@
-
-
CREATE TABLE {$NAMESPACE}_flag.flag (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
ownerPHID varchar(64) COLLATE utf8_bin NOT NULL,
type varchar(4) COLLATE utf8_bin NOT NULL,
objectPHID varchar(64) COLLATE utf8_bin NOT NULL,
reasonPHID varchar(64) COLLATE utf8_bin NOT NULL,
color INT UNSIGNED NOT NULL,
note varchar(255) COLLATE utf8_general_ci,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY (ownerPHID, type, objectPHID),
KEY (objectPHID)
-) ENGINE=InnoDB;
\ No newline at end of file
+) ENGINE=InnoDB;
diff --git a/resources/sql/patches/124.subpriority.sql b/resources/sql/patches/124.subpriority.sql
index 2133b6e574..cd826a1efc 100644
--- a/resources/sql/patches/124.subpriority.sql
+++ b/resources/sql/patches/124.subpriority.sql
@@ -1,11 +1,9 @@
ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task
ADD subpriority DOUBLE NOT NULL;
ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task
ADD KEY (priority, subpriority);
/* Seed the subpriority column with reasonable values that keep order stable. */
UPDATE {$NAMESPACE}_maniphest.maniphest_task
SET subpriority = (UNIX_TIMESTAMP() - dateModified);
-
-
diff --git a/resources/sql/patches/128.phabricatorcom.sql b/resources/sql/patches/128.phabricatorcom.sql
index 4623688e52..9db23c5475 100644
--- a/resources/sql/patches/128.phabricatorcom.sql
+++ b/resources/sql/patches/128.phabricatorcom.sql
@@ -1 +1 @@
-/* This used to be a "directory" update. */;
\ No newline at end of file
+/* This used to be a "directory" update. */;
diff --git a/resources/sql/patches/132.phame.sql b/resources/sql/patches/132.phame.sql
index e56de6fe91..be513ea050 100644
--- a/resources/sql/patches/132.phame.sql
+++ b/resources/sql/patches/132.phame.sql
@@ -1,18 +1,16 @@
-
-
CREATE TABLE `{$NAMESPACE}_phame`.`phame_post` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`phid` VARCHAR(64) BINARY NOT NULL COLLATE utf8_bin,
`bloggerPHID` VARCHAR(64) BINARY NOT NULL COLLATE utf8_bin,
`title` VARCHAR(255) NOT NULL,
`phameTitle` VARCHAR(64) NOT NULL,
`body` LONGTEXT COLLATE utf8_general_ci,
`visibility` INT UNSIGNED NOT NULL DEFAULT 0,
`configData` LONGTEXT COLLATE utf8_general_ci,
`datePublished` INT UNSIGNED NOT NULL,
`dateCreated` INT UNSIGNED NOT NULL,
`dateModified` INT UNSIGNED NOT NULL,
KEY `bloggerPosts` (`bloggerPHID`, `visibility`, `datePublished`, `id`),
UNIQUE KEY `phid` (`phid`),
UNIQUE KEY `phameTitle` (`bloggerPHID`, `phameTitle`)
) ENGINE=InnoDB;
diff --git a/resources/sql/patches/20121209.pholioxactions.sql b/resources/sql/patches/20121209.pholioxactions.sql
index 70f2280057..ab302801ba 100644
--- a/resources/sql/patches/20121209.pholioxactions.sql
+++ b/resources/sql/patches/20121209.pholioxactions.sql
@@ -1,51 +1,50 @@
DROP TABLE {$NAMESPACE}_pholio.pholio_transaction;
DROP TABLE {$NAMESPACE}_pholio.pholio_pixelcomment;
CREATE TABLE {$NAMESPACE}_pholio.pholio_transaction (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
commentPHID VARCHAR(64) COLLATE utf8_bin,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin,
oldValue LONGTEXT NOT NULL COLLATE utf8_bin,
newValue LONGTEXT NOT NULL COLLATE utf8_bin,
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
KEY `key_object` (objectPHID)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
CREATE TABLE {$NAMESPACE}_pholio.pholio_transaction_comment (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
transactionPHID VARCHAR(64) COLLATE utf8_bin,
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
commentVersion INT UNSIGNED NOT NULL,
content LONGTEXT NOT NULL COLLATE utf8_bin,
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
isDeleted BOOL NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
mockID INT UNSIGNED,
imageID INT UNSIGNED,
x INT UNSIGNED,
y INT UNSIGNED,
width INT UNSIGNED,
height INT UNSIGNED,
UNIQUE KEY `key_phid` (phid),
UNIQUE KEY `key_version` (transactionPHID, commentVersion),
UNIQUE KEY `key_draft` (authorPHID, mockID, transactionPHID)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
-
diff --git a/resources/sql/patches/20130111.conpherence.sql b/resources/sql/patches/20130111.conpherence.sql
index bea6b1820e..519271771b 100644
--- a/resources/sql/patches/20130111.conpherence.sql
+++ b/resources/sql/patches/20130111.conpherence.sql
@@ -1,83 +1,81 @@
-
CREATE TABLE {$NAMESPACE}_conpherence.conpherence_thread (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
title VARCHAR(255),
imagePHID VARCHAR(64) COLLATE utf8_bin,
mailKey VARCHAR(20) NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY(phid)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
CREATE TABLE {$NAMESPACE}_conpherence.conpherence_participant (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
participantPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
conpherencePHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
participationStatus INT UNSIGNED NOT NULL DEFAULT 0,
dateTouched INT UNSIGNED NOT NULL,
behindTransactionPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY(conpherencePHID, participantPHID),
KEY(participantPHID, participationStatus, dateTouched)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
CREATE TABLE {$NAMESPACE}_conpherence.edge (
src varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
type varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
dst varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
dateCreated int(10) unsigned NOT NULL,
seq int(10) unsigned NOT NULL,
dataID int(10) unsigned DEFAULT NULL,
PRIMARY KEY (src, type, dst),
KEY src (src, type, dateCreated, seq)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE {$NAMESPACE}_conpherence.edgedata (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
data longtext CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE {$NAMESPACE}_conpherence.conpherence_transaction (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
commentPHID VARCHAR(64) COLLATE utf8_bin,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin,
oldValue LONGTEXT NOT NULL COLLATE utf8_bin,
newValue LONGTEXT NOT NULL COLLATE utf8_bin,
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
KEY `key_object` (objectPHID)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
CREATE TABLE {$NAMESPACE}_conpherence.conpherence_transaction_comment (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
transactionPHID VARCHAR(64) COLLATE utf8_bin,
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
commentVersion INT UNSIGNED NOT NULL,
content LONGTEXT NOT NULL COLLATE utf8_bin,
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
isDeleted BOOL NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
conpherencePHID VARCHAR(64) COLLATE utf8_bin,
UNIQUE KEY `key_phid` (phid),
UNIQUE KEY `key_version` (transactionPHID, commentVersion),
UNIQUE KEY `key_draft` (authorPHID, conpherencePHID, transactionPHID)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
-
diff --git a/resources/sql/patches/20130127.altheraldtranscript.sql b/resources/sql/patches/20130127.altheraldtranscript.sql
index 5b59c4d40b..2f3f916df0 100644
--- a/resources/sql/patches/20130127.altheraldtranscript.sql
+++ b/resources/sql/patches/20130127.altheraldtranscript.sql
@@ -1,3 +1,2 @@
ALTER TABLE `{$NAMESPACE}_herald`.`herald_transcript`
DROP `psth`;
-
diff --git a/resources/sql/patches/20130131.conpherencepics.sql b/resources/sql/patches/20130131.conpherencepics.sql
index 421291225f..0b22a89967 100644
--- a/resources/sql/patches/20130131.conpherencepics.sql
+++ b/resources/sql/patches/20130131.conpherencepics.sql
@@ -1,6 +1,6 @@
-ALTER TABLE {$NAMESPACE}_conpherence.conpherence_thread
+ALTER TABLE {$NAMESPACE}_conpherence.conpherence_thread
DROP imagePHID,
ADD imagePHIDs LONGTEXT COLLATE utf8_bin NOT NULL AFTER title;
-UPDATE {$NAMESPACE}_conpherence.conpherence_thread
+UPDATE {$NAMESPACE}_conpherence.conpherence_thread
SET imagePHIDs = '{}' WHERE imagePHIDs = '';
diff --git a/resources/sql/patches/20130214.chatlogchannel.sql b/resources/sql/patches/20130214.chatlogchannel.sql
index a28b1d0a4b..6bb0a777ac 100644
--- a/resources/sql/patches/20130214.chatlogchannel.sql
+++ b/resources/sql/patches/20130214.chatlogchannel.sql
@@ -1,12 +1,11 @@
-
CREATE TABLE {$NAMESPACE}_chatlog.chatlog_channel (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
serviceName VARCHAR(64) COLLATE utf8_bin NOT NULL,
serviceType VARCHAR(32) COLLATE utf8_bin NOT NULL,
channelName VARCHAR(64) COLLATE utf8_bin NOT NULL,
viewPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL,
editPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_channel` (channelName, serviceType, serviceName)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
diff --git a/resources/sql/patches/20130215.phabricatorfileaddttl.sql b/resources/sql/patches/20130215.phabricatorfileaddttl.sql
index d8c3cbf8b3..eece5414e8 100644
--- a/resources/sql/patches/20130215.phabricatorfileaddttl.sql
+++ b/resources/sql/patches/20130215.phabricatorfileaddttl.sql
@@ -1,3 +1,3 @@
-ALTER TABLE {$NAMESPACE}_file.file
- ADD ttl INT(10) UNSIGNED DEFAULT NULL,
+ALTER TABLE {$NAMESPACE}_file.file
+ ADD ttl INT(10) UNSIGNED DEFAULT NULL,
ADD KEY key_ttl (ttl);
diff --git a/resources/sql/patches/20130317.phrictionedge.sql b/resources/sql/patches/20130317.phrictionedge.sql
index fdeed8f178..802d259bcb 100644
--- a/resources/sql/patches/20130317.phrictionedge.sql
+++ b/resources/sql/patches/20130317.phrictionedge.sql
@@ -1,16 +1,15 @@
CREATE TABLE {$NAMESPACE}_phriction.edge (
src VARCHAR(64) NOT NULL COLLATE utf8_bin,
type INT UNSIGNED NOT NULL COLLATE utf8_bin,
dst VARCHAR(64) NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
seq INT UNSIGNED NOT NULL,
dataID INT UNSIGNED,
PRIMARY KEY (src, type, dst),
KEY (src, type, dateCreated, seq)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
CREATE TABLE {$NAMESPACE}_phriction.edgedata (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
data LONGTEXT NOT NULL COLLATE utf8_bin
) ENGINE=InnoDB, COLLATE utf8_general_ci;
-
diff --git a/resources/sql/patches/20130320.phlux.sql b/resources/sql/patches/20130320.phlux.sql
index fc3db93a42..07e6fd9949 100644
--- a/resources/sql/patches/20130320.phlux.sql
+++ b/resources/sql/patches/20130320.phlux.sql
@@ -1,32 +1,31 @@
CREATE TABLE {$NAMESPACE}_phlux.phlux_variable (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
variableKey VARCHAR(64) NOT NULL COLLATE utf8_bin,
variableValue LONGTEXT NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
UNIQUE KEY `key_key` (variableKey)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE {$NAMESPACE}_phlux.phlux_transaction (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
commentPHID VARCHAR(64) COLLATE utf8_bin,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin,
oldValue LONGTEXT NOT NULL COLLATE utf8_bin,
newValue LONGTEXT NOT NULL COLLATE utf8_bin,
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
KEY `key_object` (objectPHID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
diff --git a/resources/sql/patches/20130322.phortune.sql b/resources/sql/patches/20130322.phortune.sql
index 5105716972..5a9447aab0 100644
--- a/resources/sql/patches/20130322.phortune.sql
+++ b/resources/sql/patches/20130322.phortune.sql
@@ -1,46 +1,45 @@
CREATE TABLE {$NAMESPACE}_phortune.phortune_account (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
name VARCHAR(255) NOT NULL,
balanceInCents BIGINT NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE {$NAMESPACE}_phortune.phortune_accounttransaction (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
commentPHID VARCHAR(64) COLLATE utf8_bin,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin,
oldValue LONGTEXT NOT NULL COLLATE utf8_bin,
newValue LONGTEXT NOT NULL COLLATE utf8_bin,
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
metadata LONGTEXT NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
KEY `key_object` (objectPHID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE {$NAMESPACE}_phortune.edge (
src VARCHAR(64) NOT NULL COLLATE utf8_bin,
type INT UNSIGNED NOT NULL COLLATE utf8_bin,
dst VARCHAR(64) NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
seq INT UNSIGNED NOT NULL,
dataID INT UNSIGNED,
PRIMARY KEY (src, type, dst),
KEY (src, type, dateCreated, seq)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE {$NAMESPACE}_phortune.edgedata (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
data LONGTEXT NOT NULL COLLATE utf8_bin
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
diff --git a/resources/sql/patches/20130417.externalaccount.sql b/resources/sql/patches/20130417.externalaccount.sql
index 0d87db0afb..806117709a 100644
--- a/resources/sql/patches/20130417.externalaccount.sql
+++ b/resources/sql/patches/20130417.externalaccount.sql
@@ -1,12 +1,11 @@
-
CREATE TABLE {$NAMESPACE}_user.externalaccount (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) COLLATE utf8_bin NOT NULL UNIQUE KEY,
userPHID VARCHAR(64) COLLATE utf8_bin,
accountType VARCHAR(16) COLLATE utf8_bin NOT NULL,
accountDomain VARCHAR(64) COLLATE utf8_bin,
accountSecret LONGTEXT COLLATE utf8_bin,
accountID VARCHAR(160) COLLATE utf8_bin NOT NULL,
displayName VARCHAR(256) COLLATE utf8_bin NOT NULL,
UNIQUE KEY `account_details` (accountType, accountDomain, accountID)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
diff --git a/resources/sql/patches/20130519.diviner.sql b/resources/sql/patches/20130519.diviner.sql
index fc746bb354..7adc386baa 100644
--- a/resources/sql/patches/20130519.diviner.sql
+++ b/resources/sql/patches/20130519.diviner.sql
@@ -1,38 +1,36 @@
CREATE TABLE {$NAMESPACE}_diviner.diviner_livebook (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
name VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY (name),
UNIQUE KEY (phid)
) ENGINE=InnoDB, DEFAULT CHARSET = utf8;
CREATE TABLE {$NAMESPACE}_diviner.diviner_livesymbol (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
bookPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
context VARCHAR(255) COLLATE utf8_bin,
type VARCHAR (32) NOT NULL COLLATE utf8_bin,
name VARCHAR (255) NOT NULL COLLATE utf8_bin,
atomIndex INT UNSIGNED NOT NULL,
identityHash VARCHAR(12) NOT NULL COLLATE utf8_bin,
graphHash VARCHAR(33) COLLATE utf8_bin,
KEY (bookPHID, type, name(64), context(64), atomIndex),
KEY (name),
UNIQUE KEY (graphHash),
UNIQUE KEY (identityHash),
UNIQUE KEY (phid)
) ENGINE=InnoDB, DEFAULT CHARSET = utf8;
CREATE TABLE {$NAMESPACE}_diviner.diviner_liveatom (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
symbolPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
content LONGTEXT NOT NULL COLLATE utf8_bin,
atomData LONGTEXT NOT NULL COLLATE utf8_bin,
UNIQUE KEY (symbolPHID)
) ENGINE=InnoDB, DEFAULT CHARSET = utf8;
-
-
diff --git a/resources/sql/patches/20130606.userxactions.sql b/resources/sql/patches/20130606.userxactions.sql
index 3d266e826c..5659c5e56c 100644
--- a/resources/sql/patches/20130606.userxactions.sql
+++ b/resources/sql/patches/20130606.userxactions.sql
@@ -1,23 +1,21 @@
CREATE TABLE {$NAMESPACE}_user.user_transaction (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
commentPHID VARCHAR(64) COLLATE utf8_bin,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin,
oldValue LONGTEXT NOT NULL COLLATE utf8_bin,
newValue LONGTEXT NOT NULL COLLATE utf8_bin,
metadata LONGTEXT NOT NULL COLLATE utf8_bin,
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
KEY `key_object` (objectPHID)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
-
-
diff --git a/resources/sql/patches/20130620.diffxactions.sql b/resources/sql/patches/20130620.diffxactions.sql
index 7b63f895f6..bd88123b35 100644
--- a/resources/sql/patches/20130620.diffxactions.sql
+++ b/resources/sql/patches/20130620.diffxactions.sql
@@ -1,51 +1,50 @@
CREATE TABLE {$NAMESPACE}_differential.differential_transaction (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
commentPHID VARCHAR(64) COLLATE utf8_bin,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin,
oldValue LONGTEXT NOT NULL COLLATE utf8_bin,
newValue LONGTEXT NOT NULL COLLATE utf8_bin,
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
metadata LONGTEXT NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
KEY `key_object` (objectPHID)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
CREATE TABLE {$NAMESPACE}_differential.differential_transaction_comment (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
transactionPHID VARCHAR(64) COLLATE utf8_bin,
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
commentVersion INT UNSIGNED NOT NULL,
content LONGTEXT NOT NULL COLLATE utf8_bin,
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
isDeleted BOOL NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
revisionPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
changesetID INT UNSIGNED,
isNewFile BOOL NOT NULL,
lineNumber INT UNSIGNED NOT NULL,
lineLength INT UNSIGNED NOT NULL,
fixedState VARCHAR(12) COLLATE utf8_bin,
hasReplies BOOL NOT NULL,
replyToCommentPHID VARCHAR(64),
UNIQUE KEY `key_phid` (phid),
UNIQUE KEY `key_version` (transactionPHID, commentVersion),
UNIQUE KEY `key_draft` (authorPHID, revisionPHID, transactionPHID)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
-
diff --git a/resources/sql/patches/20130622.doorkeeper.sql b/resources/sql/patches/20130622.doorkeeper.sql
index 80329f03d5..6125e613cc 100644
--- a/resources/sql/patches/20130622.doorkeeper.sql
+++ b/resources/sql/patches/20130622.doorkeeper.sql
@@ -1,35 +1,34 @@
CREATE TABLE {$NAMESPACE}_doorkeeper.doorkeeper_externalobject (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
objectKey CHAR(12) NOT NULL COLLATE utf8_bin,
applicationType VARCHAR(32) NOT NULL COLLATE utf8_bin,
applicationDomain VARCHAR(32) NOT NULL COLLATE utf8_bin,
objectType VARCHAR(32) NOT NULL COLLATE utf8_bin,
objectID VARCHAR(64) NOT NULL COLLATE utf8_bin,
objectURI VARCHAR(128) COLLATE utf8_bin,
importerPHID VARCHAR(64) COLLATE utf8_bin,
properties LONGTEXT NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
UNIQUE KEY `key_object` (objectKey),
KEY `key_full` (applicationType, applicationDomain, objectType, objectID)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
CREATE TABLE {$NAMESPACE}_doorkeeper.edge (
src VARCHAR(64) NOT NULL COLLATE utf8_bin,
type INT UNSIGNED NOT NULL COLLATE utf8_bin,
dst VARCHAR(64) NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
seq INT UNSIGNED NOT NULL,
dataID INT UNSIGNED,
PRIMARY KEY (src, type, dst),
KEY (src, type, dateCreated, seq)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
CREATE TABLE {$NAMESPACE}_doorkeeper.edgedata (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
data LONGTEXT NOT NULL COLLATE utf8_bin
) ENGINE=InnoDB, COLLATE utf8_general_ci;
-
diff --git a/resources/sql/patches/20130628.legalpadv0.sql b/resources/sql/patches/20130628.legalpadv0.sql
index fa843f18d9..723e25c552 100644
--- a/resources/sql/patches/20130628.legalpadv0.sql
+++ b/resources/sql/patches/20130628.legalpadv0.sql
@@ -1,104 +1,103 @@
CREATE TABLE {$NAMESPACE}_legalpad.legalpad_document (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
creatorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
versions INT UNSIGNED NOT NULL DEFAULT 0,
documentBodyPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
UNIQUE KEY `key_creator` (creatorPHID, dateModified)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE {$NAMESPACE}_legalpad.legalpad_documentbody (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
creatorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
documentPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
version INT UNSIGNED NOT NULL DEFAULT 0,
title VARCHAR(255) NOT NULL COLLATE utf8_general_ci,
text LONGTEXT NULL COLLATE utf8_general_ci,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
UNIQUE KEY `key_document` (documentPHID, version)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE {$NAMESPACE}_legalpad.legalpad_documentsignature (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
documentPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
documentVersion INT UNSIGNED NOT NULL DEFAULT 0,
signerPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_document` (documentPHID, documentVersion, signerPHID),
KEY `key_signer` (signerPHID, dateModified)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE {$NAMESPACE}_legalpad.edge (
src VARCHAR(64) NOT NULL COLLATE utf8_bin,
type VARCHAR(64) NOT NULL COLLATE utf8_bin,
dst VARCHAR(64) NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
seq INT UNSIGNED NOT NULL,
dataID INT UNSIGNED,
PRIMARY KEY (src, type, dst),
KEY (src, type, dateCreated, seq)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
CREATE TABLE {$NAMESPACE}_legalpad.edgedata (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
data LONGTEXT NOT NULL COLLATE utf8_bin
) ENGINE=InnoDB, COLLATE utf8_general_ci;
CREATE TABLE {$NAMESPACE}_legalpad.legalpad_transaction (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
commentPHID VARCHAR(64) COLLATE utf8_bin,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin,
oldValue LONGTEXT NOT NULL COLLATE utf8_bin,
newValue LONGTEXT NOT NULL COLLATE utf8_bin,
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
metadata LONGTEXT NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
KEY `key_object` (objectPHID)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
CREATE TABLE {$NAMESPACE}_legalpad.legalpad_transaction_comment (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
transactionPHID VARCHAR(64) COLLATE utf8_bin,
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
commentVersion INT UNSIGNED NOT NULL,
content LONGTEXT NOT NULL COLLATE utf8_bin,
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
isDeleted BOOL NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
documentID INT UNSIGNED,
lineNumber INT UNSIGNED NOT NULL,
lineLength INT UNSIGNED NOT NULL,
fixedState VARCHAR(12) COLLATE utf8_bin,
hasReplies BOOL NOT NULL,
replyToCommentPHID VARCHAR(64),
UNIQUE KEY `key_phid` (phid),
UNIQUE KEY `key_version` (transactionPHID, commentVersion),
UNIQUE KEY `key_draft` (authorPHID, documentID, transactionPHID)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
-
diff --git a/resources/sql/patches/20130723.taskstarttime.sql b/resources/sql/patches/20130723.taskstarttime.sql
index eee39b8e82..2ca759db1e 100644
--- a/resources/sql/patches/20130723.taskstarttime.sql
+++ b/resources/sql/patches/20130723.taskstarttime.sql
@@ -1,6 +1,5 @@
ALTER TABLE {$NAMESPACE}_worker.worker_activetask
ADD failureTime INT UNSIGNED;
ALTER TABLE {$NAMESPACE}_worker.worker_activetask
ADD KEY `key_failuretime` (`failureTime`);
-
diff --git a/resources/sql/patches/20130726.ponderxactions.sql b/resources/sql/patches/20130726.ponderxactions.sql
index a3caf75d75..107eae2c16 100644
--- a/resources/sql/patches/20130726.ponderxactions.sql
+++ b/resources/sql/patches/20130726.ponderxactions.sql
@@ -1,82 +1,81 @@
CREATE TABLE {$NAMESPACE}_ponder.ponder_questiontransaction (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
commentPHID VARCHAR(64) COLLATE utf8_bin,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin,
oldValue LONGTEXT NOT NULL COLLATE utf8_bin,
newValue LONGTEXT NOT NULL COLLATE utf8_bin,
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
metadata LONGTEXT NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
KEY `key_object` (objectPHID)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
CREATE TABLE {$NAMESPACE}_ponder.ponder_questiontransaction_comment (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
transactionPHID VARCHAR(64) COLLATE utf8_bin,
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
commentVersion INT UNSIGNED NOT NULL,
content LONGTEXT NOT NULL COLLATE utf8_bin,
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
isDeleted BOOL NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
UNIQUE KEY `key_version` (transactionPHID, commentVersion)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
CREATE TABLE {$NAMESPACE}_ponder.ponder_answertransaction (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
commentPHID VARCHAR(64) COLLATE utf8_bin,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin,
oldValue LONGTEXT NOT NULL COLLATE utf8_bin,
newValue LONGTEXT NOT NULL COLLATE utf8_bin,
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
metadata LONGTEXT NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
KEY `key_object` (objectPHID)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
CREATE TABLE {$NAMESPACE}_ponder.ponder_answertransaction_comment (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
transactionPHID VARCHAR(64) COLLATE utf8_bin,
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
commentVersion INT UNSIGNED NOT NULL,
content LONGTEXT NOT NULL COLLATE utf8_bin,
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
isDeleted BOOL NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
UNIQUE KEY `key_version` (transactionPHID, commentVersion)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
-
diff --git a/resources/sql/patches/20130820.filexactions.sql b/resources/sql/patches/20130820.filexactions.sql
index 8f92df563f..5febdc2710 100644
--- a/resources/sql/patches/20130820.filexactions.sql
+++ b/resources/sql/patches/20130820.filexactions.sql
@@ -1,42 +1,41 @@
CREATE TABLE {$NAMESPACE}_file.file_transaction (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
commentPHID VARCHAR(64) COLLATE utf8_bin,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin,
oldValue LONGTEXT NOT NULL COLLATE utf8_bin,
newValue LONGTEXT NOT NULL COLLATE utf8_bin,
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
metadata LONGTEXT NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
KEY `key_object` (objectPHID)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
CREATE TABLE {$NAMESPACE}_file.file_transaction_comment (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
transactionPHID VARCHAR(64) COLLATE utf8_bin,
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
commentVersion INT UNSIGNED NOT NULL,
content LONGTEXT NOT NULL COLLATE utf8_bin,
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
isDeleted BOOL NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
UNIQUE KEY `key_version` (transactionPHID, commentVersion),
UNIQUE KEY `key_draft` (authorPHID, transactionPHID)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
-
diff --git a/resources/sql/patches/20130926.dinkeys.sql b/resources/sql/patches/20130926.dinkeys.sql
index a96b7be9c0..7d7caf8b87 100644
--- a/resources/sql/patches/20130926.dinkeys.sql
+++ b/resources/sql/patches/20130926.dinkeys.sql
@@ -1,15 +1,14 @@
ALTER TABLE {$NAMESPACE}_differential.differential_transaction_comment
DROP KEY `key_draft`;
ALTER TABLE {$NAMESPACE}_differential.differential_transaction_comment
ADD KEY `key_changeset` (changesetID);
ALTER TABLE {$NAMESPACE}_differential.differential_transaction_comment
ADD KEY `key_draft` (authorPHID, transactionPHID);
ALTER TABLE {$NAMESPACE}_differential.differential_transaction_comment
ADD KEY `key_revision` (revisionPHID);
ALTER TABLE {$NAMESPACE}_differential.differential_transaction_comment
ADD KEY `key_legacy` (legacyCommentID);
-
diff --git a/resources/sql/patches/20131030.repostatusmessage.sql b/resources/sql/patches/20131030.repostatusmessage.sql
index 159f5dadb1..17ee068368 100644
--- a/resources/sql/patches/20131030.repostatusmessage.sql
+++ b/resources/sql/patches/20131030.repostatusmessage.sql
@@ -1,10 +1,9 @@
CREATE TABLE {$NAMESPACE}_repository.repository_statusmessage (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
repositoryID INT UNSIGNED NOT NULL,
statusType VARCHAR(32) NOT NULL COLLATE utf8_bin,
statusCode VARCHAR(32) NOT NULL COLLATE utf8_bin,
parameters LONGTEXT NOT NULL,
epoch INT UNSIGNED NOT NULL,
UNIQUE KEY (repositoryID, statusType)
) ENGINE=InnoDB, CHARSET utf8;
-
diff --git a/resources/sql/patches/20131224.harbormanual.sql b/resources/sql/patches/20131224.harbormanual.sql
index fb5a5ee970..0ce5aba968 100644
--- a/resources/sql/patches/20131224.harbormanual.sql
+++ b/resources/sql/patches/20131224.harbormanual.sql
@@ -1,6 +1,5 @@
ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildable
ADD isManualBuildable BOOL NOT NULL;
ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildable
ADD KEY `key_manual` (isManualBuildable);
-
diff --git a/resources/sql/patches/20131227.heraldobject.sql b/resources/sql/patches/20131227.heraldobject.sql
index b53d95ab86..04f886d9df 100644
--- a/resources/sql/patches/20131227.heraldobject.sql
+++ b/resources/sql/patches/20131227.heraldobject.sql
@@ -1,6 +1,5 @@
ALTER TABLE {$NAMESPACE}_herald.herald_rule
ADD triggerObjectPHID VARCHAR(64) COLLATE utf8_bin;
ALTER TABLE {$NAMESPACE}_herald.herald_rule
ADD KEY `key_trigger` (triggerObjectPHID);
-
diff --git a/resources/sql/patches/20131302.maniphestvalue.sql b/resources/sql/patches/20131302.maniphestvalue.sql
index 43f703a06d..bfcb9336f9 100644
--- a/resources/sql/patches/20131302.maniphestvalue.sql
+++ b/resources/sql/patches/20131302.maniphestvalue.sql
@@ -1,3 +1,2 @@
ALTER TABLE {$NAMESPACE}_maniphest.maniphest_taskauxiliarystorage
MODIFY value LONGTEXT COLLATE utf8_bin;
-
diff --git a/resources/sql/patches/emailtableremove.sql b/resources/sql/patches/emailtableremove.sql
index be31125fec..9712d660dc 100644
--- a/resources/sql/patches/emailtableremove.sql
+++ b/resources/sql/patches/emailtableremove.sql
@@ -1 +1 @@
-ALTER TABLE {$NAMESPACE}_user.user DROP email;
\ No newline at end of file
+ALTER TABLE {$NAMESPACE}_user.user DROP email;
diff --git a/resources/sql/patches/phameblog.sql b/resources/sql/patches/phameblog.sql
index b54d76c8ed..74ae9c8f89 100644
--- a/resources/sql/patches/phameblog.sql
+++ b/resources/sql/patches/phameblog.sql
@@ -1,31 +1,30 @@
CREATE TABLE {$NAMESPACE}_phame.phame_blog (
`id` INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`phid` VARCHAR(64) NOT NULL COLLATE utf8_bin,
`name` VARCHAR(64) NOT NULL COLLATE utf8_bin,
`description` LONGTEXT NOT NULL COLLATE utf8_bin,
`configData` LONGTEXT NOT NULL COLLATE utf8_bin,
`creatorPHID` VARCHAR(64) NOT NULL COLLATE utf8_bin,
`dateCreated` INT UNSIGNED NOT NULL,
`dateModified` INT UNSIGNED NOT NULL,
UNIQUE KEY (`phid`)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
CREATE TABLE {$NAMESPACE}_phame.edge (
src VARCHAR(64) NOT NULL COLLATE utf8_bin,
type VARCHAR(64) NOT NULL COLLATE utf8_bin,
dst VARCHAR(64) NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
seq INT UNSIGNED NOT NULL,
dataID INT UNSIGNED,
PRIMARY KEY (src, type, dst),
KEY (src, type, dateCreated, seq)
) ENGINE=InnoDB, COLLATE utf8_general_ci;
CREATE TABLE {$NAMESPACE}_phame.edgedata (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
data LONGTEXT NOT NULL COLLATE utf8_bin
) ENGINE=InnoDB, COLLATE utf8_general_ci;
ALTER TABLE {$NAMESPACE}_phame.phame_post
ADD KEY `instancePosts` (`visibility`, `datePublished`, `id`);
-
diff --git a/resources/sql/patches/phamedomain.sql b/resources/sql/patches/phamedomain.sql
index 066fa37e46..1feddd7ab4 100644
--- a/resources/sql/patches/phamedomain.sql
+++ b/resources/sql/patches/phamedomain.sql
@@ -1,4 +1,4 @@
ALTER TABLE `{$NAMESPACE}_phame`.`phame_blog`
- ADD COLUMN `domain` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin
+ ADD COLUMN `domain` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin
AFTER `description`,
ADD UNIQUE KEY (`domain`);
diff --git a/resources/sql/patches/phamepolicy.sql b/resources/sql/patches/phamepolicy.sql
index d48bdf2223..be86021e4e 100644
--- a/resources/sql/patches/phamepolicy.sql
+++ b/resources/sql/patches/phamepolicy.sql
@@ -1,18 +1,17 @@
ALTER TABLE `{$NAMESPACE}_phame`.`phame_blog`
ADD `viewPolicy` varchar(64) COLLATE utf8_bin;
UPDATE `{$NAMESPACE}_phame`.`phame_blog` SET viewPolicy = 'users'
WHERE viewPolicy IS NULL;
ALTER TABLE `{$NAMESPACE}_phame`.`phame_blog`
ADD `editPolicy` varchar(64) COLLATE utf8_bin;
UPDATE `{$NAMESPACE}_phame`.`phame_blog` SET editPolicy = 'users'
WHERE editPolicy IS NULL;
ALTER TABLE `{$NAMESPACE}_phame`.`phame_blog`
ADD `joinPolicy` varchar(64) COLLATE utf8_bin;
UPDATE `{$NAMESPACE}_phame`.`phame_blog` SET joinPolicy = 'users'
WHERE joinPolicy IS NULL;
-
diff --git a/resources/sql/patches/phiddrop.sql b/resources/sql/patches/phiddrop.sql
index 53c95e5c20..6f3c3344d2 100644
--- a/resources/sql/patches/phiddrop.sql
+++ b/resources/sql/patches/phiddrop.sql
@@ -1 +1 @@
-DROP DATABASE IF EXISTS {$NAMESPACE}_phid;
\ No newline at end of file
+DROP DATABASE IF EXISTS {$NAMESPACE}_phid;
diff --git a/resources/sql/patches/ponder-comments.sql b/resources/sql/patches/ponder-comments.sql
index 1f21bfc3e6..b93092f67b 100644
--- a/resources/sql/patches/ponder-comments.sql
+++ b/resources/sql/patches/ponder-comments.sql
@@ -1,11 +1,11 @@
CREATE TABLE `{$NAMESPACE}_ponder`.`ponder_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`authorPHID` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`targetPHID` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`content` longtext CHARACTER SET utf8 NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `authorPHID` (`authorPHID`),
KEY `targetPHID` (`targetPHID`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8;
\ No newline at end of file
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
diff --git a/resources/sql/patches/ponder.sql b/resources/sql/patches/ponder.sql
index 86e36a075b..868dbe66ce 100644
--- a/resources/sql/patches/ponder.sql
+++ b/resources/sql/patches/ponder.sql
@@ -1,50 +1,50 @@
CREATE TABLE `{$NAMESPACE}_ponder`.`ponder_question` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`phid` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`voteCount` int(10) NOT NULL,
`authorPHID` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`content` longtext CHARACTER SET utf8 NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`contentSource` varchar(255) DEFAULT NULL,
`heat` float NOT NULL,
`answerCount` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `phid` (`phid`),
KEY `authorPHID` (`authorPHID`),
KEY `heat` (`heat`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=11;
CREATE TABLE `{$NAMESPACE}_ponder`.`ponder_answer` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`questionID` int(10) unsigned NOT NULL,
`phid` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`voteCount` int(10) NOT NULL,
`authorPHID` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`content` longtext CHARACTER SET utf8 NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`contentSource` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `phid` (`phid`),
KEY `questionID` (`questionID`),
KEY `authorPHID` (`authorPHID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `{$NAMESPACE}_ponder`.`edge` (
`src` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`type` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`dst` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `{$NAMESPACE}_ponder`.`edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8;
\ No newline at end of file
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
diff --git a/scripts/differential/destroy_revision.php b/scripts/differential/destroy_revision.php
index af97de3932..8f24e6d17d 100755
--- a/scripts/differential/destroy_revision.php
+++ b/scripts/differential/destroy_revision.php
@@ -1,52 +1,51 @@
#!/usr/bin/env php
<?php
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
$args = new PhutilArgumentParser($argv);
$args->setTagline('permanently destroy a Differential Revision');
$args->setSynopsis(<<<EOHELP
**destroy_revision.php** __D123__
Permanently destroy the specified Differential Revision (for example,
because it contains secrets that the world is not ready to know).
Normally, you can just "Abandon" unwanted revisions, but in dire
circumstances this script can be used to completely destroy a
revision. Destroying a revision may cause some glitches in
linked objects.
The revision is utterly destroyed and can not be recovered unless you
have backups.
EOHELP
);
$args->parseStandardArguments();
$args->parse(
array(
array(
'name' => 'revision',
'wildcard' => true,
),
));
$revisions = $args->getArg('revision');
if (count($revisions) != 1) {
$args->printHelpAndExit();
}
$id = trim(strtolower(head($revisions)), 'd ');
$revision = id(new DifferentialRevision())->load($id);
if (!$revision) {
throw new Exception("No revision '{$id}' exists!");
}
$title = $revision->getTitle();
$ok = phutil_console_confirm("Really destroy 'D{$id}: {$title}' forever?");
if (!$ok) {
throw new Exception("User aborted workflow.");
}
$revision->delete();
echo "OK, destroyed revision.\n";
-
diff --git a/scripts/mail/mail_handler.php b/scripts/mail/mail_handler.php
index 11d8f1c29f..63d6688a5c 100755
--- a/scripts/mail/mail_handler.php
+++ b/scripts/mail/mail_handler.php
@@ -1,96 +1,94 @@
#!/usr/bin/env php
<?php
// NOTE: This script is very oldschool and takes the environment as an argument.
// Some day, we could take a shot at cleaning this up.
if ($argc > 1) {
foreach (array_slice($argv, 1) as $arg) {
if (!preg_match('/^-/', $arg)) {
$_SERVER['PHABRICATOR_ENV'] = $arg;
break;
}
}
}
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
require_once $root.'/externals/mimemailparser/MimeMailParser.class.php';
$args = new PhutilArgumentParser($argv);
$args->parseStandardArguments();
$args->parse(
array(
array(
'name' => 'process-duplicates',
'help' => pht(
"Process this message, even if it's a duplicate of another message. ".
"This is mostly useful when debugging issues with mail routing."),
),
array(
'name' => 'env',
'wildcard' => true,
),
));
$parser = new MimeMailParser();
$parser->setText(file_get_contents('php://stdin'));
$text_body = $parser->getMessageBody('text');
$text_body_headers = $parser->getMessageBodyHeaders('text');
$content_type = idx($text_body_headers, 'content-type');
if (
!phutil_is_utf8($text_body) &&
(preg_match('/charset="(.*?)"/', $content_type, $matches) ||
preg_match('/charset=(\S+)/', $content_type, $matches))
) {
$text_body = phutil_utf8_convert($text_body, "UTF-8", $matches[1]);
}
$headers = $parser->getHeaders();
$headers['subject'] = iconv_mime_decode($headers['subject'], 0, "UTF-8");
$headers['from'] = iconv_mime_decode($headers['from'], 0, "UTF-8");
if ($args->getArg('process-duplicates')) {
$headers['message-id'] = Filesystem::readRandomCharacters(64);
}
$received = new PhabricatorMetaMTAReceivedMail();
$received->setHeaders($headers);
$received->setBodies(array(
'text' => $text_body,
'html' => $parser->getMessageBody('html'),
));
$attachments = array();
foreach ($parser->getAttachments() as $attachment) {
if (preg_match('@text/(plain|html)@', $attachment->getContentType()) &&
$attachment->getContentDisposition() == 'inline') {
// If this is an "inline" attachment with some sort of text content-type,
// do not treat it as a file for attachment. MimeMailParser already picked
// it up in the getMessageBody() call above. We still want to treat 'inline'
// attachments with other content types (e.g., images) as attachments.
continue;
}
$file = PhabricatorFile::newFromFileData(
$attachment->getContent(),
array(
'name' => $attachment->getFilename(),
));
$attachments[] = $file->getPHID();
}
try {
$received->setAttachments($attachments);
$received->save();
$received->processReceivedMail();
} catch (Exception $e) {
$received
->setMessage('EXCEPTION: '.$e->getMessage())
->save();
throw $e;
}
-
-
diff --git a/scripts/mail/manage_mail.php b/scripts/mail/manage_mail.php
index fefb1e0da3..9822565175 100755
--- a/scripts/mail/manage_mail.php
+++ b/scripts/mail/manage_mail.php
@@ -1,22 +1,21 @@
#!/usr/bin/env php
<?php
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
$args = new PhutilArgumentParser($argv);
$args->setTagline('manage mail');
$args->setSynopsis(<<<EOSYNOPSIS
**mail** __command__ [__options__]
Manage Phabricator mail stuff.
EOSYNOPSIS
);
$args->parseStandardArguments();
$workflows = id(new PhutilSymbolLoader())
->setAncestorClass('PhabricatorMailManagementWorkflow')
->loadObjects();
$workflows[] = new PhutilHelpArgumentWorkflow();
$args->parseWorkflows($workflows);
-
diff --git a/scripts/repository/test_connection.php b/scripts/repository/test_connection.php
index 6047f3429d..226a967c78 100755
--- a/scripts/repository/test_connection.php
+++ b/scripts/repository/test_connection.php
@@ -1,6 +1,5 @@
#!/usr/bin/env php
<?php
echo "This script is obsolete. Use `bin/repository` to manage repositories.\n";
exit(1);
-
diff --git a/scripts/search/reindex_maniphest.php b/scripts/search/reindex_maniphest.php
index ed4670b690..a99e79e342 100755
--- a/scripts/search/reindex_maniphest.php
+++ b/scripts/search/reindex_maniphest.php
@@ -1,16 +1,15 @@
#!/usr/bin/env php
<?php
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
ini_set('memory_limit', -1);
$tasks = id(new ManiphestTask())->loadAll();
echo "Updating relationships for ".count($tasks)." tasks";
foreach ($tasks as $task) {
ManiphestTaskProject::updateTaskProjects($task);
ManiphestTaskSubscriber::updateTaskSubscribers($task);
echo '.';
}
echo "\nDone.\n";
-
diff --git a/src/aphront/console/DarkConsoleCore.php b/src/aphront/console/DarkConsoleCore.php
index 3501f514ea..3389109ebf 100644
--- a/src/aphront/console/DarkConsoleCore.php
+++ b/src/aphront/console/DarkConsoleCore.php
@@ -1,138 +1,137 @@
<?php
/**
* @group console
*/
final class DarkConsoleCore {
private $plugins = array();
const STORAGE_VERSION = 1;
public function __construct() {
$symbols = id(new PhutilSymbolLoader())
->setType('class')
->setAncestorClass('DarkConsolePlugin')
->selectAndLoadSymbols();
foreach ($symbols as $symbol) {
$plugin = newv($symbol['name'], array());
if (!$plugin->shouldStartup()) {
continue;
}
$plugin->setConsoleCore($this);
$plugin->didStartup();
$this->plugins[$symbol['name']] = $plugin;
}
}
public function getPlugins() {
return $this->plugins;
}
public function getKey(AphrontRequest $request) {
$plugins = $this->getPlugins();
foreach ($plugins as $plugin) {
$plugin->setRequest($request);
$plugin->willShutdown();
}
foreach ($plugins as $plugin) {
$plugin->didShutdown();
}
foreach ($plugins as $plugin) {
$plugin->setData($plugin->generateData());
}
$plugins = msort($plugins, 'getOrderKey');
$key = Filesystem::readRandomCharacters(24);
$tabs = array();
$data = array();
foreach ($plugins as $plugin) {
$class = get_class($plugin);
$tabs[] = array(
'class' => $class,
'name' => $plugin->getName(),
'color' => $plugin->getColor(),
);
$data[$class] = $this->sanitizeForJSON($plugin->getData());
}
$storage = array(
'vers' => self::STORAGE_VERSION,
'tabs' => $tabs,
'data' => $data,
'user' => $request->getUser()
? $request->getUser()->getPHID()
: null,
);
$cache = new PhabricatorKeyValueDatabaseCache();
$cache = new PhutilKeyValueCacheProfiler($cache);
$cache->setProfiler(PhutilServiceProfiler::getInstance());
// This encoding may fail if there are, e.g., database queries which
// include binary data. It would be a little cleaner to try to strip these,
// but just do something non-broken here if we end up with unrepresentable
// data.
$json = @json_encode($storage);
if (!$json) {
$json = '{}';
}
$cache->setKeys(
array(
'darkconsole:'.$key => $json,
),
$ttl = (60 * 60 * 6));
return $key;
}
public function getColor() {
foreach ($this->getPlugins() as $plugin) {
if ($plugin->getColor()) {
return $plugin->getColor();
}
}
}
public function render(AphrontRequest $request) {
$user = $request->getUser();
$visible = $user ? $user->getConsoleVisible() : true;
return javelin_tag(
'div',
array(
'id' => 'darkconsole',
'class' => 'dark-console',
'style' => $visible ? '' : 'display: none;',
'data-console-key' => $this->getKey($request),
'data-console-color' => $this->getColor(),
),
'');
}
/**
* Sometimes, tab data includes binary information (like INSERT queries which
* write file data into the database). To successfully JSON encode it, we
* need to convert it to UTF-8.
*/
private function sanitizeForJSON($data) {
if (is_object($data)) {
return '<object:'.get_class($data).'>';
} else if (is_array($data)) {
foreach ($data as $key => $value) {
$data[$key] = $this->sanitizeForJSON($value);
}
return $data;
} else {
return phutil_utf8ize($data);
}
}
}
-
diff --git a/src/aphront/console/plugin/DarkConsoleServicesPlugin.php b/src/aphront/console/plugin/DarkConsoleServicesPlugin.php
index 38538f0564..e0f760d271 100644
--- a/src/aphront/console/plugin/DarkConsoleServicesPlugin.php
+++ b/src/aphront/console/plugin/DarkConsoleServicesPlugin.php
@@ -1,296 +1,295 @@
<?php
/**
* @group console
*/
final class DarkConsoleServicesPlugin extends DarkConsolePlugin {
protected $observations;
public function getName() {
return 'Services';
}
public function getDescription() {
return 'Information about services.';
}
public static function getQueryAnalyzerHeader() {
return 'X-Phabricator-QueryAnalyzer';
}
public static function isQueryAnalyzerRequested() {
if (!empty($_REQUEST['__analyze__'])) {
return true;
}
$header = AphrontRequest::getHTTPHeader(self::getQueryAnalyzerHeader());
if ($header) {
return true;
}
return false;
}
/**
* @phutil-external-symbol class PhabricatorStartup
*/
public function generateData() {
$should_analyze = self::isQueryAnalyzerRequested();
$log = PhutilServiceProfiler::getInstance()->getServiceCallLog();
foreach ($log as $key => $entry) {
$config = idx($entry, 'config', array());
unset($log[$key]['config']);
if (!$should_analyze) {
$log[$key]['explain'] = array(
'sev' => 7,
'size' => null,
'reason' => 'Disabled',
);
// Query analysis is disabled for this request, so don't do any of it.
continue;
}
if ($entry['type'] != 'query') {
continue;
}
// For each SELECT query, go issue an EXPLAIN on it so we can flag stuff
// causing table scans, etc.
if (preg_match('/^\s*SELECT\b/i', $entry['query'])) {
$conn = PhabricatorEnv::newObjectFromConfig(
'mysql.implementation',
array($entry['config']));
try {
$explain = queryfx_all(
$conn,
'EXPLAIN %Q',
$entry['query']);
$badness = 0;
$size = 1;
$reason = null;
foreach ($explain as $table) {
$size *= (int)$table['rows'];
switch ($table['type']) {
case 'index':
$cur_badness = 1;
$cur_reason = 'Index';
break;
case 'const':
$cur_badness = 1;
$cur_reason = 'Const';
break;
case 'eq_ref';
$cur_badness = 2;
$cur_reason = 'EqRef';
break;
case 'range':
$cur_badness = 3;
$cur_reason = 'Range';
break;
case 'ref':
$cur_badness = 3;
$cur_reason = 'Ref';
break;
case 'fulltext':
$cur_badness = 3;
$cur_reason = 'Fulltext';
break;
case 'ALL':
if (preg_match('/Using where/', $table['Extra'])) {
if ($table['rows'] < 256 && !empty($table['possible_keys'])) {
$cur_badness = 2;
$cur_reason = 'Small Table Scan';
} else {
$cur_badness = 6;
$cur_reason = 'TABLE SCAN!';
}
} else {
$cur_badness = 3;
$cur_reason = 'Whole Table';
}
break;
default:
if (preg_match('/No tables used/i', $table['Extra'])) {
$cur_badness = 1;
$cur_reason = 'No Tables';
} else if (preg_match('/Impossible/i', $table['Extra'])) {
$cur_badness = 1;
$cur_reason = 'Empty';
} else {
$cur_badness = 4;
$cur_reason = "Can't Analyze";
}
break;
}
if ($cur_badness > $badness) {
$badness = $cur_badness;
$reason = $cur_reason;
}
}
$log[$key]['explain'] = array(
'sev' => $badness,
'size' => $size,
'reason' => $reason,
);
} catch (Exception $ex) {
$log[$key]['explain'] = array(
'sev' => 5,
'size' => null,
'reason' => $ex->getMessage(),
);
}
}
}
return array(
'start' => PhabricatorStartup::getStartTime(),
'end' => microtime(true),
'log' => $log,
'analyzeURI' => (string)$this
->getRequestURI()
->alter('__analyze__', true),
'didAnalyze' => $should_analyze,
);
}
public function renderPanel() {
$data = $this->getData();
$log = $data['log'];
$results = array();
$results[] = phutil_tag(
'div',
array('class' => 'dark-console-panel-header'),
array(
phutil_tag(
'a',
array(
'href' => $data['analyzeURI'],
'class' => $data['didAnalyze'] ? 'disabled button' : 'green button',
),
pht('Analyze Query Plans')),
phutil_tag('h1', array(), pht('Calls to External Services')),
phutil_tag('div', array('style' => 'clear: both;')),
));
$page_total = $data['end'] - $data['start'];
$totals = array();
$counts = array();
foreach ($log as $row) {
$totals[$row['type']] = idx($totals, $row['type'], 0) + $row['duration'];
$counts[$row['type']] = idx($counts, $row['type'], 0) + 1;
}
$totals['All Services'] = array_sum($totals);
$counts['All Services'] = array_sum($counts);
$totals['Entire Page'] = $page_total;
$counts['Entire Page'] = 0;
$summary = array();
foreach ($totals as $type => $total) {
$summary[] = array(
$type,
number_format($counts[$type]),
number_format((int)(1000000 * $totals[$type])).' us',
sprintf('%.1f%%', 100 * $totals[$type] / $page_total),
);
}
$summary_table = new AphrontTableView($summary);
$summary_table->setColumnClasses(
array(
'',
'n',
'n',
'wide',
));
$summary_table->setHeaders(
array(
'Type',
'Count',
'Total Cost',
'Page Weight',
));
$results[] = $summary_table->render();
$rows = array();
foreach ($log as $row) {
$analysis = null;
switch ($row['type']) {
case 'query':
$info = $row['query'];
$info = wordwrap($info, 128, "\n", true);
if (!empty($row['explain'])) {
$analysis = phutil_tag(
'span',
array(
'class' => 'explain-sev-'.$row['explain']['sev'],
),
$row['explain']['reason']);
}
break;
case 'connect':
$info = $row['host'].':'.$row['database'];
break;
case 'exec':
$info = $row['command'];
break;
case 'conduit':
$info = $row['method'];
break;
case 'http':
$info = $row['uri'];
break;
default:
$info = '-';
break;
}
$rows[] = array(
$row['type'],
'+'.number_format(1000 * ($row['begin'] - $data['start'])).' ms',
number_format(1000000 * $row['duration']).' us',
$info,
$analysis,
);
}
$table = new AphrontTableView($rows);
$table->setColumnClasses(
array(
null,
'n',
'n',
'wide',
'',
));
$table->setHeaders(
array(
'Event',
'Start',
'Duration',
'Details',
'Analysis',
));
$results[] = $table->render();
return phutil_implode_html("\n", $results);
}
}
-
diff --git a/src/aphront/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php b/src/aphront/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php
index 3855dd4a89..c65a4a27b8 100644
--- a/src/aphront/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php
+++ b/src/aphront/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php
@@ -1,79 +1,78 @@
<?php
/**
* @group console
*/
final class DarkConsoleErrorLogPluginAPI {
private static $errors = array();
private static $discardMode = false;
public static function registerErrorHandler() {
// NOTE: This forces PhutilReadableSerializer to load, so that we are
// able to handle errors which fire from inside autoloaders (PHP will not
// reenter autoloaders).
PhutilReadableSerializer::printableValue(null);
PhutilErrorHandler::setErrorListener(
array('DarkConsoleErrorLogPluginAPI', 'handleErrors'));
}
public static function enableDiscardMode() {
self::$discardMode = true;
}
public static function disableDiscardMode() {
self::$discardMode = false;
}
public static function getErrors() {
return self::$errors;
}
public static function handleErrors($event, $value, $metadata) {
if (self::$discardMode) {
return;
}
switch ($event) {
case PhutilErrorHandler::EXCEPTION:
// $value is of type Exception
self::$errors[] = array(
'details' => $value->getMessage(),
'event' => $event,
'file' => $value->getFile(),
'line' => $value->getLine(),
'str' => $value->getMessage(),
'trace' => $metadata['trace'],
);
break;
case PhutilErrorHandler::ERROR:
// $value is a simple string
self::$errors[] = array(
'details' => $value,
'event' => $event,
'file' => $metadata['file'],
'line' => $metadata['line'],
'str' => $value,
'trace' => $metadata['trace'],
);
break;
case PhutilErrorHandler::PHLOG:
// $value can be anything
self::$errors[] = array(
'details' => PhutilReadableSerializer::printShallow($value, 3),
'event' => $event,
'file' => $metadata['file'],
'line' => $metadata['line'],
'str' => PhutilReadableSerializer::printShort($value),
'trace' => $metadata['trace'],
);
break;
default:
error_log('Unknown event : '.$event);
break;
}
}
}
-
diff --git a/src/aphront/console/plugin/event/DarkConsoleEventPluginAPI.php b/src/aphront/console/plugin/event/DarkConsoleEventPluginAPI.php
index 3298fb46c0..afac58ceb4 100644
--- a/src/aphront/console/plugin/event/DarkConsoleEventPluginAPI.php
+++ b/src/aphront/console/plugin/event/DarkConsoleEventPluginAPI.php
@@ -1,31 +1,30 @@
<?php
/**
* @group console
*/
final class DarkConsoleEventPluginAPI extends PhabricatorEventListener {
private static $events = array();
private static $discardMode = false;
public static function enableDiscardMode() {
self::$discardMode = true;
}
public static function getEvents() {
return self::$events;
}
public function register() {
$this->listen(PhabricatorEventType::TYPE_ALL);
}
public function handleEvent(PhutilEvent $event) {
if (self::$discardMode) {
return;
}
self::$events[] = $event;
}
}
-
diff --git a/src/applications/audit/application/PhabricatorApplicationAudit.php b/src/applications/audit/application/PhabricatorApplicationAudit.php
index ce0a120b16..d568a7be3d 100644
--- a/src/applications/audit/application/PhabricatorApplicationAudit.php
+++ b/src/applications/audit/application/PhabricatorApplicationAudit.php
@@ -1,81 +1,80 @@
<?php
final class PhabricatorApplicationAudit extends PhabricatorApplication {
public function getShortDescription() {
return pht('Audit Code');
}
public function getBaseURI() {
return '/audit/';
}
public function getIconName() {
return 'audit';
}
public function getHelpURI() {
return PhabricatorEnv::getDoclink('article/Audit_User_Guide.html');
}
public function getEventListeners() {
return array(
new AuditActionMenuEventListener()
);
}
public function getRoutes() {
return array(
'/audit/' => array(
'' => 'PhabricatorAuditListController',
'view/(?P<filter>[^/]+)/(?:(?P<name>[^/]+)/)?'
=> 'PhabricatorAuditListController',
'addcomment/' => 'PhabricatorAuditAddCommentController',
'preview/(?P<id>[1-9]\d*)/' => 'PhabricatorAuditPreviewController',
),
);
}
public function getApplicationGroup() {
return self::GROUP_CORE;
}
public function getApplicationOrder() {
return 0.130;
}
public function loadStatus(PhabricatorUser $user) {
$status = array();
$phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
$commits = id(new PhabricatorAuditCommitQuery())
->withAuthorPHIDs($phids)
->withStatus(PhabricatorAuditCommitQuery::STATUS_CONCERN)
->execute();
$count = count($commits);
$type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION;
$status[] = id(new PhabricatorApplicationStatusView())
->setType($type)
->setText(pht('%d Problem Commit(s)', $count))
->setCount($count);
$audits = id(new PhabricatorAuditQuery())
->withAuditorPHIDs($phids)
->withStatus(PhabricatorAuditQuery::STATUS_OPEN)
->withAwaitingUser($user)
->execute();
$count = count($audits);
$type = PhabricatorApplicationStatusView::TYPE_WARNING;
$status[] = id(new PhabricatorApplicationStatusView())
->setType($type)
->setText(pht('%d Commit(s) Awaiting Audit', $count))
->setCount($count);
return $status;
}
}
-
diff --git a/src/applications/audit/events/AuditActionMenuEventListener.php b/src/applications/audit/events/AuditActionMenuEventListener.php
index 73a5e1a01b..97dfe7af67 100644
--- a/src/applications/audit/events/AuditActionMenuEventListener.php
+++ b/src/applications/audit/events/AuditActionMenuEventListener.php
@@ -1,46 +1,45 @@
<?php
final class AuditActionMenuEventListener extends PhabricatorEventListener {
public function register() {
$this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS);
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS:
$this->handleActionsEvent($event);
break;
}
}
private function handleActionsEvent(PhutilEvent $event) {
$object = $event->getValue('object');
$actions = null;
if ($object instanceof PhabricatorUser) {
$actions = $this->renderUserItems($event);
}
$this->addActionMenuItems($event, $actions);
}
private function renderUserItems(PhutilEvent $event) {
if (!$this->canUseApplication($event->getUser())) {
return null;
}
$user = $event->getValue('object');
$username = phutil_escape_uri($user->getUsername());
$view_uri = '/audit/view/author/'.$username.'/';
return id(new PhabricatorActionView())
->setIcon('audit-dark')
->setIconSheet(PHUIIconView::SPRITE_APPS)
->setName(pht('View Commits'))
->setHref($view_uri);
}
}
-
diff --git a/src/applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php b/src/applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php
index ff6564c95e..bb5b35b986 100644
--- a/src/applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php
+++ b/src/applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php
@@ -1,143 +1,142 @@
<?php
final class PhabricatorAuthProviderConfigTransaction
extends PhabricatorApplicationTransaction {
const TYPE_ENABLE = 'config:enable';
const TYPE_REGISTRATION = 'config:registration';
const TYPE_LINK = 'config:link';
const TYPE_UNLINK = 'config:unlink';
const TYPE_PROPERTY = 'config:property';
const PROPERTY_KEY = 'auth:property';
private $provider;
public function setProvider(PhabricatorAuthProvider $provider) {
$this->provider = $provider;
return $this;
}
public function getProvider() {
return $this->provider;
}
public function getApplicationName() {
return 'auth';
}
public function getApplicationTransactionType() {
return PhabricatorPHIDConstants::PHID_TYPE_AUTH;
}
public function getApplicationTransactionCommentObject() {
return null;
}
public function getIcon() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_ENABLE:
if ($new) {
return 'new';
} else {
return 'delete';
}
}
return parent::getIcon();
}
public function getColor() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_ENABLE:
if ($new) {
return 'green';
} else {
return 'red';
}
}
return parent::getColor();
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_ENABLE:
if ($old === null) {
return pht(
'%s created this provider.',
$this->renderHandleLink($author_phid));
} else if ($new) {
return pht(
'%s enabled this provider.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s disabled this provider.',
$this->renderHandleLink($author_phid));
}
break;
case self::TYPE_REGISTRATION:
if ($new) {
return pht(
'%s enabled registration.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s disabled registration.',
$this->renderHandleLink($author_phid));
}
break;
case self::TYPE_LINK:
if ($new) {
return pht(
'%s enabled accont linking.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s disabled account linking.',
$this->renderHandleLink($author_phid));
}
break;
case self::TYPE_UNLINK:
if ($new) {
return pht(
'%s enabled account unlinking.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s disabled account unlinking.',
$this->renderHandleLink($author_phid));
}
break;
case self::TYPE_PROPERTY:
$provider = $this->getProvider();
if ($provider) {
$title = $provider->renderConfigPropertyTransactionTitle($this);
if (strlen($title)) {
return $title;
}
}
return pht(
'%s edited a property of this provider.',
$this->renderHandleLink($author_phid));
break;
}
return parent::getTitle();
}
}
-
diff --git a/src/applications/base/controller/__tests__/PhabricatorApplicationTest.php b/src/applications/base/controller/__tests__/PhabricatorApplicationTest.php
index b20faaf553..d5e993bc4f 100644
--- a/src/applications/base/controller/__tests__/PhabricatorApplicationTest.php
+++ b/src/applications/base/controller/__tests__/PhabricatorApplicationTest.php
@@ -1,38 +1,37 @@
<?php
final class PhabricatorApplicationTest extends PhabricatorApplication {
private $policies = array();
public function isUnlisted() {
return true;
}
public function reset() {
$this->policies = array();
}
public function setPolicy($capability, $value) {
$this->policies[$capability] = $value;
return $this;
}
public function getPolicy($capability) {
return idx($this->policies, $capability, parent::getPolicy($capability));
}
public function shouldAppearInLaunchView() {
return false;
}
public function canUninstall() {
return false;
}
public function getRoutes() {
return array(
);
}
}
-
diff --git a/src/applications/chatlog/applications/PhabricatorApplicationChatLog.php b/src/applications/chatlog/applications/PhabricatorApplicationChatLog.php
index 5c873ac414..707cf46344 100644
--- a/src/applications/chatlog/applications/PhabricatorApplicationChatLog.php
+++ b/src/applications/chatlog/applications/PhabricatorApplicationChatLog.php
@@ -1,41 +1,40 @@
<?php
final class PhabricatorApplicationChatLog extends PhabricatorApplication {
public function getBaseURI() {
return '/chatlog/';
}
public function getShortDescription() {
return pht('Chat Log');
}
public function getIconName() {
return 'chatlog';
}
public function isBeta() {
return true;
}
public function getTitleGlyph() {
return "\xE0\xBC\x84";
}
public function getApplicationGroup() {
return self::GROUP_COMMUNICATION;
}
public function getRoutes() {
return array(
'/chatlog/' => array(
'' => 'PhabricatorChatLogChannelListController',
'channel/(?P<channelID>[^/]+)/' =>
'PhabricatorChatLogChannelLogController',
),
);
}
}
-
diff --git a/src/applications/chatlog/storage/PhabricatorChatLogChannel.php b/src/applications/chatlog/storage/PhabricatorChatLogChannel.php
index e9ea837379..5e3f355a7b 100644
--- a/src/applications/chatlog/storage/PhabricatorChatLogChannel.php
+++ b/src/applications/chatlog/storage/PhabricatorChatLogChannel.php
@@ -1,40 +1,39 @@
<?php
final class PhabricatorChatLogChannel
extends PhabricatorChatLogDAO
implements PhabricatorPolicyInterface {
protected $serviceName;
protected $serviceType;
protected $channelName;
protected $viewPolicy;
protected $editPolicy;
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->viewPolicy;
break;
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->editPolicy;
break;
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}
-
diff --git a/src/applications/config/check/PhabricatorSetupCheckImagemagick.php b/src/applications/config/check/PhabricatorSetupCheckImagemagick.php
index be041518d2..6d201be53d 100644
--- a/src/applications/config/check/PhabricatorSetupCheckImagemagick.php
+++ b/src/applications/config/check/PhabricatorSetupCheckImagemagick.php
@@ -1,24 +1,23 @@
<?php
final class PhabricatorSetupCheckImagemagick extends PhabricatorSetupCheck {
protected function executeChecks() {
$imagemagick = PhabricatorEnv::getEnvConfig('files.enable-imagemagick');
if ($imagemagick) {
if (!Filesystem::binaryExists('convert')) {
$message = pht(
'You have enabled Imagemagick in your config, but the \'convert\' '.
'binary is not in the webserver\'s $PATH. Disable imagemagick '.
'or make it available to the webserver.');
$this->newIssue('files.enable-imagemagick')
->setName(pht(
"'convert' binary not found or Imagemagick is not installed."))
->setMessage($message)
->addRelatedPhabricatorConfig('files.enable-imagemagick')
->addPhabricatorConfig('environment.append-paths');
}
}
}
}
-
diff --git a/src/applications/config/storage/PhabricatorConfigTransaction.php b/src/applications/config/storage/PhabricatorConfigTransaction.php
index 75c2cc34a3..579b8b3b65 100644
--- a/src/applications/config/storage/PhabricatorConfigTransaction.php
+++ b/src/applications/config/storage/PhabricatorConfigTransaction.php
@@ -1,119 +1,118 @@
<?php
final class PhabricatorConfigTransaction
extends PhabricatorApplicationTransaction {
const TYPE_EDIT = 'config:edit';
public function getApplicationName() {
return 'config';
}
public function getApplicationTransactionType() {
return PhabricatorConfigPHIDTypeConfig::TYPECONST;
}
public function getApplicationTransactionCommentObject() {
return null;
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_EDIT:
// TODO: After T2213 show the actual values too; for now, we don't
// have the tools to do it without making a bit of a mess of it.
$old_del = idx($old, 'deleted');
$new_del = idx($new, 'deleted');
if ($old_del && !$new_del) {
return pht(
'%s created this configuration entry.',
$this->renderHandleLink($author_phid));
} else if (!$old_del && $new_del) {
return pht(
'%s deleted this configuration entry.',
$this->renderHandleLink($author_phid));
} else if ($old_del && $new_del) {
// This is a bug.
return pht(
'%s deleted this configuration entry (again?).',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s edited this configuration entry.',
$this->renderHandleLink($author_phid));
}
break;
}
return parent::getTitle();
}
public function getIcon() {
switch ($this->getTransactionType()) {
case self::TYPE_EDIT:
return 'edit';
}
return parent::getIcon();
}
public function hasChangeDetails() {
switch ($this->getTransactionType()) {
case self::TYPE_EDIT:
return true;
}
return parent::hasChangeDetails();
}
public function renderChangeDetails(PhabricatorUser $viewer) {
$old = $this->getOldValue();
$new = $this->getNewValue();
if ($old['deleted']) {
$old_text = '';
} else {
$old_text = PhabricatorConfigJSON::prettyPrintJSON($old['value']);
}
if ($new['deleted']) {
$new_text = '';
} else {
$new_text = PhabricatorConfigJSON::prettyPrintJSON($new['value']);
}
return $this->renderTextCorpusChangeDetails(
$viewer,
$old_text,
$new_text);
}
public function getColor() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_EDIT:
$old_del = idx($old, 'deleted');
$new_del = idx($new, 'deleted');
if ($old_del && !$new_del) {
return PhabricatorTransactions::COLOR_GREEN;
} else if (!$old_del && $new_del) {
return PhabricatorTransactions::COLOR_RED;
} else {
return PhabricatorTransactions::COLOR_BLUE;
}
break;
}
}
}
-
diff --git a/src/applications/conpherence/events/ConpherenceActionMenuEventListener.php b/src/applications/conpherence/events/ConpherenceActionMenuEventListener.php
index 1fa71004eb..ca5145bd5b 100644
--- a/src/applications/conpherence/events/ConpherenceActionMenuEventListener.php
+++ b/src/applications/conpherence/events/ConpherenceActionMenuEventListener.php
@@ -1,45 +1,44 @@
<?php
final class ConpherenceActionMenuEventListener
extends PhabricatorEventListener {
public function register() {
$this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS);
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS:
$this->handleActionsEvent($event);
break;
}
}
private function handleActionsEvent(PhutilEvent $event) {
$object = $event->getValue('object');
$actions = null;
if ($object instanceof PhabricatorUser) {
$actions = $this->renderUserItems($event);
}
$this->addActionMenuItems($event, $actions);
}
private function renderUserItems(PhutilEvent $event) {
if (!$this->canUseApplication($event->getUser())) {
return null;
}
$user = $event->getValue('object');
$href = '/conpherence/new/?participant='.$user->getPHID();
return id(new PhabricatorActionView())
->setIcon('message')
->setName(pht('Send Message'))
->setWorkflow(true)
->setHref($href);
}
}
-
diff --git a/src/applications/conpherence/events/ConpherenceHovercardEventListener.php b/src/applications/conpherence/events/ConpherenceHovercardEventListener.php
index 6020d6f7e0..616af030ea 100644
--- a/src/applications/conpherence/events/ConpherenceHovercardEventListener.php
+++ b/src/applications/conpherence/events/ConpherenceHovercardEventListener.php
@@ -1,42 +1,41 @@
<?php
/**
* This event listener is tasked with probably one of the most important
* missions in this world: Adding a Conpherence button to a hovercard.
*
* Handle with care when modifying!
*
* @task event
*/
final class ConpherenceHovercardEventListener extends PhabricatorEventListener {
public function register() {
$this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD);
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD:
$this->handleHovercardEvent($event);
break;
}
}
private function handleHovercardEvent($event) {
$hovercard = $event->getValue('hovercard');
$user = $event->getValue('object');
if (!($user instanceof PhabricatorUser)) {
return;
}
$conpherence_uri = new PhutilURI(
'/conpherence/new/?participant='.$user->getPHID());
$name = pht('Send a Message');
$hovercard->addAction($name, $conpherence_uri, true);
$event->setValue('hovercard', $hovercard);
}
}
-
diff --git a/src/applications/differential/application/PhabricatorApplicationDifferential.php b/src/applications/differential/application/PhabricatorApplicationDifferential.php
index 50b0dd3c04..705b2932bb 100644
--- a/src/applications/differential/application/PhabricatorApplicationDifferential.php
+++ b/src/applications/differential/application/PhabricatorApplicationDifferential.php
@@ -1,135 +1,134 @@
<?php
final class PhabricatorApplicationDifferential extends PhabricatorApplication {
public function getBaseURI() {
return '/differential/';
}
public function getShortDescription() {
return pht('Review Code');
}
public function getIconName() {
return 'differential';
}
public function getHelpURI() {
return PhabricatorEnv::getDoclink('article/Differential_User_Guide.html');
}
public function getFactObjectsForAnalysis() {
return array(
new DifferentialRevision(),
);
}
public function getTitleGlyph() {
return "\xE2\x9A\x99";
}
public function getEventListeners() {
return array(
new DifferentialActionMenuEventListener(),
new DifferentialHovercardEventListener(),
new DifferentialLandingActionMenuEventListener(),
);
}
public function getRoutes() {
return array(
'/D(?P<id>[1-9]\d*)' => 'DifferentialRevisionViewController',
'/differential/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?'
=> 'DifferentialRevisionListController',
'diff/' => array(
'(?P<id>[1-9]\d*)/' => 'DifferentialDiffViewController',
'create/' => 'DifferentialDiffCreateController',
),
'changeset/' => 'DifferentialChangesetViewController',
'revision/edit/(?:(?P<id>[1-9]\d*)/)?'
=> 'DifferentialRevisionEditController',
'revision/editpro/(?:(?P<id>[1-9]\d*)/)?'
=> 'DifferentialRevisionEditControllerPro',
'revision/land/(?:(?P<id>[1-9]\d*))/(?P<strategy>[^/]+)/'
=> 'DifferentialRevisionLandController',
'comment/' => array(
'preview/(?P<id>[1-9]\d*)/' => 'DifferentialCommentPreviewController',
'save/' => 'DifferentialCommentSaveController',
'savepro/(?P<id>[1-9]\d*)/' => 'DifferentialCommentSaveControllerPro',
'inline/' => array(
'preview/(?P<id>[1-9]\d*)/'
=> 'DifferentialInlineCommentPreviewController',
'edit/(?P<id>[1-9]\d*)/'
=> 'DifferentialInlineCommentEditController',
),
),
'preview/' => 'PhabricatorMarkupPreviewController',
),
);
}
public function getApplicationGroup() {
return self::GROUP_CORE;
}
public function getApplicationOrder() {
return 0.100;
}
public function getRemarkupRules() {
return array(
new DifferentialRemarkupRule(),
);
}
public function loadStatus(PhabricatorUser $user) {
$revisions = id(new DifferentialRevisionQuery())
->setViewer($user)
->withResponsibleUsers(array($user->getPHID()))
->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
->needRelationships(true)
->execute();
list($blocking, $active, $waiting) =
DifferentialRevisionQuery::splitResponsible(
$revisions,
array($user->getPHID()));
$status = array();
$blocking = count($blocking);
$type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION;
$status[] = id(new PhabricatorApplicationStatusView())
->setType($type)
->setText(pht('%d Review(s) Blocking Others', $blocking))
->setCount($blocking);
$active = count($active);
$type = PhabricatorApplicationStatusView::TYPE_WARNING;
$status[] = id(new PhabricatorApplicationStatusView())
->setType($type)
->setText(pht('%d Review(s) Need Attention', $active))
->setCount($active);
$waiting = count($waiting);
$type = PhabricatorApplicationStatusView::TYPE_INFO;
$status[] = id(new PhabricatorApplicationStatusView())
->setType($type)
->setText(pht('%d Review(s) Waiting on Others', $waiting))
->setCount($waiting);
return $status;
}
protected function getCustomCapabilities() {
return array(
DifferentialCapabilityDefaultView::CAPABILITY => array(
'caption' => pht(
'Default view policy for newly created revisions.')
),
);
}
}
-
diff --git a/src/applications/differential/controller/DifferentialRevisionLandController.php b/src/applications/differential/controller/DifferentialRevisionLandController.php
index aecc5644b7..774906671c 100644
--- a/src/applications/differential/controller/DifferentialRevisionLandController.php
+++ b/src/applications/differential/controller/DifferentialRevisionLandController.php
@@ -1,154 +1,153 @@
<?php
final class DifferentialRevisionLandController extends DifferentialController {
private $revisionID;
private $strategyClass;
private $pushStrategy;
public function willProcessRequest(array $data) {
$this->revisionID = $data['id'];
$this->strategyClass = $data['strategy'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$revision_id = $this->revisionID;
$revision = id(new DifferentialRevisionQuery())
->withIDs(array($revision_id))
->setViewer($viewer)
->executeOne();
if (!$revision) {
return new Aphront404Response();
}
if (is_subclass_of($this->strategyClass, 'DifferentialLandingStrategy')) {
$this->pushStrategy = newv($this->strategyClass, array());
} else {
throw new Exception(
"Strategy type must be a valid class name and must subclass ".
"DifferentialLandingStrategy. ".
"'{$this->strategyClass}' is not a subclass of ".
"DifferentialLandingStrategy.");
}
if ($request->isDialogFormPost()) {
$response = null;
$text = '';
try {
$response = $this->attemptLand($revision, $request);
$title = pht("Success!");
$text = pht("Revision was successfully landed.");
} catch (Exception $ex) {
$title = pht("Failed to land revision");
if ($ex instanceof PhutilProxyException) {
$text = hsprintf(
'%s:<br><pre>%s</pre>',
$ex->getMessage(),
$ex->getPreviousException()->getMessage());
} else {
$text = phutil_tag('pre', array(), $ex->getMessage());
}
$text = id(new AphrontErrorView())
->appendChild($text);
}
if ($response instanceof AphrontDialogView) {
$dialog = $response;
} else {
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle($title)
->appendChild(phutil_tag('p', array(), $text))
->addCancelButton('/D'.$revision_id, pht('Done'));
}
return id(new AphrontDialogResponse())->setDialog($dialog);
}
$is_disabled = $this->pushStrategy->isActionDisabled(
$viewer,
$revision,
$revision->getRepository());
if ($is_disabled) {
if (is_string($is_disabled)) {
$explain = $is_disabled;
} else {
$explain = pht("This action is not currently enabled.");
}
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle(pht("Can't land revision"))
->appendChild($explain)
->addCancelButton('/D'.$revision_id);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
$prompt = hsprintf('%s<br><br>%s',
pht(
'This will squash and rebase revision %s, and push it to '.
'the default / master branch.',
$revision_id),
pht('It is an experimental feature and may not work.'));
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle(pht("Land Revision %s?", $revision_id))
->appendChild($prompt)
->setSubmitURI($request->getRequestURI())
->addSubmitButton(pht('Land it!'))
->addCancelButton('/D'.$revision_id);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
private function attemptLand($revision, $request) {
$status = $revision->getStatus();
if ($status != ArcanistDifferentialRevisionStatus::ACCEPTED) {
throw new Exception("Only Accepted revisions can be landed.");
}
$repository = $revision->getRepository();
if ($repository === null) {
throw new Exception("revision is not attached to a repository.");
}
$can_push = PhabricatorPolicyFilter::hasCapability(
$request->getUser(),
$repository,
DiffusionCapabilityPush::CAPABILITY);
if (!$can_push) {
throw new Exception(
pht('You do not have permission to push to this repository.'));
}
$lock = $this->lockRepository($repository);
try {
$response = $this->pushStrategy->processLandRequest(
$request,
$revision,
$repository);
} catch (Exception $e) {
$lock->unlock();
throw $e;
}
$lock->unlock();
return $response;
}
private function lockRepository($repository) {
$lock_name = __CLASS__.':'.($repository->getCallsign());
$lock = PhabricatorGlobalLock::newLock($lock_name);
$lock->lock();
return $lock;
}
}
-
diff --git a/src/applications/differential/editor/DifferentialRevisionEditor.php b/src/applications/differential/editor/DifferentialRevisionEditor.php
index 0ddbbb1720..10411f2346 100644
--- a/src/applications/differential/editor/DifferentialRevisionEditor.php
+++ b/src/applications/differential/editor/DifferentialRevisionEditor.php
@@ -1,997 +1,996 @@
<?php
/**
* Handle major edit operations to DifferentialRevision -- adding and removing
* reviewers, diffs, and CCs. Unlike simple edits, these changes trigger
* complicated email workflows.
*/
final class DifferentialRevisionEditor extends PhabricatorEditor {
protected $revision;
protected $cc = null;
protected $reviewers = null;
protected $diff;
protected $comments;
protected $silentUpdate;
private $auxiliaryFields = array();
private $contentSource;
private $isCreate;
private $aphrontRequestForEventDispatch;
public function setAphrontRequestForEventDispatch(AphrontRequest $request) {
$this->aphrontRequestForEventDispatch = $request;
return $this;
}
public function getAphrontRequestForEventDispatch() {
return $this->aphrontRequestForEventDispatch;
}
public function __construct(DifferentialRevision $revision) {
$this->revision = $revision;
$this->isCreate = !($revision->getID());
}
public static function newRevisionFromConduitWithDiff(
array $fields,
DifferentialDiff $diff,
PhabricatorUser $actor) {
$revision = DifferentialRevision::initializeNewRevision($actor);
$revision->setPHID($revision->generatePHID());
$editor = new DifferentialRevisionEditor($revision);
$editor->setActor($actor);
$editor->addDiff($diff, null);
$editor->copyFieldsFromConduit($fields);
$editor->save();
return $revision;
}
public function copyFieldsFromConduit(array $fields) {
$actor = $this->getActor();
$revision = $this->revision;
$revision->loadRelationships();
$all_fields = DifferentialFieldSelector::newSelector()
->getFieldSpecifications();
$aux_fields = array();
foreach ($all_fields as $aux_field) {
$aux_field->setRevision($revision);
$aux_field->setDiff($this->diff);
$aux_field->setUser($actor);
if ($aux_field->shouldAppearOnCommitMessage()) {
$aux_fields[$aux_field->getCommitMessageKey()] = $aux_field;
}
}
foreach ($fields as $field => $value) {
if (empty($aux_fields[$field])) {
throw new Exception(
"Parsed commit message contains unrecognized field '{$field}'.");
}
$aux_fields[$field]->setValueFromParsedCommitMessage($value);
}
foreach ($aux_fields as $aux_field) {
$aux_field->validateField();
}
$this->setAuxiliaryFields($all_fields);
}
public function setAuxiliaryFields(array $auxiliary_fields) {
assert_instances_of($auxiliary_fields, 'DifferentialFieldSpecification');
$this->auxiliaryFields = $auxiliary_fields;
return $this;
}
public function getRevision() {
return $this->revision;
}
public function setReviewers(array $reviewers) {
$this->reviewers = $reviewers;
return $this;
}
public function setCCPHIDs(array $cc) {
$this->cc = $cc;
return $this;
}
public function setContentSource(PhabricatorContentSource $content_source) {
$this->contentSource = $content_source;
return $this;
}
public function addDiff(DifferentialDiff $diff, $comments) {
if ($diff->getRevisionID() &&
$diff->getRevisionID() != $this->getRevision()->getID()) {
$diff_id = (int)$diff->getID();
$targ_id = (int)$this->getRevision()->getID();
$real_id = (int)$diff->getRevisionID();
throw new Exception(
"Can not attach diff #{$diff_id} to Revision D{$targ_id}, it is ".
"already attached to D{$real_id}.");
}
$this->diff = $diff;
$this->comments = $comments;
$repository = id(new DifferentialRepositoryLookup())
->setViewer($this->getActor())
->setDiff($diff)
->lookupRepository();
if ($repository) {
$this->getRevision()->setRepositoryPHID($repository->getPHID());
}
return $this;
}
protected function getDiff() {
return $this->diff;
}
protected function getComments() {
return $this->comments;
}
protected function getActorPHID() {
return $this->getActor()->getPHID();
}
public function isNewRevision() {
return !$this->getRevision()->getID();
}
/**
* A silent update does not trigger Herald rules or send emails. This is used
* for auto-amends at commit time.
*/
public function setSilentUpdate($silent) {
$this->silentUpdate = $silent;
return $this;
}
public function save() {
$revision = $this->getRevision();
$is_new = $this->isNewRevision();
$revision->loadRelationships();
$this->willWriteRevision();
if ($this->reviewers === null) {
$this->reviewers = $revision->getReviewers();
}
if ($this->cc === null) {
$this->cc = $revision->getCCPHIDs();
}
if ($is_new) {
$content_blocks = array();
foreach ($this->auxiliaryFields as $field) {
if ($field->shouldExtractMentions()) {
$content_blocks[] = $field->renderValueForCommitMessage(false);
}
}
$phids = PhabricatorMarkupEngine::extractPHIDsFromMentions(
$content_blocks);
$this->cc = array_unique(array_merge($this->cc, $phids));
}
$diff = $this->getDiff();
if ($diff) {
$revision->setLineCount($diff->getLineCount());
}
// Save the revision, to generate its ID and PHID if it is new. We need
// the ID/PHID in order to record them in Herald transcripts, but don't
// want to hold a transaction open while running Herald because it is
// potentially somewhat slow. The downside is that we may end up with a
// saved revision/diff pair without appropriate CCs. We could be better
// about this -- for example:
//
// - Herald can't affect reviewers, so we could compute them before
// opening the transaction and then save them in the transaction.
// - Herald doesn't *really* need PHIDs to compute its effects, we could
// run it before saving these objects and then hand over the PHIDs later.
//
// But this should address the problem of orphaned revisions, which is
// currently the only problem we experience in practice.
$revision->openTransaction();
if ($diff) {
$revision->setBranchName($diff->getBranch());
$revision->setArcanistProjectPHID($diff->getArcanistProjectPHID());
}
$revision->save();
if ($diff) {
$diff->setRevisionID($revision->getID());
$diff->save();
}
$revision->saveTransaction();
// We're going to build up three dictionaries: $add, $rem, and $stable. The
// $add dictionary has added reviewers/CCs. The $rem dictionary has
// reviewers/CCs who have been removed, and the $stable array is
// reviewers/CCs who haven't changed. We're going to send new reviewers/CCs
// a different ("welcome") email than we send stable reviewers/CCs.
$old = array(
'rev' => array_fill_keys($revision->getReviewers(), true),
'ccs' => array_fill_keys($revision->getCCPHIDs(), true),
);
$xscript_header = null;
$xscript_uri = null;
$new = array(
'rev' => array_fill_keys($this->reviewers, true),
'ccs' => array_fill_keys($this->cc, true),
);
$rem_ccs = array();
$xscript_phid = null;
if ($diff) {
$unsubscribed_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$revision->getPHID(),
PhabricatorEdgeConfig::TYPE_OBJECT_HAS_UNSUBSCRIBER);
$adapter = HeraldDifferentialRevisionAdapter::newLegacyAdapter(
$revision,
$diff);
$adapter->setExplicitCCs($new['ccs']);
$adapter->setExplicitReviewers($new['rev']);
$adapter->setForbiddenCCs($unsubscribed_phids);
$adapter->setIsNewObject($is_new);
$xscript = HeraldEngine::loadAndApplyRules($adapter);
$xscript_uri = '/herald/transcript/'.$xscript->getID().'/';
$xscript_phid = $xscript->getPHID();
$xscript_header = $xscript->getXHeraldRulesHeader();
$xscript_header = HeraldTranscript::saveXHeraldRulesHeader(
$revision->getPHID(),
$xscript_header);
$sub = array(
'rev' => $adapter->getReviewersAddedByHerald(),
'ccs' => $adapter->getCCsAddedByHerald(),
);
$rem_ccs = $adapter->getCCsRemovedByHerald();
$blocking_reviewers = array_keys(
$adapter->getBlockingReviewersAddedByHerald());
HarbormasterBuildable::applyBuildPlans(
$diff->getPHID(),
$revision->getPHID(),
$adapter->getBuildPlans());
} else {
$sub = array(
'rev' => array(),
'ccs' => array(),
);
$blocking_reviewers = array();
}
// Remove any CCs which are prevented by Herald rules.
$sub['ccs'] = array_diff_key($sub['ccs'], $rem_ccs);
$new['ccs'] = array_diff_key($new['ccs'], $rem_ccs);
$add = array();
$rem = array();
$stable = array();
foreach (array('rev', 'ccs') as $key) {
$add[$key] = array();
if ($new[$key] !== null) {
$add[$key] += array_diff_key($new[$key], $old[$key]);
}
$add[$key] += array_diff_key($sub[$key], $old[$key]);
$combined = $sub[$key];
if ($new[$key] !== null) {
$combined += $new[$key];
}
$rem[$key] = array_diff_key($old[$key], $combined);
$stable[$key] = array_diff_key($old[$key], $add[$key] + $rem[$key]);
}
// Prevent Herald rules from adding a revision's owner as a reviewer.
unset($add['rev'][$revision->getAuthorPHID()]);
self::updateReviewers(
$revision,
$this->getActor(),
array_keys($add['rev']),
array_keys($rem['rev']),
$blocking_reviewers);
// We want to attribute new CCs to a "reasonPHID", representing the reason
// they were added. This is either a user (if some user explicitly CCs
// them, or uses "Add CCs...") or a Herald transcript PHID, indicating that
// they were added by a Herald rule.
if ($add['ccs'] || $rem['ccs']) {
$reasons = array();
foreach ($add['ccs'] as $phid => $ignored) {
if (empty($new['ccs'][$phid])) {
$reasons[$phid] = $xscript_phid;
} else {
$reasons[$phid] = $this->getActorPHID();
}
}
foreach ($rem['ccs'] as $phid => $ignored) {
if (empty($new['ccs'][$phid])) {
$reasons[$phid] = $this->getActorPHID();
} else {
$reasons[$phid] = $xscript_phid;
}
}
} else {
$reasons = $this->getActorPHID();
}
self::alterCCs(
$revision,
$this->cc,
array_keys($rem['ccs']),
array_keys($add['ccs']),
$reasons);
$this->updateAuxiliaryFields();
// Add the author and users included from Herald rules to the relevant set
// of users so they get a copy of the email.
if (!$this->silentUpdate) {
if ($is_new) {
$add['rev'][$this->getActorPHID()] = true;
if ($diff) {
$add['rev'] += $adapter->getEmailPHIDsAddedByHerald();
}
} else {
$stable['rev'][$this->getActorPHID()] = true;
if ($diff) {
$stable['rev'] += $adapter->getEmailPHIDsAddedByHerald();
}
}
}
$mail = array();
$phids = array($this->getActorPHID());
$handles = id(new PhabricatorHandleQuery())
->setViewer($this->getActor())
->withPHIDs($phids)
->execute();
$actor_handle = $handles[$this->getActorPHID()];
$changesets = null;
$old_status = $revision->getStatus();
if ($diff) {
$changesets = $diff->loadChangesets();
// TODO: This should probably be in DifferentialFeedbackEditor?
if (!$is_new) {
$this->createComment();
$mail[] = id(new DifferentialNewDiffMail(
$revision,
$actor_handle,
$changesets))
->setActor($this->getActor())
->setIsFirstMailAboutRevision(false)
->setIsFirstMailToRecipients(false)
->setCommentText($this->getComments())
->setToPHIDs(array_keys($stable['rev']))
->setCCPHIDs(array_keys($stable['ccs']));
}
// Save the changes we made above.
$diff->setDescription(preg_replace('/\n.*/s', '', $this->getComments()));
$diff->save();
$this->updateAffectedPathTable($revision, $diff, $changesets);
$this->updateRevisionHashTable($revision, $diff);
// An updated diff should require review, as long as it's not closed
// or accepted. The "accepted" status is "sticky" to encourage courtesy
// re-diffs after someone accepts with minor changes/suggestions.
$status = $revision->getStatus();
if ($status != ArcanistDifferentialRevisionStatus::CLOSED &&
$status != ArcanistDifferentialRevisionStatus::ACCEPTED) {
$revision->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW);
}
} else {
$diff = $revision->loadActiveDiff();
if ($diff) {
$changesets = $diff->loadChangesets();
} else {
$changesets = array();
}
}
$revision->save();
// If the actor just deleted all the blocking/rejected reviewers, we may
// be able to put the revision into "accepted".
switch ($revision->getStatus()) {
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
$revision = self::updateAcceptedStatus(
$this->getActor(),
$revision);
break;
}
$this->didWriteRevision();
$event_data = array(
'revision_id' => $revision->getID(),
'revision_phid' => $revision->getPHID(),
'revision_name' => $revision->getTitle(),
'revision_author_phid' => $revision->getAuthorPHID(),
'action' => $is_new
? DifferentialAction::ACTION_CREATE
: DifferentialAction::ACTION_UPDATE,
'feedback_content' => $is_new
? phutil_utf8_shorten($revision->getSummary(), 140)
: $this->getComments(),
'actor_phid' => $revision->getAuthorPHID(),
);
$mailed_phids = array();
if (!$this->silentUpdate) {
$revision->loadRelationships();
if ($add['rev']) {
$message = id(new DifferentialNewDiffMail(
$revision,
$actor_handle,
$changesets))
->setActor($this->getActor())
->setIsFirstMailAboutRevision($is_new)
->setIsFirstMailToRecipients(true)
->setToPHIDs(array_keys($add['rev']));
if ($is_new) {
// The first time we send an email about a revision, put the CCs in
// the "CC:" field of the same "Review Requested" email that reviewers
// get, so you don't get two initial emails if you're on a list that
// is CC'd.
$message->setCCPHIDs(array_keys($add['ccs']));
}
$mail[] = $message;
}
// If we added CCs, we want to send them an email, but only if they were
// not already a reviewer and were not added as one (in these cases, they
// got a "NewDiff" mail, either in the past or just a moment ago). You can
// still get two emails, but only if a revision is updated and you are
// added as a reviewer at the same time a list you are on is added as a
// CC, which is rare and reasonable.
$implied_ccs = self::getImpliedCCs($revision);
$implied_ccs = array_fill_keys($implied_ccs, true);
$add['ccs'] = array_diff_key($add['ccs'], $implied_ccs);
if (!$is_new && $add['ccs']) {
$mail[] = id(new DifferentialCCWelcomeMail(
$revision,
$actor_handle,
$changesets))
->setActor($this->getActor())
->setIsFirstMailToRecipients(true)
->setToPHIDs(array_keys($add['ccs']));
}
foreach ($mail as $message) {
$message->setHeraldTranscriptURI($xscript_uri);
$message->setXHeraldRulesHeader($xscript_header);
$message->send();
$mailed_phids[] = $message->getRawMail()->buildRecipientList();
}
$mailed_phids = array_mergev($mailed_phids);
}
id(new PhabricatorFeedStoryPublisher())
->setStoryType('PhabricatorFeedStoryDifferential')
->setStoryData($event_data)
->setStoryTime(time())
->setStoryAuthorPHID($revision->getAuthorPHID())
->setRelatedPHIDs(
array(
$revision->getPHID(),
$revision->getAuthorPHID(),
))
->setPrimaryObjectPHID($revision->getPHID())
->setSubscribedPHIDs(
array_merge(
array($revision->getAuthorPHID()),
$revision->getReviewers(),
$revision->getCCPHIDs()))
->setMailRecipientPHIDs($mailed_phids)
->publish();
id(new PhabricatorSearchIndexer())
->queueDocumentForIndexing($revision->getPHID());
}
public static function addCC(
DifferentialRevision $revision,
$phid,
$reason) {
return self::alterCCs(
$revision,
$revision->getCCPHIDs(),
$rem = array(),
$add = array($phid),
$reason);
}
protected static function alterCCs(
DifferentialRevision $revision,
array $stable_phids,
array $rem_phids,
array $add_phids,
$reason_phid) {
$dont_add = self::getImpliedCCs($revision);
$add_phids = array_diff($add_phids, $dont_add);
id(new PhabricatorSubscriptionsEditor())
->setActor(PhabricatorUser::getOmnipotentUser())
->setObject($revision)
->subscribeExplicit($add_phids)
->unsubscribe($rem_phids)
->save();
}
private static function getImpliedCCs(DifferentialRevision $revision) {
return array_merge(
$revision->getReviewers(),
array($revision->getAuthorPHID()));
}
public static function updateReviewers(
DifferentialRevision $revision,
PhabricatorUser $actor,
array $add_phids,
array $remove_phids,
array $blocking_phids = array()) {
$reviewers = $revision->getReviewers();
$editor = id(new PhabricatorEdgeEditor())
->setActor($actor);
$reviewer_phids_map = array_fill_keys($reviewers, true);
$blocking_phids = array_fuse($blocking_phids);
foreach ($add_phids as $phid) {
// Adding an already existing edge again would have cause memory loss
// That is, the previous state for that reviewer would be lost
if (isset($reviewer_phids_map[$phid])) {
// TODO: If we're writing a blocking edge, we should overwrite an
// existing weaker edge (like "added" or "commented"), just not a
// stronger existing edge.
continue;
}
if (isset($blocking_phids[$phid])) {
$status = DifferentialReviewerStatus::STATUS_BLOCKING;
} else {
$status = DifferentialReviewerStatus::STATUS_ADDED;
}
$options = array(
'data' => array(
'status' => $status,
)
);
$editor->addEdge(
$revision->getPHID(),
PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER,
$phid,
$options);
}
foreach ($remove_phids as $phid) {
$editor->removeEdge(
$revision->getPHID(),
PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER,
$phid);
}
$editor->save();
}
public static function updateReviewerStatus(
DifferentialRevision $revision,
PhabricatorUser $actor,
$reviewer_phid,
$status) {
$options = array(
'data' => array(
'status' => $status
)
);
$active_diff = $revision->loadActiveDiff();
if ($active_diff) {
$options['data']['diff'] = $active_diff->getID();
}
id(new PhabricatorEdgeEditor())
->setActor($actor)
->addEdge(
$revision->getPHID(),
PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER,
$reviewer_phid,
$options)
->save();
}
private function createComment() {
$template = id(new DifferentialComment())
->setAuthorPHID($this->getActorPHID())
->setRevision($this->revision);
if ($this->contentSource) {
$content_source = $this->contentSource;
} else {
$content_source = PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_LEGACY,
array());
}
$template->setContentSource($content_source);
// Write the "update active diff" transaction.
id(clone $template)
->setAction(DifferentialAction::ACTION_UPDATE)
->setMetadata(
array(
DifferentialComment::METADATA_DIFF_ID => $this->getDiff()->getID(),
))
->save();
// If we have a comment, write the "add a comment" transaction.
if (strlen($this->getComments())) {
id(clone $template)
->setAction(DifferentialAction::ACTION_COMMENT)
->setContent($this->getComments())
->save();
}
}
private function updateAuxiliaryFields() {
$aux_map = array();
foreach ($this->auxiliaryFields as $aux_field) {
$key = $aux_field->getStorageKey();
if ($key !== null) {
$val = $aux_field->getValueForStorage();
$aux_map[$key] = $val;
}
}
if (!$aux_map) {
return;
}
$revision = $this->revision;
$fields = id(new DifferentialAuxiliaryField())->loadAllWhere(
'revisionPHID = %s AND name IN (%Ls)',
$revision->getPHID(),
array_keys($aux_map));
$fields = mpull($fields, null, 'getName');
foreach ($aux_map as $key => $val) {
$obj = idx($fields, $key);
if (!strlen($val)) {
// If the new value is empty, just delete the old row if one exists and
// don't add a new row if it doesn't.
if ($obj) {
$obj->delete();
}
} else {
if (!$obj) {
$obj = new DifferentialAuxiliaryField();
$obj->setRevisionPHID($revision->getPHID());
$obj->setName($key);
}
if ($obj->getValue() !== $val) {
$obj->setValue($val);
$obj->save();
}
}
}
}
private function willWriteRevision() {
foreach ($this->auxiliaryFields as $aux_field) {
$aux_field->willWriteRevision($this);
}
$this->dispatchEvent(
PhabricatorEventType::TYPE_DIFFERENTIAL_WILLEDITREVISION);
}
private function didWriteRevision() {
foreach ($this->auxiliaryFields as $aux_field) {
$aux_field->didWriteRevision($this);
}
$this->dispatchEvent(
PhabricatorEventType::TYPE_DIFFERENTIAL_DIDEDITREVISION);
}
private function dispatchEvent($type) {
$event = new PhabricatorEvent(
$type,
array(
'revision' => $this->revision,
'new' => $this->isCreate,
));
$event->setUser($this->getActor());
$request = $this->getAphrontRequestForEventDispatch();
if ($request) {
$event->setAphrontRequest($request);
}
PhutilEventEngine::dispatchEvent($event);
}
/**
* Update the table which links Differential revisions to paths they affect,
* so Diffusion can efficiently find pending revisions for a given file.
*/
private function updateAffectedPathTable(
DifferentialRevision $revision,
DifferentialDiff $diff,
array $changesets) {
assert_instances_of($changesets, 'DifferentialChangeset');
$project = $diff->loadArcanistProject();
if (!$project) {
// Probably an old revision from before projects.
return;
}
$repository = $project->loadRepository();
if (!$repository) {
// Probably no project <-> repository link, or the repository where the
// project lives is untracked.
return;
}
$path_prefix = null;
$local_root = $diff->getSourceControlPath();
if ($local_root) {
// We're in a working copy which supports subdirectory checkouts (e.g.,
// SVN) so we need to figure out what prefix we should add to each path
// (e.g., trunk/projects/example/) to get the absolute path from the
// root of the repository. DVCS systems like Git and Mercurial are not
// affected.
// Normalize both paths and check if the repository root is a prefix of
// the local root. If so, throw it away. Note that this correctly handles
// the case where the remote path is "/".
$local_root = id(new PhutilURI($local_root))->getPath();
$local_root = rtrim($local_root, '/');
$repo_root = id(new PhutilURI($repository->getRemoteURI()))->getPath();
$repo_root = rtrim($repo_root, '/');
if (!strncmp($repo_root, $local_root, strlen($repo_root))) {
$path_prefix = substr($local_root, strlen($repo_root));
}
}
$paths = array();
foreach ($changesets as $changeset) {
$paths[] = $path_prefix.'/'.$changeset->getFilename();
}
// Mark this as also touching all parent paths, so you can see all pending
// changes to any file within a directory.
$all_paths = array();
foreach ($paths as $local) {
foreach (DiffusionPathIDQuery::expandPathToRoot($local) as $path) {
$all_paths[$path] = true;
}
}
$all_paths = array_keys($all_paths);
$path_ids =
PhabricatorRepositoryCommitChangeParserWorker::lookupOrCreatePaths(
$all_paths);
$table = new DifferentialAffectedPath();
$conn_w = $table->establishConnection('w');
$sql = array();
foreach ($path_ids as $path_id) {
$sql[] = qsprintf(
$conn_w,
'(%d, %d, %d, %d)',
$repository->getID(),
$path_id,
time(),
$revision->getID());
}
queryfx(
$conn_w,
'DELETE FROM %T WHERE revisionID = %d',
$table->getTableName(),
$revision->getID());
foreach (array_chunk($sql, 256) as $chunk) {
queryfx(
$conn_w,
'INSERT INTO %T (repositoryID, pathID, epoch, revisionID) VALUES %Q',
$table->getTableName(),
implode(', ', $chunk));
}
}
/**
* Update the table connecting revisions to DVCS local hashes, so we can
* identify revisions by commit/tree hashes.
*/
private function updateRevisionHashTable(
DifferentialRevision $revision,
DifferentialDiff $diff) {
$vcs = $diff->getSourceControlSystem();
if ($vcs == DifferentialRevisionControlSystem::SVN) {
// Subversion has no local commit or tree hash information, so we don't
// have to do anything.
return;
}
$property = id(new DifferentialDiffProperty())->loadOneWhere(
'diffID = %d AND name = %s',
$diff->getID(),
'local:commits');
if (!$property) {
return;
}
$hashes = array();
$data = $property->getData();
switch ($vcs) {
case DifferentialRevisionControlSystem::GIT:
foreach ($data as $commit) {
$hashes[] = array(
ArcanistDifferentialRevisionHash::HASH_GIT_COMMIT,
$commit['commit'],
);
$hashes[] = array(
ArcanistDifferentialRevisionHash::HASH_GIT_TREE,
$commit['tree'],
);
}
break;
case DifferentialRevisionControlSystem::MERCURIAL:
foreach ($data as $commit) {
$hashes[] = array(
ArcanistDifferentialRevisionHash::HASH_MERCURIAL_COMMIT,
$commit['rev'],
);
}
break;
}
$conn_w = $revision->establishConnection('w');
$sql = array();
foreach ($hashes as $info) {
list($type, $hash) = $info;
$sql[] = qsprintf(
$conn_w,
'(%d, %s, %s)',
$revision->getID(),
$type,
$hash);
}
queryfx(
$conn_w,
'DELETE FROM %T WHERE revisionID = %d',
ArcanistDifferentialRevisionHash::TABLE_NAME,
$revision->getID());
if ($sql) {
queryfx(
$conn_w,
'INSERT INTO %T (revisionID, type, hash) VALUES %Q',
ArcanistDifferentialRevisionHash::TABLE_NAME,
implode(', ', $sql));
}
}
/**
* Try to move a revision to "accepted". We look for:
*
* - at least one accepting reviewer who is a user; and
* - no rejects; and
* - no blocking reviewers.
*/
public static function updateAcceptedStatus(
PhabricatorUser $viewer,
DifferentialRevision $revision) {
$revision = id(new DifferentialRevisionQuery())
->setViewer($viewer)
->withIDs(array($revision->getID()))
->needRelationships(true)
->needReviewerStatus(true)
->needReviewerAuthority(true)
->executeOne();
$has_user_accept = false;
foreach ($revision->getReviewerStatus() as $reviewer) {
$status = $reviewer->getStatus();
if ($status == DifferentialReviewerStatus::STATUS_BLOCKING) {
// We have a blocking reviewer, so just leave the revision in its
// existing state.
return $revision;
}
if ($status == DifferentialReviewerStatus::STATUS_REJECTED) {
// We have a rejecting reviewer, so leave the revisoin as is.
return $revision;
}
if ($reviewer->isUser()) {
if ($status == DifferentialReviewerStatus::STATUS_ACCEPTED) {
$has_user_accept = true;
}
}
}
if ($has_user_accept) {
$revision
->setStatus(ArcanistDifferentialRevisionStatus::ACCEPTED)
->save();
}
return $revision;
}
}
-
diff --git a/src/applications/differential/event/DifferentialActionMenuEventListener.php b/src/applications/differential/event/DifferentialActionMenuEventListener.php
index a7f42bde4f..6f46c44c04 100644
--- a/src/applications/differential/event/DifferentialActionMenuEventListener.php
+++ b/src/applications/differential/event/DifferentialActionMenuEventListener.php
@@ -1,70 +1,69 @@
<?php
final class DifferentialActionMenuEventListener
extends PhabricatorEventListener {
public function register() {
$this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS);
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS:
$this->handleActionsEvent($event);
break;
}
}
private function handleActionsEvent(PhutilEvent $event) {
$object = $event->getValue('object');
$actions = null;
if ($object instanceof PhabricatorUser) {
$actions = $this->renderUserItems($event);
} else if ($object instanceof ManiphestTask) {
$actions = $this->renderTaskItems($event);
}
$this->addActionMenuItems($event, $actions);
}
private function renderUserItems(PhutilEvent $event) {
if (!$this->canUseApplication($event->getUser())) {
return null;
}
$person = $event->getValue('object');
$href = '/differential/?authorPHIDs[]='.$person->getPHID();
return id(new PhabricatorActionView())
->setRenderAsForm(true)
->setIcon('differential-dark')
->setIconSheet(PHUIIconView::SPRITE_APPS)
->setName(pht('View Revisions'))
->setHref($href);
}
private function renderTaskItems(PhutilEvent $event) {
if (!$this->canUseApplication($event->getUser())) {
return null;
}
$task = $event->getValue('object');
$phid = $task->getPHID();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$event->getUser(),
$task,
PhabricatorPolicyCapability::CAN_EDIT);
return id(new PhabricatorActionView())
->setName(pht('Edit Differential Revisions'))
->setHref("/search/attach/{$phid}/DREV/")
->setWorkflow(true)
->setIcon('attach')
->setDisabled(!$can_edit)
->setWorkflow(true);
}
}
-
diff --git a/src/applications/differential/field/selector/DifferentialDefaultFieldSelector.php b/src/applications/differential/field/selector/DifferentialDefaultFieldSelector.php
index e95dc3acbd..9d2a6021aa 100644
--- a/src/applications/differential/field/selector/DifferentialDefaultFieldSelector.php
+++ b/src/applications/differential/field/selector/DifferentialDefaultFieldSelector.php
@@ -1,96 +1,95 @@
<?php
final class DifferentialDefaultFieldSelector
extends DifferentialFieldSelector {
public function getFieldSpecifications() {
$fields = array(
new DifferentialTitleFieldSpecification(),
new DifferentialSummaryFieldSpecification(),
new DifferentialTestPlanFieldSpecification(),
new DifferentialRevisionStatusFieldSpecification(),
new DifferentialAuthorFieldSpecification(),
new DifferentialReviewersFieldSpecification(),
new DifferentialProjectReviewersFieldSpecification(),
new DifferentialReviewedByFieldSpecification(),
new DifferentialCCsFieldSpecification(),
new DifferentialRepositoryFieldSpecification(),
new DifferentialViewPolicyFieldSpecification(),
new DifferentialEditPolicyFieldSpecification(),
new DifferentialLintFieldSpecification(),
new DifferentialUnitFieldSpecification(),
new DifferentialCommitsFieldSpecification(),
new DifferentialDependsOnFieldSpecification(),
new DifferentialDependenciesFieldSpecification(),
new DifferentialManiphestTasksFieldSpecification(),
new DifferentialHostFieldSpecification(),
new DifferentialPathFieldSpecification(),
new DifferentialBranchFieldSpecification(),
new DifferentialArcanistProjectFieldSpecification(),
new DifferentialApplyPatchFieldSpecification(),
new DifferentialRevisionIDFieldSpecification(),
new DifferentialGitSVNIDFieldSpecification(),
new DifferentialConflictsFieldSpecification(),
new DifferentialDateModifiedFieldSpecification(),
new DifferentialDateCreatedFieldSpecification(),
new DifferentialAuditorsFieldSpecification(),
new DifferentialDiffViewPolicyFieldSpecification(),
new DifferentialAsanaRepresentationFieldSpecification(),
);
if (PhabricatorAuthProviderOAuth1JIRA::getJIRAProvider()) {
$fields[] = new DifferentialJIRAIssuesFieldSpecification();
}
return $fields;
}
public function sortFieldsForRevisionList(array $fields) {
assert_instances_of($fields, 'DifferentialFieldSpecification');
$map = array();
foreach ($fields as $field) {
$map[get_class($field)] = $field;
}
$map = array_select_keys(
$map,
array(
'DifferentialRevisionIDFieldSpecification',
'DifferentialTitleFieldSpecification',
'DifferentialRevisionStatusFieldSpecification',
'DifferentialAuthorFieldSpecification',
'DifferentialReviewersFieldSpecification',
'DifferentialDateModifiedFieldSpecification',
'DifferentialDateCreatedFieldSpecification',
)) + $map;
return array_values($map);
}
public function sortFieldsForMail(array $fields) {
assert_instances_of($fields, 'DifferentialFieldSpecification');
$map = array();
foreach ($fields as $field) {
$map[get_class($field)] = $field;
}
$map = array_select_keys(
$map,
array(
'DifferentialReviewersFieldSpecification',
'DifferentialSummaryFieldSpecification',
'DifferentialTestPlanFieldSpecification',
'DifferentialRevisionIDFieldSpecification',
'DifferentialManiphestTasksFieldSpecification',
'DifferentialBranchFieldSpecification',
'DifferentialArcanistProjectFieldSpecification',
'DifferentialCommitsFieldSpecification',
)) + $map;
return array_values($map);
}
}
-
diff --git a/src/applications/differential/mail/DifferentialExceptionMail.php b/src/applications/differential/mail/DifferentialExceptionMail.php
index efe90d0c04..aded833d77 100644
--- a/src/applications/differential/mail/DifferentialExceptionMail.php
+++ b/src/applications/differential/mail/DifferentialExceptionMail.php
@@ -1,47 +1,45 @@
<?php
final class DifferentialExceptionMail extends DifferentialMail {
public function __construct(
DifferentialRevision $revision,
Exception $exception,
$original_body) {
$this->revision = $revision;
$this->exception = $exception;
$this->originalBody = $original_body;
}
protected function renderBody() {
// Never called since buildBody() is overridden.
}
protected function renderSubject() {
return "Exception: unable to process your mail request";
}
protected function renderVaryPrefix() {
return '';
}
protected function buildBody() {
$exception = $this->exception;
$original_body = $this->originalBody;
$message = $exception->getMessage();
return <<<EOBODY
Your request failed because an exception was encoutered while processing it:
EXCEPTION: {$message}
-- Original Body -------------------------------------------------------------
{$original_body}
EOBODY;
}
}
-
-
diff --git a/src/applications/differential/parser/__tests__/DifferentialHunkParserTestCase.php b/src/applications/differential/parser/__tests__/DifferentialHunkParserTestCase.php
index 0d247eec4a..dae9fb48ac 100644
--- a/src/applications/differential/parser/__tests__/DifferentialHunkParserTestCase.php
+++ b/src/applications/differential/parser/__tests__/DifferentialHunkParserTestCase.php
@@ -1,283 +1,282 @@
<?php
final class DifferentialHunkParserTestCase extends PhabricatorTestCase {
private function createComment() {
$comment = new DifferentialInlineComment();
return $comment;
}
private function createHunk($oldOffset, $oldLen, $newOffset, $newLen, $changes) {
$hunk = new DifferentialHunk();
$hunk->setOldOffset($oldOffset);
$hunk->setOldLen($oldLen);
$hunk->setNewOffset($newOffset);
$hunk->setNewLen($newLen);
$hunk->setChanges($changes);
return $hunk;
}
// Returns a change that consists of a single hunk, starting at line 1.
private function createSingleChange($old_lines, $new_lines, $changes) {
return array(
0 => $this->createHunk(1, $old_lines, 1, $new_lines, $changes),
);
}
private function createHunksFromFile($name) {
$data = Filesystem::readFile(dirname(__FILE__).'/data/'.$name);
$parser = new ArcanistDiffParser();
$changes = $parser->parseDiff($data);
if (count($changes) !== 1) {
throw new Exception("Expected 1 changeset for '{$name}'!");
}
$diff = DifferentialDiff::newFromRawChanges($changes);
return head($diff->getChangesets())->getHunks();
}
public function testOneLineOldComment() {
$parser = new DifferentialHunkParser();
$hunks = $this->createSingleChange(1, 0, "-a");
$context = $parser->makeContextDiff(
$hunks,
0,
1,
0,
0);
$this->assertEqual("@@ -1,1 @@\n-a", $context);
}
public function testOneLineNewComment() {
$parser = new DifferentialHunkParser();
$hunks = $this->createSingleChange(0, 1, "+a");
$context = $parser->makeContextDiff(
$hunks,
1,
1,
0,
0);
$this->assertEqual("@@ +1,1 @@\n+a", $context);
}
public function testCannotFindContext() {
$parser = new DifferentialHunkParser();
$hunks = $this->createSingleChange(0, 1, "+a");
$context = $parser->makeContextDiff(
$hunks,
1,
2,
0,
0);
$this->assertEqual("", $context);
}
public function testOverlapFromStartOfHunk() {
$parser = new DifferentialHunkParser();
$hunks = array(
0 => $this->createHunk(23, 2, 42, 2, " 1\n 2"),
);
$context = $parser->makeContextDiff(
$hunks,
1,
41,
1,
0);
$this->assertEqual("@@ -23,1 +42,1 @@\n 1", $context);
}
public function testOverlapAfterEndOfHunk() {
$parser = new DifferentialHunkParser();
$hunks = array(
0 => $this->createHunk(23, 2, 42, 2, " 1\n 2"),
);
$context = $parser->makeContextDiff(
$hunks,
1,
43,
1,
0);
$this->assertEqual("@@ -24,1 +43,1 @@\n 2", $context);
}
public function testInclusionOfNewFileInOldCommentFromStart() {
$parser = new DifferentialHunkParser();
$hunks = $this->createSingleChange(2, 3,
"+n1\n".
" e1/2\n".
"-o2\n".
"+n3\n");
$context = $parser->makeContextDiff(
$hunks,
0,
1,
1,
0);
$this->assertEqual(
"@@ -1,2 +2,1 @@\n".
" e1/2\n".
"-o2", $context);
}
public function testInclusionOfOldFileInNewCommentFromStart() {
$parser = new DifferentialHunkParser();
$hunks = $this->createSingleChange(2, 2,
"-o1\n".
" e2/1\n".
"-o3\n".
"+n2\n");
$context = $parser->makeContextDiff(
$hunks,
1,
1,
1,
0);
$this->assertEqual(
"@@ -2,1 +1,2 @@\n".
" e2/1\n".
"+n2", $context);
}
public function testNoNewlineAtEndOfFile() {
$parser = new DifferentialHunkParser();
$hunks = $this->createSingleChange(0, 1,
"+a\n".
"\\No newline at end of file");
// Note that this only works with additional context.
$context = $parser->makeContextDiff(
$hunks,
1,
2,
0,
1);
$this->assertEqual(
"@@ +1,1 @@\n".
"+a\n".
"\\No newline at end of file", $context);
}
public function testMultiLineNewComment() {
$parser = new DifferentialHunkParser();
$hunks = $this->createSingleChange(7, 7,
" e1\n".
" e2\n".
"-o3\n".
"-o4\n".
"+n3\n".
" e5/4\n".
" e6/5\n".
"+n6\n".
" e7\n");
$context = $parser->makeContextDiff(
$hunks,
1,
2,
4,
0);
$this->assertEqual(
"@@ -2,5 +2,5 @@\n".
" e2\n".
"-o3\n".
"-o4\n".
"+n3\n".
" e5/4\n".
" e6/5\n".
"+n6", $context);
}
public function testMultiLineOldComment() {
$parser = new DifferentialHunkParser();
$hunks = $this->createSingleChange(7, 7,
" e1\n".
" e2\n".
"-o3\n".
"-o4\n".
"+n3\n".
" e5/4\n".
" e6/5\n".
"+n6\n".
" e7\n");
$context = $parser->makeContextDiff(
$hunks,
0,
2,
4,
0);
$this->assertEqual(
"@@ -2,5 +2,4 @@\n".
" e2\n".
"-o3\n".
"-o4\n".
"+n3\n".
" e5/4\n".
" e6/5", $context);
}
public function testInclusionOfNewFileInOldCommentFromStartWithContext() {
$parser = new DifferentialHunkParser();
$hunks = $this->createSingleChange(2, 3,
"+n1\n".
" e1/2\n".
"-o2\n".
"+n3\n");
$context = $parser->makeContextDiff(
$hunks,
0,
1,
1,
1);
$this->assertEqual(
"@@ -1,2 +1,2 @@\n".
"+n1\n".
" e1/2\n".
"-o2", $context);
}
public function testInclusionOfOldFileInNewCommentFromStartWithContext() {
$parser = new DifferentialHunkParser();
$hunks = $this->createSingleChange(2, 2,
"-o1\n".
" e2/1\n".
"-o3\n".
"+n2\n");
$context = $parser->makeContextDiff(
$hunks,
1,
1,
1,
1);
$this->assertEqual(
"@@ -1,3 +1,2 @@\n".
"-o1\n".
" e2/1\n".
"-o3\n".
"+n2", $context);
}
public function testMissingContext() {
$tests = array(
'missing_context.diff' => array(
4 => true,
),
'missing_context_2.diff' => array(
5 => true,
),
'missing_context_3.diff' => array(
4 => true,
13 => true,
),
);
foreach ($tests as $name => $expect) {
$hunks = $this->createHunksFromFile($name);
$parser = new DifferentialHunkParser();
$actual = $parser->getHunkStartLines($hunks);
$this->assertEqual($expect, $actual, $name);
}
}
}
-
diff --git a/src/applications/differential/storage/DifferentialCustomFieldNumericIndex.php b/src/applications/differential/storage/DifferentialCustomFieldNumericIndex.php
index a9155467de..3939d4e205 100644
--- a/src/applications/differential/storage/DifferentialCustomFieldNumericIndex.php
+++ b/src/applications/differential/storage/DifferentialCustomFieldNumericIndex.php
@@ -1,11 +1,10 @@
<?php
final class DifferentialCustomFieldNumericIndex
extends PhabricatorCustomFieldNumericIndexStorage {
public function getApplicationName() {
return 'differential';
}
}
-
diff --git a/src/applications/differential/storage/DifferentialCustomFieldStorage.php b/src/applications/differential/storage/DifferentialCustomFieldStorage.php
index 03de1403b9..7e2874e6ac 100644
--- a/src/applications/differential/storage/DifferentialCustomFieldStorage.php
+++ b/src/applications/differential/storage/DifferentialCustomFieldStorage.php
@@ -1,11 +1,10 @@
<?php
final class DifferentialCustomFieldStorage
extends PhabricatorCustomFieldStorage {
public function getApplicationName() {
return 'differential';
}
}
-
diff --git a/src/applications/differential/storage/DifferentialCustomFieldStringIndex.php b/src/applications/differential/storage/DifferentialCustomFieldStringIndex.php
index e2790f5f8f..6c695329d9 100644
--- a/src/applications/differential/storage/DifferentialCustomFieldStringIndex.php
+++ b/src/applications/differential/storage/DifferentialCustomFieldStringIndex.php
@@ -1,11 +1,10 @@
<?php
final class DifferentialCustomFieldStringIndex
extends PhabricatorCustomFieldStringIndexStorage {
public function getApplicationName() {
return 'differential';
}
}
-
diff --git a/src/applications/differential/view/DifferentialChangesetFileTreeSideNavBuilder.php b/src/applications/differential/view/DifferentialChangesetFileTreeSideNavBuilder.php
index acfc3a7a88..f56c5c13d1 100644
--- a/src/applications/differential/view/DifferentialChangesetFileTreeSideNavBuilder.php
+++ b/src/applications/differential/view/DifferentialChangesetFileTreeSideNavBuilder.php
@@ -1,143 +1,142 @@
<?php
final class DifferentialChangesetFileTreeSideNavBuilder {
private $title;
private $baseURI;
private $anchorName;
private $collapsed = false;
public function setAnchorName($anchor_name) {
$this->anchorName = $anchor_name;
return $this;
}
public function getAnchorName() {
return $this->anchorName;
}
public function setBaseURI(PhutilURI $base_uri) {
$this->baseURI = $base_uri;
return $this;
}
public function getBaseURI() {
return $this->baseURI;
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function getTitle() {
return $this->title;
}
public function setCollapsed($collapsed) {
$this->collapsed = $collapsed;
return $this;
}
public function build(array $changesets) {
assert_instances_of($changesets, 'DifferentialChangeset');
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI($this->getBaseURI());
$nav->setFlexible(true);
$nav->setCollapsed($this->collapsed);
$anchor = $this->getAnchorName();
$tree = new PhutilFileTree();
foreach ($changesets as $changeset) {
try {
$tree->addPath($changeset->getFilename(), $changeset);
} catch (Exception $ex) {
// TODO: See T1702. When viewing the versus diff of diffs, we may
// have files with the same filename. For example, if you have a setup
// like this in SVN:
//
// a/
// README
// b/
// README
//
// ...and you run "arc diff" once from a/, and again from b/, you'll
// get two diffs with path README. However, in the versus diff view we
// will compute their absolute repository paths and detect that they
// aren't really the same file. This is correct, but causes us to
// throw when inserting them.
//
// We should probably compute the smallest unique path for each file
// and show these as "a/README" and "b/README" when diffed against
// one another. However, we get this wrong in a lot of places (the
// other TOC shows two "README" files, and we generate the same anchor
// hash for both) so I'm just stopping the bleeding until we can get
// a proper fix in place.
}
}
require_celerity_resource('phabricator-filetree-view-css');
$filetree = array();
$path = $tree;
while (($path = $path->getNextNode())) {
$data = $path->getData();
$name = $path->getName();
$style = 'padding-left: '.(2 + (3 * $path->getDepth())).'px';
$href = null;
if ($data) {
$href = '#'.$data->getAnchorName();
$title = $name;
$icon = 'phabricator-filetree-icon-file';
} else {
$name .= '/';
$title = $path->getFullPath().'/';
$icon = 'phabricator-filetree-icon-dir';
}
$icon = phutil_tag(
'span',
array(
'class' => 'phabricator-filetree-icon '.$icon,
),
'');
$name_element = phutil_tag(
'span',
array(
'class' => 'phabricator-filetree-name',
),
$name);
$filetree[] = javelin_tag(
$href ? 'a' : 'span',
array(
'href' => $href,
'style' => $style,
'title' => $title,
'class' => 'phabricator-filetree-item',
),
array($icon, $name_element));
}
$tree->destroy();
$filetree = phutil_tag(
'div',
array(
'class' => 'phabricator-filetree',
),
$filetree);
Javelin::initBehavior('phabricator-file-tree', array());
$nav->addLabel(pht('Changed Files'));
$nav->addCustomBlock($filetree);
$nav->setActive(true);
$nav->selectFilter(null);
return $nav;
}
}
-
diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php
index 403522a056..e8b238053d 100644
--- a/src/applications/diffusion/controller/DiffusionServeController.php
+++ b/src/applications/diffusion/controller/DiffusionServeController.php
@@ -1,604 +1,603 @@
<?php
final class DiffusionServeController extends DiffusionController {
public static function isVCSRequest(AphrontRequest $request) {
if (!self::getCallsign($request)) {
return null;
}
$content_type = $request->getHTTPHeader('Content-Type');
$user_agent = idx($_SERVER, 'HTTP_USER_AGENT');
$vcs = null;
if ($request->getExists('service')) {
$service = $request->getStr('service');
// We get this initially for `info/refs`.
// Git also gives us a User-Agent like "git/1.8.2.3".
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
} else if (strncmp($user_agent, "git/", 4) === 0) {
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
} else if ($content_type == 'application/x-git-upload-pack-request') {
// We get this for `git-upload-pack`.
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
} else if ($content_type == 'application/x-git-receive-pack-request') {
// We get this for `git-receive-pack`.
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
} else if ($request->getExists('cmd')) {
// Mercurial also sends an Accept header like
// "application/mercurial-0.1", and a User-Agent like
// "mercurial/proto-1.0".
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL;
} else {
// Subversion also sends an initial OPTIONS request (vs GET/POST), and
// has a User-Agent like "SVN/1.8.3 (x86_64-apple-darwin11.4.2)
// serf/1.3.2".
$dav = $request->getHTTPHeader('DAV');
$dav = new PhutilURI($dav);
if ($dav->getDomain() === 'subversion.tigris.org') {
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_SVN;
}
}
return $vcs;
}
private static function getCallsign(AphrontRequest $request) {
$uri = $request->getRequestURI();
$regex = '@^/diffusion/(?P<callsign>[A-Z]+)(/|$)@';
$matches = null;
if (!preg_match($regex, (string)$uri, $matches)) {
return null;
}
return $matches['callsign'];
}
public function processRequest() {
$request = $this->getRequest();
$callsign = self::getCallsign($request);
// If authentication credentials have been provided, try to find a user
// that actually matches those credentials.
if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
$username = $_SERVER['PHP_AUTH_USER'];
$password = new PhutilOpaqueEnvelope($_SERVER['PHP_AUTH_PW']);
$viewer = $this->authenticateHTTPRepositoryUser($username, $password);
if (!$viewer) {
return new PhabricatorVCSResponse(
403,
pht('Invalid credentials.'));
}
} else {
// User hasn't provided credentials, which means we count them as
// being "not logged in".
$viewer = new PhabricatorUser();
}
$allow_public = PhabricatorEnv::getEnvConfig('policy.allow-public');
$allow_auth = PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth');
if (!$allow_public) {
if (!$viewer->isLoggedIn()) {
if ($allow_auth) {
return new PhabricatorVCSResponse(
401,
pht('You must log in to access repositories.'));
} else {
return new PhabricatorVCSResponse(
403,
pht('Public and authenticated HTTP access are both forbidden.'));
}
}
}
try {
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->withCallsigns(array($callsign))
->executeOne();
if (!$repository) {
return new PhabricatorVCSResponse(
404,
pht('No such repository exists.'));
}
} catch (PhabricatorPolicyException $ex) {
if ($viewer->isLoggedIn()) {
return new PhabricatorVCSResponse(
403,
pht('You do not have permission to access this repository.'));
} else {
if ($allow_auth) {
return new PhabricatorVCSResponse(
401,
pht('You must log in to access this repository.'));
} else {
return new PhabricatorVCSResponse(
403,
pht(
'This repository requires authentication, which is forbidden '.
'over HTTP.'));
}
}
}
if (!$repository->isTracked()) {
return new PhabricatorVCSResponse(
403,
pht('This repository is inactive.'));
}
$is_push = !$this->isReadOnlyRequest($repository);
switch ($repository->getServeOverHTTP()) {
case PhabricatorRepository::SERVE_READONLY:
if ($is_push) {
return new PhabricatorVCSResponse(
403,
pht('This repository is read-only over HTTP.'));
}
break;
case PhabricatorRepository::SERVE_READWRITE:
if ($is_push) {
$can_push = PhabricatorPolicyFilter::hasCapability(
$viewer,
$repository,
DiffusionCapabilityPush::CAPABILITY);
if (!$can_push) {
if ($viewer->isLoggedIn()) {
return new PhabricatorVCSResponse(
403,
pht('You do not have permission to push to this repository.'));
} else {
if ($allow_auth) {
return new PhabricatorVCSResponse(
401,
pht('You must log in to push to this repository.'));
} else {
return new PhabricatorVCSResponse(
403,
pht(
'Pushing to this repository requires authentication, '.
'which is forbidden over HTTP.'));
}
}
}
}
break;
case PhabricatorRepository::SERVE_OFF:
default:
return new PhabricatorVCSResponse(
403,
pht('This repository is not available over HTTP.'));
}
$vcs_type = $repository->getVersionControlSystem();
$req_type = $this->isVCSRequest($request);
if ($vcs_type != $req_type) {
switch ($req_type) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$result = new PhabricatorVCSResponse(
500,
pht('This is not a Git repository.'));
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$result = new PhabricatorVCSResponse(
500,
pht('This is not a Mercurial repository.'));
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$result = new PhabricatorVCSResponse(
500,
pht('This is not a Subversion repository.'));
break;
default:
$result = new PhabricatorVCSResponse(
500,
pht('Unknown request type.'));
break;
}
} else {
switch ($vcs_type) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$result = $this->serveGitRequest($repository, $viewer);
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$result = $this->serveMercurialRequest($repository, $viewer);
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$result = new PhabricatorVCSResponse(
500,
pht(
'Phabricator does not support HTTP access to Subversion '.
'repositories.'));
break;
default:
$result = new PhabricatorVCSResponse(
500,
pht('Unknown version control system.'));
break;
}
}
$code = $result->getHTTPResponseCode();
if ($is_push && ($code == 200)) {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$repository->writeStatusMessage(
PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE,
PhabricatorRepositoryStatusMessage::CODE_OKAY);
unset($unguarded);
}
return $result;
}
private function isReadOnlyRequest(
PhabricatorRepository $repository) {
$request = $this->getRequest();
$method = $_SERVER['REQUEST_METHOD'];
// TODO: This implementation is safe by default, but very incomplete.
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$service = $request->getStr('service');
$path = $this->getRequestDirectoryPath($repository);
// NOTE: Service names are the reverse of what you might expect, as they
// are from the point of view of the server. The main read service is
// "git-upload-pack", and the main write service is "git-receive-pack".
if ($method == 'GET' &&
$path == '/info/refs' &&
$service == 'git-upload-pack') {
return true;
}
if ($path == '/git-upload-pack') {
return true;
}
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$cmd = $request->getStr('cmd');
if ($cmd == 'batch') {
$cmds = idx($this->getMercurialArguments(), 'cmds');
return DiffusionMercurialWireProtocol::isReadOnlyBatchCommand($cmds);
}
return DiffusionMercurialWireProtocol::isReadOnlyCommand($cmd);
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
break;
}
return false;
}
/**
* @phutil-external-symbol class PhabricatorStartup
*/
private function serveGitRequest(
PhabricatorRepository $repository,
PhabricatorUser $viewer) {
$request = $this->getRequest();
$request_path = $this->getRequestDirectoryPath($repository);
$repository_root = $repository->getLocalPath();
// Rebuild the query string to strip `__magic__` parameters and prevent
// issues where we might interpret inputs like "service=read&service=write"
// differently than the server does and pass it an unsafe command.
// NOTE: This does not use getPassthroughRequestParameters() because
// that code is HTTP-method agnostic and will encode POST data.
$query_data = $_GET;
foreach ($query_data as $key => $value) {
if (!strncmp($key, '__', 2)) {
unset($query_data[$key]);
}
}
$query_string = http_build_query($query_data, '', '&');
// We're about to wipe out PATH with the rest of the environment, so
// resolve the binary first.
$bin = Filesystem::resolveBinary('git-http-backend');
if (!$bin) {
throw new Exception("Unable to find `git-http-backend` in PATH!");
}
$env = array(
'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'],
'QUERY_STRING' => $query_string,
'CONTENT_TYPE' => $request->getHTTPHeader('Content-Type'),
'HTTP_CONTENT_ENCODING' => $request->getHTTPHeader('Content-Encoding'),
'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'],
'GIT_PROJECT_ROOT' => $repository_root,
'GIT_HTTP_EXPORT_ALL' => '1',
'PATH_INFO' => $request_path,
'REMOTE_USER' => $viewer->getUsername(),
// TODO: Set these correctly.
// GIT_COMMITTER_NAME
// GIT_COMMITTER_EMAIL
) + $this->getCommonEnvironment($viewer);
$input = PhabricatorStartup::getRawInput();
$command = csprintf('%s', $bin);
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
list($err, $stdout, $stderr) = id(new ExecFuture('%C', $command))
->setEnv($env, true)
->write($input)
->resolve();
if ($err) {
if ($this->isValidGitShallowCloneResponse($stdout, $stderr)) {
// Ignore the error if the response passes this special check for
// validity.
$err = 0;
}
}
if ($err) {
return new PhabricatorVCSResponse(
500,
pht('Error %d: %s', $err, $stderr));
}
return id(new DiffusionGitResponse())->setGitData($stdout);
}
private function getRequestDirectoryPath(PhabricatorRepository $repository) {
$request = $this->getRequest();
$request_path = $request->getRequestURI()->getPath();
$base_path = preg_replace('@^/diffusion/[A-Z]+@', '', $request_path);
// For Git repositories, strip an optional directory component if it
// isn't the name of a known Git resource. This allows users to clone
// repositories as "/diffusion/X/anything.git", for example.
if ($repository->isGit()) {
$known = array(
'info',
'git-upload-pack',
'git-receive-pack',
);
foreach ($known as $key => $path) {
$known[$key] = preg_quote($path, '@');
}
$known = implode('|', $known);
if (preg_match('@^/([^/]+)/('.$known.')(/|$)@', $base_path)) {
$base_path = preg_replace('@^/([^/]+)@', '', $base_path);
}
}
return $base_path;
}
private function authenticateHTTPRepositoryUser(
$username,
PhutilOpaqueEnvelope $password) {
if (!PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth')) {
// No HTTP auth permitted.
return null;
}
if (!strlen($username)) {
// No username.
return null;
}
if (!strlen($password->openEnvelope())) {
// No password.
return null;
}
$user = id(new PhabricatorPeopleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withUsernames(array($username))
->executeOne();
if (!$user) {
// Username doesn't match anything.
return null;
}
if (!$user->isUserActivated()) {
// User is not activated.
return null;
}
$password_entry = id(new PhabricatorRepositoryVCSPassword())
->loadOneWhere('userPHID = %s', $user->getPHID());
if (!$password_entry) {
// User doesn't have a password set.
return null;
}
if (!$password_entry->comparePassword($password, $user)) {
// Password doesn't match.
return null;
}
// If the user's password is stored using a less-than-optimal hash, upgrade
// them to the strongest available hash.
$hash_envelope = new PhutilOpaqueEnvelope(
$password_entry->getPasswordHash());
if (PhabricatorPasswordHasher::canUpgradeHash($hash_envelope)) {
$password_entry->setPassword($password, $user);
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$password_entry->save();
unset($unguarded);
}
return $user;
}
private function serveMercurialRequest(
PhabricatorRepository $repository,
PhabricatorUser $viewer) {
$request = $this->getRequest();
$bin = Filesystem::resolveBinary('hg');
if (!$bin) {
throw new Exception("Unable to find `hg` in PATH!");
}
$env = $this->getCommonEnvironment($viewer);
$input = PhabricatorStartup::getRawInput();
$cmd = $request->getStr('cmd');
$args = $this->getMercurialArguments();
$args = $this->formatMercurialArguments($cmd, $args);
if (strlen($input)) {
$input = strlen($input)."\n".$input."0\n";
}
$command = csprintf('%s serve --stdio', $bin);
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
list($err, $stdout, $stderr) = id(new ExecFuture('%C', $command))
->setEnv($env, true)
->setCWD($repository->getLocalPath())
->write("{$cmd}\n{$args}{$input}")
->resolve();
if ($err) {
return new PhabricatorVCSResponse(
500,
pht('Error %d: %s', $err, $stderr));
}
if ($cmd == 'getbundle' ||
$cmd == 'changegroup' ||
$cmd == 'changegroupsubset') {
// We're not completely sure that "changegroup" and "changegroupsubset"
// actually work, they're for very old Mercurial.
$body = gzcompress($stdout);
} else if ($cmd == 'unbundle') {
// This includes diagnostic information and anything echoed by commit
// hooks. We ignore `stdout` since it just has protocol garbage, and
// substitute `stderr`.
$body = strlen($stderr)."\n".$stderr;
} else {
list($length, $body) = explode("\n", $stdout, 2);
}
return id(new DiffusionMercurialResponse())->setContent($body);
}
private function getMercurialArguments() {
// Mercurial sends arguments in HTTP headers. "Why?", you might wonder,
// "Why would you do this?".
$args_raw = array();
for ($ii = 1; ; $ii++) {
$header = 'HTTP_X_HGARG_'.$ii;
if (!array_key_exists($header, $_SERVER)) {
break;
}
$args_raw[] = $_SERVER[$header];
}
$args_raw = implode('', $args_raw);
return id(new PhutilQueryStringParser())
->parseQueryString($args_raw);
}
private function formatMercurialArguments($command, array $arguments) {
$spec = DiffusionMercurialWireProtocol::getCommandArgs($command);
$out = array();
// Mercurial takes normal arguments like this:
//
// name <length(value)>
// value
$has_star = false;
foreach ($spec as $arg_key) {
if ($arg_key == '*') {
$has_star = true;
continue;
}
if (isset($arguments[$arg_key])) {
$value = $arguments[$arg_key];
$size = strlen($value);
$out[] = "{$arg_key} {$size}\n{$value}";
unset($arguments[$arg_key]);
}
}
if ($has_star) {
// Mercurial takes arguments for variable argument lists roughly like
// this:
//
// * <count(args)>
// argname1 <length(argvalue1)>
// argvalue1
// argname2 <length(argvalue2)>
// argvalue2
$count = count($arguments);
$out[] = "* {$count}\n";
foreach ($arguments as $key => $value) {
if (in_array($key, $spec)) {
// We already added this argument above, so skip it.
continue;
}
$size = strlen($value);
$out[] = "{$key} {$size}\n{$value}";
}
}
return implode('', $out);
}
private function isValidGitShallowCloneResponse($stdout, $stderr) {
// If you execute `git clone --depth N ...`, git sends a request which
// `git-http-backend` responds to by emitting valid output and then exiting
// with a failure code and an error message. If we ignore this error,
// everything works.
// This is a pretty funky fix: it would be nice to more precisely detect
// that a request is a `--depth N` clone request, but we don't have any code
// to decode protocol frames yet. Instead, look for reasonable evidence
// in the error and output that we're looking at a `--depth` clone.
// For evidence this isn't completely crazy, see:
// https://github.com/schacon/grack/pull/7
$stdout_regexp = '(^Content-Type: application/x-git-upload-pack-result)m';
$stderr_regexp = '(The remote end hung up unexpectedly)';
$has_pack = preg_match($stdout_regexp, $stdout);
$is_hangup = preg_match($stderr_regexp, $stderr);
return $has_pack && $is_hangup;
}
private function getCommonEnvironment(PhabricatorUser $viewer) {
$remote_addr = $this->getRequest()->getRemoteAddr();
return array(
DiffusionCommitHookEngine::ENV_USER => $viewer->getUsername(),
DiffusionCommitHookEngine::ENV_REMOTE_ADDRESS => $remote_addr,
DiffusionCommitHookEngine::ENV_REMOTE_PROTOCOL => 'http',
);
}
}
-
diff --git a/src/applications/diffusion/events/DiffusionHovercardEventListener.php b/src/applications/diffusion/events/DiffusionHovercardEventListener.php
index ccba5c8260..ebd91d994c 100644
--- a/src/applications/diffusion/events/DiffusionHovercardEventListener.php
+++ b/src/applications/diffusion/events/DiffusionHovercardEventListener.php
@@ -1,76 +1,75 @@
<?php
final class DiffusionHovercardEventListener extends PhabricatorEventListener {
public function register() {
$this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD);
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD:
$this->handleHovercardEvent($event);
break;
}
}
private function handleHovercardEvent($event) {
$viewer = $event->getUser();
$hovercard = $event->getValue('hovercard');
$object_handle = $event->getValue('handle');
$commit = $event->getValue('object');
if (!($commit instanceof PhabricatorRepositoryCommit)) {
return;
}
$commit_data = $commit->loadCommitData();
$revision = PhabricatorEdgeQuery::loadDestinationPHIDs(
$commit->getPHID(),
PhabricatorEdgeConfig::TYPE_COMMIT_HAS_DREV);
$revision = reset($revision);
$author = $commit->getAuthorPHID();
$phids = array_filter(array(
$revision,
$author,
));
$handles = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs($phids)
->execute();
if ($author) {
$author = $handles[$author]->renderLink();
} else {
$author = phutil_tag('em', array(), $commit_data->getAuthorName());
}
$hovercard->setTitle($object_handle->getName());
$hovercard->setDetail($commit->getSummary());
$hovercard->addField(pht('Author'), $author);
$hovercard->addField(pht('Date'),
phabricator_date($commit->getEpoch(), $viewer));
if ($commit->getAuditStatus() !=
PhabricatorAuditCommitStatusConstants::NONE) {
$hovercard->addField(pht('Audit Status'),
PhabricatorAuditCommitStatusConstants::getStatusName(
$commit->getAuditStatus()));
}
if ($revision) {
$rev_handle = $handles[$revision];
$hovercard->addField(pht('Revision'), $rev_handle->renderLink());
}
$event->setValue('hovercard', $hovercard);
}
}
-
diff --git a/src/applications/diffusion/query/pathid/__tests__/DiffusionPathQueryTestCase.php b/src/applications/diffusion/query/pathid/__tests__/DiffusionPathQueryTestCase.php
index 00602372a0..b0c6183761 100644
--- a/src/applications/diffusion/query/pathid/__tests__/DiffusionPathQueryTestCase.php
+++ b/src/applications/diffusion/query/pathid/__tests__/DiffusionPathQueryTestCase.php
@@ -1,43 +1,42 @@
<?php
final class DiffusionPathQueryTestCase extends PhabricatorTestCase {
public function testParentEdgeCases() {
$this->assertEqual(
'/',
DiffusionPathIDQuery::getParentPath('/'),
'Parent of /');
$this->assertEqual(
'/',
DiffusionPathIDQuery::getParentPath('x.txt'),
'Parent of x.txt');
$this->assertEqual(
'/a',
DiffusionPathIDQuery::getParentPath('/a/b'),
'Parent of /a/b');
$this->assertEqual(
'/a',
DiffusionPathIDQuery::getParentPath('/a///b'),
'Parent of /a///b');
}
public function testExpandEdgeCases() {
$this->assertEqual(
array('/'),
DiffusionPathIDQuery::expandPathToRoot('/'));
$this->assertEqual(
array('/'),
DiffusionPathIDQuery::expandPathToRoot('//'));
$this->assertEqual(
array('/a/b', '/a', '/'),
DiffusionPathIDQuery::expandPathToRoot('/a/b'));
$this->assertEqual(
array('/a/b', '/a', '/'),
DiffusionPathIDQuery::expandPathToRoot('/a//b'));
$this->assertEqual(
array('/a/b', '/a', '/'),
DiffusionPathIDQuery::expandPathToRoot('a/b'));
}
}
-
diff --git a/src/applications/diviner/application/PhabricatorApplicationDiviner.php b/src/applications/diviner/application/PhabricatorApplicationDiviner.php
index a0b140ae77..3ff59349f5 100644
--- a/src/applications/diviner/application/PhabricatorApplicationDiviner.php
+++ b/src/applications/diviner/application/PhabricatorApplicationDiviner.php
@@ -1,75 +1,74 @@
<?php
final class PhabricatorApplicationDiviner extends PhabricatorApplication {
public function getBaseURI() {
return '/diviner/';
}
public function getIconName() {
return 'diviner';
}
public function getShortDescription() {
return pht('Documentation');
}
public function getTitleGlyph() {
return "\xE2\x97\x89";
}
public function getRoutes() {
return array(
'/diviner/' => array(
'' => 'DivinerLegacyController',
'query/((?<key>[^/]+)/)?' => 'DivinerAtomListController',
'find/' => 'DivinerFindController',
),
'/docs/(?P<keyword>[^/]+)/' => 'DivinerJumpController',
'/book/(?P<book>[^/]+)/' => 'DivinerBookController',
'/book/'.
'(?P<book>[^/]+)/'.
'(?P<type>[^/]+)/'.
'(?:(?P<context>[^/]+)/)?'.
'(?P<name>[^/]+)/'.
'(?:(?P<index>\d+)/)?' => 'DivinerAtomController',
);
}
public function getApplicationGroup() {
return self::GROUP_COMMUNICATION;
}
public function getRemarkupRules() {
return array(
new DivinerRemarkupRuleSymbol(),
);
}
public function buildMainMenuItems(
PhabricatorUser $user,
PhabricatorController $controller = null) {
$items = array();
$application = null;
if ($controller) {
$application = $controller->getCurrentApplication();
}
if ($application && $application->getHelpURI()) {
$item = id(new PHUIListItemView())
->setName(pht('%s Help', $application->getName()))
->addClass('core-menu-item')
->setIcon('info-sm')
->setOrder(200)
->setHref($application->getHelpURI());
$items[] = $item;
}
return $items;
}
}
-
diff --git a/src/applications/diviner/atomizer/DivinerPHPAtomizer.php b/src/applications/diviner/atomizer/DivinerPHPAtomizer.php
index 0c344aee13..18405b9116 100644
--- a/src/applications/diviner/atomizer/DivinerPHPAtomizer.php
+++ b/src/applications/diviner/atomizer/DivinerPHPAtomizer.php
@@ -1,319 +1,318 @@
<?php
final class DivinerPHPAtomizer extends DivinerAtomizer {
protected function newAtom($type) {
return parent::newAtom($type)->setLanguage('php');
}
protected function executeAtomize($file_name, $file_data) {
$future = xhpast_get_parser_future($file_data);
$tree = XHPASTTree::newFromDataAndResolvedExecFuture(
$file_data,
$future->resolve());
$atoms = array();
$root = $tree->getRootNode();
$func_decl = $root->selectDescendantsOfType('n_FUNCTION_DECLARATION');
foreach ($func_decl as $func) {
$name = $func->getChildByIndex(2);
$atom = $this->newAtom(DivinerAtom::TYPE_FUNCTION)
->setName($name->getConcreteString())
->setLine($func->getLineNumber())
->setFile($file_name);
$this->findAtomDocblock($atom, $func);
$this->parseParams($atom, $func);
$this->parseReturnType($atom, $func);
$atoms[] = $atom;
}
$class_types = array(
DivinerAtom::TYPE_CLASS => 'n_CLASS_DECLARATION',
DivinerAtom::TYPE_INTERFACE => 'n_INTERFACE_DECLARATION',
);
foreach ($class_types as $atom_type => $node_type) {
$class_decls = $root->selectDescendantsOfType($node_type);
foreach ($class_decls as $class) {
$name = $class->getChildByIndex(1, 'n_CLASS_NAME');
$atom = $this->newAtom($atom_type)
->setName($name->getConcreteString())
->setFile($file_name)
->setLine($class->getLineNumber());
// This parses "final" and "abstract".
$attributes = $class->getChildByIndex(0, 'n_CLASS_ATTRIBUTES');
foreach ($attributes->selectDescendantsOfType('n_STRING') as $attr) {
$atom->setProperty($attr->getConcreteString(), true);
}
// If this exists, it is n_EXTENDS_LIST.
$extends = $class->getChildByIndex(2);
$extends_class = $extends->selectDescendantsOfType('n_CLASS_NAME');
foreach ($extends_class as $parent_class) {
$atom->addExtends(
$this->newRef(
DivinerAtom::TYPE_CLASS,
$parent_class->getConcreteString()));
}
// If this exists, it is n_IMPLEMENTS_LIST.
$implements = $class->getChildByIndex(3);
$iface_names = $implements->selectDescendantsOfType('n_CLASS_NAME');
foreach ($iface_names as $iface_name) {
$atom->addExtends(
$this->newRef(
DivinerAtom::TYPE_INTERFACE,
$iface_name->getConcreteString()));
}
$this->findAtomDocblock($atom, $class);
$methods = $class->selectDescendantsOfType('n_METHOD_DECLARATION');
foreach ($methods as $method) {
$matom = $this->newAtom(DivinerAtom::TYPE_METHOD);
$this->findAtomDocblock($matom, $method);
$attribute_list = $method->getChildByIndex(0);
$attributes = $attribute_list->selectDescendantsOfType('n_STRING');
if ($attributes) {
foreach ($attributes as $attribute) {
$attr = strtolower($attribute->getConcreteString());
switch ($attr) {
case 'final':
case 'abstract':
case 'static':
$matom->setProperty($attr, true);
break;
case 'public':
case 'protected':
case 'private':
$matom->setProperty('access', $attr);
break;
}
}
} else {
$matom->setProperty('access', 'public');
}
$this->parseParams($matom, $method);
$matom->setName($method->getChildByIndex(2)->getConcreteString());
$matom->setLine($method->getLineNumber());
$matom->setFile($file_name);
$this->parseReturnType($matom, $method);
$atom->addChild($matom);
$atoms[] = $matom;
}
$atoms[] = $atom;
}
}
return $atoms;
}
private function parseParams(DivinerAtom $atom, AASTNode $func) {
$params = $func
->getChildByIndex(3, 'n_DECLARATAION_PARAMETER_LIST')
->selectDescendantsOfType('n_DECLARATION_PARAMETER');
$param_spec = array();
if ($atom->getDocblockRaw()) {
$metadata = $atom->getDocblockMeta();
} else {
$metadata = array();
}
$docs = idx($metadata, 'param');
if ($docs) {
$docs = explode("\n", $docs);
$docs = array_filter($docs);
} else {
$docs = array();
}
if (count($docs)) {
if (count($docs) < count($params)) {
$atom->addWarning(
pht(
'This call takes %d parameters, but only %d are documented.',
count($params),
count($docs)));
}
}
foreach ($params as $param) {
$name = $param->getChildByIndex(1)->getConcreteString();
$dict = array(
'type' => $param->getChildByIndex(0)->getConcreteString(),
'default' => $param->getChildByIndex(2)->getConcreteString(),
);
if ($docs) {
$doc = array_shift($docs);
if ($doc) {
$dict += $this->parseParamDoc($atom, $doc, $name);
}
}
$param_spec[] = array(
'name' => $name,
) + $dict;
}
if ($docs) {
foreach ($docs as $doc) {
if ($doc) {
$param_spec[] = $this->parseParamDoc($atom, $doc, null);
}
}
}
// TODO: Find `assert_instances_of()` calls in the function body and
// add their type information here. See T1089.
$atom->setProperty('parameters', $param_spec);
}
private function findAtomDocblock(DivinerAtom $atom, XHPASTNode $node) {
$token = $node->getDocblockToken();
if ($token) {
$atom->setDocblockRaw($token->getValue());
return true;
} else {
$tokens = $node->getTokens();
if ($tokens) {
$prev = head($tokens);
while ($prev = $prev->getPrevToken()) {
if ($prev->isAnyWhitespace()) {
continue;
}
break;
}
if ($prev && $prev->isComment()) {
$value = $prev->getValue();
$matches = null;
if (preg_match('/@(return|param|task|author)/', $value, $matches)) {
$atom->addWarning(
pht(
'Atom "%s" is preceded by a comment containing "@%s", but the '.
'comment is not a documentation comment. Documentation '.
'comments must begin with "/**", followed by a newline. Did '.
'you mean to use a documentation comment? (As the comment is '.
'not a documentation comment, it will be ignored.)',
$atom->getName(),
$matches[1]));
}
}
}
$atom->setDocblockRaw('');
return false;
}
}
protected function parseParamDoc(DivinerAtom $atom, $doc, $name) {
$dict = array();
$split = preg_split('/\s+/', trim($doc), $limit = 2);
if (!empty($split[0])) {
$dict['doctype'] = $split[0];
}
if (!empty($split[1])) {
$docs = $split[1];
// If the parameter is documented like "@param int $num Blah blah ..",
// get rid of the `$num` part (which Diviner considers optional). If it
// is present and different from the declared name, raise a warning.
$matches = null;
if (preg_match('/^(\\$\S+)\s+/', $docs, $matches)) {
if ($name !== null) {
if ($matches[1] !== $name) {
$atom->addWarning(
pht(
'Parameter "%s" is named "%s" in the documentation. The '.
'documentation may be out of date.',
$name,
$matches[1]));
}
}
$docs = substr($docs, strlen($matches[0]));
}
$dict['docs'] = $docs;
}
return $dict;
}
private function parseReturnType(DivinerAtom $atom, XHPASTNode $decl) {
$return_spec = array();
$metadata = $atom->getDocblockMeta();
$return = idx($metadata, 'return');
if (!$return) {
$return = idx($metadata, 'returns');
if ($return) {
$atom->addWarning(
pht('Documentation uses `@returns`, but should use `@return`.'));
}
}
if ($atom->getName() == '__construct' && $atom->getType() == 'method') {
$return_spec = array(
'doctype' => 'this',
'docs' => '//Implicit.//',
);
if ($return) {
$atom->addWarning(
'Method __construct() has explicitly documented @return. The '.
'__construct() method always returns $this. Diviner documents '.
'this implicitly.');
}
} else if ($return) {
$split = preg_split('/\s+/', trim($return), $limit = 2);
if (!empty($split[0])) {
$type = $split[0];
}
if ($decl->getChildByIndex(1)->getTypeName() == 'n_REFERENCE') {
$type = $type.' &';
}
$docs = null;
if (!empty($split[1])) {
$docs = $split[1];
}
$return_spec = array(
'doctype' => $type,
'docs' => $docs,
);
} else {
$return_spec = array(
'type' => 'wild',
);
}
$atom->setProperty('return', $return_spec);
}
}
-
diff --git a/src/applications/drydock/worker/DrydockAllocatorWorker.php b/src/applications/drydock/worker/DrydockAllocatorWorker.php
index 6c2064c587..0e9d99aa97 100644
--- a/src/applications/drydock/worker/DrydockAllocatorWorker.php
+++ b/src/applications/drydock/worker/DrydockAllocatorWorker.php
@@ -1,183 +1,181 @@
<?php
final class DrydockAllocatorWorker extends PhabricatorWorker {
private $lease;
public function getMaximumRetryCount() {
// TODO: Allow Drydock allocations to retry. For now, every failure is
// permanent and most of them are because I am bad at programming, so fail
// fast rather than ending up in limbo.
return 0;
}
private function loadLease() {
if (empty($this->lease)) {
$lease = id(new DrydockLeaseQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withIDs(array($this->getTaskData()))
->executeOne();
if (!$lease) {
throw new PhabricatorWorkerPermanentFailureException(
pht("No such lease %d!", $this->getTaskData()));
}
$this->lease = $lease;
}
return $this->lease;
}
private function logToDrydock($message) {
DrydockBlueprintImplementation::writeLog(
null,
$this->loadLease(),
$message);
}
protected function doWork() {
$lease = $this->loadLease();
$this->logToDrydock('Allocating Lease');
try {
$this->allocateLease($lease);
} catch (Exception $ex) {
// TODO: We should really do this when archiving the task, if we've
// suffered a permanent failure. But we don't have hooks for that yet
// and always fail after the first retry right now, so this is
// functionally equivalent.
$lease->reload();
if ($lease->getStatus() == DrydockLeaseStatus::STATUS_PENDING) {
$lease->setStatus(DrydockLeaseStatus::STATUS_BROKEN);
$lease->save();
}
throw $ex;
}
}
private function loadAllBlueprints() {
$viewer = PhabricatorUser::getOmnipotentUser();
$instances = id(new DrydockBlueprintQuery())
->setViewer($viewer)
->execute();
$blueprints = array();
foreach ($instances as $instance) {
$blueprints[$instance->getPHID()] = $instance;
}
return $blueprints;
}
private function allocateLease(DrydockLease $lease) {
$type = $lease->getResourceType();
$blueprints = $this->loadAllBlueprints();
// TODO: Policy stuff.
$pool = id(new DrydockResource())->loadAllWhere(
'type = %s AND status = %s',
$lease->getResourceType(),
DrydockResourceStatus::STATUS_OPEN);
$this->logToDrydock(
pht('Found %d Open Resource(s)', count($pool)));
$candidates = array();
foreach ($pool as $key => $candidate) {
if (!isset($blueprints[$candidate->getBlueprintPHID()])) {
unset($pool[$key]);
continue;
}
$blueprint = $blueprints[$candidate->getBlueprintPHID()];
$implementation = $blueprint->getImplementation();
if ($implementation->filterResource($candidate, $lease)) {
$candidates[] = $candidate;
}
}
$this->logToDrydock(pht('%d Open Resource(s) Remain', count($candidates)));
$resource = null;
if ($candidates) {
shuffle($candidates);
foreach ($candidates as $candidate_resource) {
$blueprint = $blueprints[$candidate_resource->getBlueprintPHID()]
->getImplementation();
if ($blueprint->allocateLease($candidate_resource, $lease)) {
$resource = $candidate_resource;
break;
}
}
}
if (!$resource) {
$blueprints = DrydockBlueprintImplementation
::getAllBlueprintImplementationsForResource($type);
$this->logToDrydock(
pht('Found %d Blueprints', count($blueprints)));
foreach ($blueprints as $key => $candidate_blueprint) {
if (!$candidate_blueprint->isEnabled()) {
unset($blueprints[$key]);
continue;
}
}
$this->logToDrydock(
pht('%d Blueprints Enabled', count($blueprints)));
foreach ($blueprints as $key => $candidate_blueprint) {
if (!$candidate_blueprint->canAllocateMoreResources($pool)) {
unset($blueprints[$key]);
continue;
}
}
$this->logToDrydock(
pht('%d Blueprints Can Allocate', count($blueprints)));
if (!$blueprints) {
$lease->setStatus(DrydockLeaseStatus::STATUS_BROKEN);
$lease->save();
$this->logToDrydock(
"There are no resources of type '{$type}' available, and no ".
"blueprints which can allocate new ones.");
return;
}
// TODO: Rank intelligently.
shuffle($blueprints);
$blueprint = head($blueprints);
$resource = $blueprint->allocateResource($lease);
if (!$blueprint->allocateLease($resource, $lease)) {
// TODO: This "should" happen only if we lost a race with another lease,
// which happened to acquire this resource immediately after we
// allocated it. In this case, the right behavior is to retry
// immediately. However, other things like a blueprint allocating a
// resource it can't actually allocate the lease on might be happening
// too, in which case we'd just allocate infinite resources. Probably
// what we should do is test for an active or allocated lease and retry
// if we find one (although it might have already been released by now)
// and fail really hard ("your configuration is a huge broken mess")
// otherwise. But just throw for now since this stuff is all edge-casey.
// Alternatively we could bring resources up in a "BESPOKE" status
// and then switch them to "OPEN" only after the allocating lease gets
// its grubby mitts on the resource. This might make more sense but
// is a bit messy.
throw new Exception("Lost an allocation race?");
}
}
$blueprint = $resource->getBlueprint();
$blueprint->acquireLease($resource, $lease);
}
}
-
-
diff --git a/src/applications/feed/application/PhabricatorApplicationFeed.php b/src/applications/feed/application/PhabricatorApplicationFeed.php
index 4f2cafc8d4..21c8092e3d 100644
--- a/src/applications/feed/application/PhabricatorApplicationFeed.php
+++ b/src/applications/feed/application/PhabricatorApplicationFeed.php
@@ -1,36 +1,35 @@
<?php
final class PhabricatorApplicationFeed extends PhabricatorApplication {
public function getBaseURI() {
return '/feed/';
}
public function getShortDescription() {
return pht('Review Activity');
}
public function getIconName() {
return 'feed';
}
public function canUninstall() {
return false;
}
public function getRoutes() {
return array(
'/feed/' => array(
'public/' => 'PhabricatorFeedPublicStreamController',
'(?P<id>\d+)/' => 'PhabricatorFeedDetailController',
'(?:query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorFeedListController',
),
);
}
public function getApplicationGroup() {
return self::GROUP_COMMUNICATION;
}
}
-
diff --git a/src/applications/flag/application/PhabricatorApplicationFlags.php b/src/applications/flag/application/PhabricatorApplicationFlags.php
index 51737fed67..d7d16997f2 100644
--- a/src/applications/flag/application/PhabricatorApplicationFlags.php
+++ b/src/applications/flag/application/PhabricatorApplicationFlags.php
@@ -1,61 +1,60 @@
<?php
final class PhabricatorApplicationFlags extends PhabricatorApplication {
public function getShortDescription() {
return pht('Reminders');
}
public function getBaseURI() {
return '/flag/';
}
public function getIconName() {
return 'flags';
}
public function getEventListeners() {
return array(
new PhabricatorFlagsUIEventListener(),
);
}
public function getTitleGlyph() {
return "\xE2\x9A\x90";
}
public function getApplicationGroup() {
return self::GROUP_ORGANIZATION;
}
public function loadStatus(PhabricatorUser $user) {
$status = array();
$flags = id(new PhabricatorFlagQuery())
->setViewer($user)
->withOwnerPHIDs(array($user->getPHID()))
->execute();
$count = count($flags);
$type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION;
$status[] = id(new PhabricatorApplicationStatusView())
->setType($type)
->setText(pht('%d Flagged Object(s)', $count))
->setCount($count);
return $status;
}
public function getRoutes() {
return array(
'/flag/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorFlagListController',
'view/(?P<view>[^/]+)/' => 'PhabricatorFlagListController',
'edit/(?P<phid>[^/]+)/' => 'PhabricatorFlagEditController',
'delete/(?P<id>[1-9]\d*)/' => 'PhabricatorFlagDeleteController',
),
);
}
}
-
diff --git a/src/applications/flag/events/PhabricatorFlagsUIEventListener.php b/src/applications/flag/events/PhabricatorFlagsUIEventListener.php
index 04314d311c..df7b1d03d7 100644
--- a/src/applications/flag/events/PhabricatorFlagsUIEventListener.php
+++ b/src/applications/flag/events/PhabricatorFlagsUIEventListener.php
@@ -1,62 +1,61 @@
<?php
final class PhabricatorFlagsUIEventListener extends PhabricatorEventListener {
public function register() {
$this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS);
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS:
$this->handleActionEvent($event);
break;
}
}
private function handleActionEvent($event) {
$user = $event->getUser();
$object = $event->getValue('object');
if (!$object || !$object->getPHID()) {
// If we have no object, or the object doesn't have a PHID yet, we can't
// flag it.
return;
}
if (!($object instanceof PhabricatorFlaggableInterface)) {
return;
}
if (!$this->canUseApplication($event->getUser())) {
return null;
}
$flag = PhabricatorFlagQuery::loadUserFlag($user, $object->getPHID());
if ($flag) {
$color = PhabricatorFlagColor::getColorName($flag->getColor());
$flag_action = id(new PhabricatorActionView())
->setWorkflow(true)
->setHref('/flag/delete/'.$flag->getID().'/')
->setName(pht('Remove %s Flag', $color))
->setIcon('flag-'.$flag->getColor());
} else {
$flag_action = id(new PhabricatorActionView())
->setWorkflow(true)
->setHref('/flag/edit/'.$object->getPHID().'/')
->setName(pht('Flag For Later'))
->setIcon('flag');
if (!$user->isLoggedIn()) {
$flag_action->setDisabled(true);
}
}
$actions = $event->getValue('actions');
$actions[] = $flag_action;
$event->setValue('actions', $actions);
}
}
-
diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php
index 136a0b1cb0..4f5343d445 100644
--- a/src/applications/herald/adapter/HeraldAdapter.php
+++ b/src/applications/herald/adapter/HeraldAdapter.php
@@ -1,1080 +1,1079 @@
<?php
/**
* @group herald
*/
abstract class HeraldAdapter {
const FIELD_TITLE = 'title';
const FIELD_BODY = 'body';
const FIELD_AUTHOR = 'author';
const FIELD_ASSIGNEE = 'assignee';
const FIELD_REVIEWER = 'reviewer';
const FIELD_REVIEWERS = 'reviewers';
const FIELD_COMMITTER = 'committer';
const FIELD_CC = 'cc';
const FIELD_TAGS = 'tags';
const FIELD_DIFF_FILE = 'diff-file';
const FIELD_DIFF_CONTENT = 'diff-content';
const FIELD_DIFF_ADDED_CONTENT = 'diff-added-content';
const FIELD_DIFF_REMOVED_CONTENT = 'diff-removed-content';
const FIELD_DIFF_ENORMOUS = 'diff-enormous';
const FIELD_REPOSITORY = 'repository';
const FIELD_REPOSITORY_PROJECTS = 'repository-projects';
const FIELD_RULE = 'rule';
const FIELD_AFFECTED_PACKAGE = 'affected-package';
const FIELD_AFFECTED_PACKAGE_OWNER = 'affected-package-owner';
const FIELD_CONTENT_SOURCE = 'contentsource';
const FIELD_ALWAYS = 'always';
const FIELD_AUTHOR_PROJECTS = 'authorprojects';
const FIELD_PROJECTS = 'projects';
const FIELD_PUSHER = 'pusher';
const FIELD_PUSHER_PROJECTS = 'pusher-projects';
const FIELD_DIFFERENTIAL_REVISION = 'differential-revision';
const FIELD_DIFFERENTIAL_REVIEWERS = 'differential-reviewers';
const FIELD_DIFFERENTIAL_CCS = 'differential-ccs';
const FIELD_DIFFERENTIAL_ACCEPTED = 'differential-accepted';
const FIELD_IS_MERGE_COMMIT = 'is-merge-commit';
const FIELD_BRANCHES = 'branches';
const FIELD_AUTHOR_RAW = 'author-raw';
const FIELD_COMMITTER_RAW = 'committer-raw';
const FIELD_IS_NEW_OBJECT = 'new-object';
const FIELD_TASK_PRIORITY = 'taskpriority';
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_UNCONDITIONALLY = 'unconditionally';
const CONDITION_REGEXP_PAIR = 'regexp-pair';
const CONDITION_HAS_BIT = 'bit';
const CONDITION_NOT_BIT = '!bit';
const CONDITION_IS_TRUE = 'true';
const CONDITION_IS_FALSE = 'false';
const ACTION_ADD_CC = 'addcc';
const ACTION_REMOVE_CC = 'remcc';
const ACTION_EMAIL = 'email';
const ACTION_NOTHING = 'nothing';
const ACTION_AUDIT = 'audit';
const ACTION_FLAG = 'flag';
const ACTION_ASSIGN_TASK = 'assigntask';
const ACTION_ADD_PROJECTS = 'addprojects';
const ACTION_ADD_REVIEWERS = 'addreviewers';
const ACTION_ADD_BLOCKING_REVIEWERS = 'addblockingreviewers';
const ACTION_APPLY_BUILD_PLANS = 'applybuildplans';
const ACTION_BLOCK = 'block';
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';
const VALUE_FLAG_COLOR = 'flagcolor';
const VALUE_CONTENT_SOURCE = 'contentsource';
const VALUE_USER_OR_PROJECT = 'userorproject';
const VALUE_BUILD_PLAN = 'buildplan';
const VALUE_TASK_PRIORITY = 'taskpriority';
private $contentSource;
private $isNewObject;
public function setContentSource(PhabricatorContentSource $content_source) {
$this->contentSource = $content_source;
return $this;
}
public function getContentSource() {
return $this->contentSource;
}
public function getIsNewObject() {
if (is_bool($this->isNewObject)) {
return $this->isNewObject;
}
throw new Exception(pht('You must setIsNewObject to a boolean first!'));
}
public function setIsNewObject($new) {
$this->isNewObject = (bool) $new;
return $this;
}
abstract public function getPHID();
abstract public function getHeraldName();
public function getHeraldField($field_name) {
switch ($field_name) {
case self::FIELD_RULE:
return null;
case self::FIELD_CONTENT_SOURCE:
return $this->getContentSource()->getSource();
case self::FIELD_ALWAYS:
return true;
case self::FIELD_IS_NEW_OBJECT:
return $this->getIsNewObject();
default:
throw new Exception(
"Unknown field '{$field_name}'!");
}
}
abstract public function applyHeraldEffects(array $effects);
public function isAvailableToUser(PhabricatorUser $viewer) {
$applications = id(new PhabricatorApplicationQuery())
->setViewer($viewer)
->withInstalled(true)
->withClasses(array($this->getAdapterApplicationClass()))
->execute();
return !empty($applications);
}
/**
* NOTE: You generally should not override this; it exists to support legacy
* adapters which had hard-coded content types.
*/
public function getAdapterContentType() {
return get_class($this);
}
abstract public function getAdapterContentName();
abstract public function getAdapterContentDescription();
abstract public function getAdapterApplicationClass();
abstract public function getObject();
public function supportsRuleType($rule_type) {
return false;
}
public function canTriggerOnObject($object) {
return false;
}
public function explainValidTriggerObjects() {
return pht('This adapter can not trigger on objects.');
}
public function getTriggerObjectPHIDs() {
return array($this->getPHID());
}
public function getAdapterSortKey() {
return sprintf(
'%08d%s',
$this->getAdapterSortOrder(),
$this->getAdapterContentName());
}
public function getAdapterSortOrder() {
return 1000;
}
/* -( Fields )------------------------------------------------------------- */
public function getFields() {
return array(
self::FIELD_ALWAYS,
self::FIELD_RULE,
);
}
public function getFieldNameMap() {
return array(
self::FIELD_TITLE => pht('Title'),
self::FIELD_BODY => pht('Body'),
self::FIELD_AUTHOR => pht('Author'),
self::FIELD_ASSIGNEE => pht('Assignee'),
self::FIELD_COMMITTER => pht('Committer'),
self::FIELD_REVIEWER => pht('Reviewer'),
self::FIELD_REVIEWERS => pht('Reviewers'),
self::FIELD_CC => pht('CCs'),
self::FIELD_TAGS => pht('Tags'),
self::FIELD_DIFF_FILE => pht('Any changed filename'),
self::FIELD_DIFF_CONTENT => pht('Any changed file content'),
self::FIELD_DIFF_ADDED_CONTENT => pht('Any added file content'),
self::FIELD_DIFF_REMOVED_CONTENT => pht('Any removed file content'),
self::FIELD_DIFF_ENORMOUS => pht('Change is enormous'),
self::FIELD_REPOSITORY => pht('Repository'),
self::FIELD_REPOSITORY_PROJECTS => pht('Repository\'s projects'),
self::FIELD_RULE => pht('Another Herald rule'),
self::FIELD_AFFECTED_PACKAGE => pht('Any affected package'),
self::FIELD_AFFECTED_PACKAGE_OWNER =>
pht("Any affected package's owner"),
self::FIELD_CONTENT_SOURCE => pht('Content Source'),
self::FIELD_ALWAYS => pht('Always'),
self::FIELD_AUTHOR_PROJECTS => pht("Author's projects"),
self::FIELD_PROJECTS => pht("Projects"),
self::FIELD_PUSHER => pht('Pusher'),
self::FIELD_PUSHER_PROJECTS => pht("Pusher's projects"),
self::FIELD_DIFFERENTIAL_REVISION => pht('Differential revision'),
self::FIELD_DIFFERENTIAL_REVIEWERS => pht('Differential reviewers'),
self::FIELD_DIFFERENTIAL_CCS => pht('Differential CCs'),
self::FIELD_DIFFERENTIAL_ACCEPTED
=> pht('Accepted Differential revision'),
self::FIELD_IS_MERGE_COMMIT => pht('Commit is a merge'),
self::FIELD_BRANCHES => pht('Commit\'s branches'),
self::FIELD_AUTHOR_RAW => pht('Raw author name'),
self::FIELD_COMMITTER_RAW => pht('Raw committer name'),
self::FIELD_IS_NEW_OBJECT => pht('Is newly created?'),
self::FIELD_TASK_PRIORITY => pht('Task priority'),
);
}
/* -( Conditions )--------------------------------------------------------- */
public function getConditionNameMap() {
return array(
self::CONDITION_CONTAINS => pht('contains'),
self::CONDITION_NOT_CONTAINS => pht('does not contain'),
self::CONDITION_IS => pht('is'),
self::CONDITION_IS_NOT => pht('is not'),
self::CONDITION_IS_ANY => pht('is any of'),
self::CONDITION_IS_TRUE => pht('is true'),
self::CONDITION_IS_FALSE => pht('is false'),
self::CONDITION_IS_NOT_ANY => pht('is not any of'),
self::CONDITION_INCLUDE_ALL => pht('include all of'),
self::CONDITION_INCLUDE_ANY => pht('include any of'),
self::CONDITION_INCLUDE_NONE => pht('do not include'),
self::CONDITION_IS_ME => pht('is myself'),
self::CONDITION_IS_NOT_ME => pht('is not myself'),
self::CONDITION_REGEXP => pht('matches regexp'),
self::CONDITION_RULE => pht('matches:'),
self::CONDITION_NOT_RULE => pht('does not match:'),
self::CONDITION_EXISTS => pht('exists'),
self::CONDITION_NOT_EXISTS => pht('does not exist'),
self::CONDITION_UNCONDITIONALLY => '', // don't show anything!
self::CONDITION_REGEXP_PAIR => pht('matches regexp pair'),
self::CONDITION_HAS_BIT => pht('has bit'),
self::CONDITION_NOT_BIT => pht('lacks bit'),
);
}
public function getConditionsForField($field) {
switch ($field) {
case self::FIELD_TITLE:
case self::FIELD_BODY:
case self::FIELD_COMMITTER_RAW:
case self::FIELD_AUTHOR_RAW:
return array(
self::CONDITION_CONTAINS,
self::CONDITION_NOT_CONTAINS,
self::CONDITION_IS,
self::CONDITION_IS_NOT,
self::CONDITION_REGEXP,
);
case self::FIELD_AUTHOR:
case self::FIELD_COMMITTER:
case self::FIELD_REVIEWER:
case self::FIELD_PUSHER:
case self::FIELD_TASK_PRIORITY:
return array(
self::CONDITION_IS_ANY,
self::CONDITION_IS_NOT_ANY,
);
case self::FIELD_REPOSITORY:
case self::FIELD_ASSIGNEE:
return array(
self::CONDITION_IS_ANY,
self::CONDITION_IS_NOT_ANY,
self::CONDITION_EXISTS,
self::CONDITION_NOT_EXISTS,
);
case self::FIELD_TAGS:
case self::FIELD_REVIEWERS:
case self::FIELD_CC:
case self::FIELD_AUTHOR_PROJECTS:
case self::FIELD_PROJECTS:
case self::FIELD_AFFECTED_PACKAGE:
case self::FIELD_AFFECTED_PACKAGE_OWNER:
case self::FIELD_PUSHER_PROJECTS:
case self::FIELD_REPOSITORY_PROJECTS:
return array(
self::CONDITION_INCLUDE_ALL,
self::CONDITION_INCLUDE_ANY,
self::CONDITION_INCLUDE_NONE,
self::CONDITION_EXISTS,
self::CONDITION_NOT_EXISTS,
);
case self::FIELD_DIFF_FILE:
case self::FIELD_BRANCHES:
return array(
self::CONDITION_CONTAINS,
self::CONDITION_REGEXP,
);
case self::FIELD_DIFF_CONTENT:
case self::FIELD_DIFF_ADDED_CONTENT:
case self::FIELD_DIFF_REMOVED_CONTENT:
return array(
self::CONDITION_CONTAINS,
self::CONDITION_REGEXP,
self::CONDITION_REGEXP_PAIR,
);
case self::FIELD_RULE:
return array(
self::CONDITION_RULE,
self::CONDITION_NOT_RULE,
);
case self::FIELD_CONTENT_SOURCE:
return array(
self::CONDITION_IS,
self::CONDITION_IS_NOT,
);
case self::FIELD_ALWAYS:
return array(
self::CONDITION_UNCONDITIONALLY,
);
case self::FIELD_DIFFERENTIAL_REVIEWERS:
return array(
self::CONDITION_EXISTS,
self::CONDITION_NOT_EXISTS,
self::CONDITION_INCLUDE_ALL,
self::CONDITION_INCLUDE_ANY,
self::CONDITION_INCLUDE_NONE,
);
case self::FIELD_DIFFERENTIAL_CCS:
return array(
self::CONDITION_INCLUDE_ALL,
self::CONDITION_INCLUDE_ANY,
self::CONDITION_INCLUDE_NONE,
);
case self::FIELD_DIFFERENTIAL_REVISION:
case self::FIELD_DIFFERENTIAL_ACCEPTED:
return array(
self::CONDITION_EXISTS,
self::CONDITION_NOT_EXISTS,
);
case self::FIELD_IS_MERGE_COMMIT:
case self::FIELD_DIFF_ENORMOUS:
case self::FIELD_IS_NEW_OBJECT:
return array(
self::CONDITION_IS_TRUE,
self::CONDITION_IS_FALSE,
);
default:
throw new Exception(
"This adapter does not define conditions for field '{$field}'!");
}
}
public function doesConditionMatch(
HeraldEngine $engine,
HeraldRule $rule,
HeraldCondition $condition,
$field_value) {
$condition_type = $condition->getFieldCondition();
$condition_value = $condition->getValue();
switch ($condition_type) {
case self::CONDITION_CONTAINS:
// "Contains" can take an array of strings, as in "Any changed
// filename" for diffs.
foreach ((array)$field_value as $value) {
if (stripos($value, $condition_value) !== false) {
return true;
}
}
return false;
case self::CONDITION_NOT_CONTAINS:
return (stripos($field_value, $condition_value) === false);
case self::CONDITION_IS:
return ($field_value == $condition_value);
case self::CONDITION_IS_NOT:
return ($field_value != $condition_value);
case self::CONDITION_IS_ME:
return ($field_value == $rule->getAuthorPHID());
case self::CONDITION_IS_NOT_ME:
return ($field_value != $rule->getAuthorPHID());
case self::CONDITION_IS_ANY:
if (!is_array($condition_value)) {
throw new HeraldInvalidConditionException(
"Expected condition value to be an array.");
}
$condition_value = array_fuse($condition_value);
return isset($condition_value[$field_value]);
case self::CONDITION_IS_NOT_ANY:
if (!is_array($condition_value)) {
throw new HeraldInvalidConditionException(
"Expected condition value to be an array.");
}
$condition_value = array_fuse($condition_value);
return !isset($condition_value[$field_value]);
case self::CONDITION_INCLUDE_ALL:
if (!is_array($field_value)) {
throw new HeraldInvalidConditionException(
"Object produced non-array value!");
}
if (!is_array($condition_value)) {
throw new HeraldInvalidConditionException(
"Expected condition value to be an array.");
}
$have = array_select_keys(array_fuse($field_value), $condition_value);
return (count($have) == count($condition_value));
case self::CONDITION_INCLUDE_ANY:
return (bool)array_select_keys(
array_fuse($field_value),
$condition_value);
case self::CONDITION_INCLUDE_NONE:
return !array_select_keys(
array_fuse($field_value),
$condition_value);
case self::CONDITION_EXISTS:
case self::CONDITION_IS_TRUE:
return (bool)$field_value;
case self::CONDITION_NOT_EXISTS:
case self::CONDITION_IS_FALSE:
return !$field_value;
case self::CONDITION_UNCONDITIONALLY:
return (bool)$field_value;
case self::CONDITION_REGEXP:
foreach ((array)$field_value as $value) {
// We add the 'S' flag because we use the regexp multiple times.
// It shouldn't cause any troubles if the flag is already there
// - /.*/S is evaluated same as /.*/SS.
$result = @preg_match($condition_value . 'S', $value);
if ($result === false) {
throw new HeraldInvalidConditionException(
"Regular expression is not valid!");
}
if ($result) {
return true;
}
}
return false;
case self::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($condition_value, true);
if (!is_array($regexp_pair)) {
throw new HeraldInvalidConditionException(
"Regular expression pair is not valid JSON!");
}
if (count($regexp_pair) != 2) {
throw new HeraldInvalidConditionException(
"Regular expression pair is not a pair!");
}
$key_regexp = array_shift($regexp_pair);
$value_regexp = array_shift($regexp_pair);
foreach ((array)$field_value as $key => $value) {
$key_matches = @preg_match($key_regexp, $key);
if ($key_matches === false) {
throw new HeraldInvalidConditionException(
"First regular expression is invalid!");
}
if ($key_matches) {
$value_matches = @preg_match($value_regexp, $value);
if ($value_matches === false) {
throw new HeraldInvalidConditionException(
"Second regular expression is invalid!");
}
if ($value_matches) {
return true;
}
}
}
return false;
case self::CONDITION_RULE:
case self::CONDITION_NOT_RULE:
$rule = $engine->getRule($condition_value);
if (!$rule) {
throw new HeraldInvalidConditionException(
"Condition references a rule which does not exist!");
}
$is_not = ($condition_type == self::CONDITION_NOT_RULE);
$result = $engine->doesRuleMatch($rule, $this);
if ($is_not) {
$result = !$result;
}
return $result;
case self::CONDITION_HAS_BIT:
return (($condition_value & $field_value) === $condition_value);
case self::CONDITION_NOT_BIT:
return (($condition_value & $field_value) !== $condition_value);
default:
throw new HeraldInvalidConditionException(
"Unknown condition '{$condition_type}'.");
}
}
public function willSaveCondition(HeraldCondition $condition) {
$condition_type = $condition->getFieldCondition();
$condition_value = $condition->getValue();
switch ($condition_type) {
case self::CONDITION_REGEXP:
$ok = @preg_match($condition_value, '');
if ($ok === false) {
throw new HeraldInvalidConditionException(
pht(
'The regular expression "%s" is not valid. Regular expressions '.
'must have enclosing characters (e.g. "@/path/to/file@", not '.
'"/path/to/file") and be syntactically correct.',
$condition_value));
}
break;
case self::CONDITION_REGEXP_PAIR:
$json = json_decode($condition_value, true);
if (!is_array($json)) {
throw new HeraldInvalidConditionException(
pht(
'The regular expression pair "%s" is not valid JSON. Enter a '.
'valid JSON array with two elements.',
$condition_value));
}
if (count($json) != 2) {
throw new HeraldInvalidConditionException(
pht(
'The regular expression pair "%s" must have exactly two '.
'elements.',
$condition_value));
}
$key_regexp = array_shift($json);
$val_regexp = array_shift($json);
$key_ok = @preg_match($key_regexp, '');
if ($key_ok === false) {
throw new HeraldInvalidConditionException(
pht(
'The first regexp in the regexp pair, "%s", is not a valid '.
'regexp.',
$key_regexp));
}
$val_ok = @preg_match($val_regexp, '');
if ($val_ok === false) {
throw new HeraldInvalidConditionException(
pht(
'The second regexp in the regexp pair, "%s", is not a valid '.
'regexp.',
$val_regexp));
}
break;
case self::CONDITION_CONTAINS:
case self::CONDITION_NOT_CONTAINS:
case self::CONDITION_IS:
case self::CONDITION_IS_NOT:
case self::CONDITION_IS_ANY:
case self::CONDITION_IS_NOT_ANY:
case self::CONDITION_INCLUDE_ALL:
case self::CONDITION_INCLUDE_ANY:
case self::CONDITION_INCLUDE_NONE:
case self::CONDITION_IS_ME:
case self::CONDITION_IS_NOT_ME:
case self::CONDITION_RULE:
case self::CONDITION_NOT_RULE:
case self::CONDITION_EXISTS:
case self::CONDITION_NOT_EXISTS:
case self::CONDITION_UNCONDITIONALLY:
case self::CONDITION_HAS_BIT:
case self::CONDITION_NOT_BIT:
case self::CONDITION_IS_TRUE:
case self::CONDITION_IS_FALSE:
// No explicit validation for these types, although there probably
// should be in some cases.
break;
default:
throw new HeraldInvalidConditionException(
pht(
'Unknown condition "%s"!',
$condition_type));
}
}
/* -( Actions )------------------------------------------------------------ */
abstract public function getActions($rule_type);
public function getActionNameMap($rule_type) {
switch ($rule_type) {
case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL:
case HeraldRuleTypeConfig::RULE_TYPE_OBJECT:
return array(
self::ACTION_NOTHING => pht('Do nothing'),
self::ACTION_ADD_CC => pht('Add emails to CC'),
self::ACTION_REMOVE_CC => pht('Remove emails from CC'),
self::ACTION_EMAIL => pht('Send an email to'),
self::ACTION_AUDIT => pht('Trigger an Audit by'),
self::ACTION_FLAG => pht('Mark with flag'),
self::ACTION_ASSIGN_TASK => pht('Assign task to'),
self::ACTION_ADD_PROJECTS => pht('Add projects'),
self::ACTION_ADD_REVIEWERS => pht('Add reviewers'),
self::ACTION_ADD_BLOCKING_REVIEWERS => pht('Add blocking reviewers'),
self::ACTION_APPLY_BUILD_PLANS => pht('Run build plans'),
self::ACTION_BLOCK => pht('Block change with message'),
);
case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
return array(
self::ACTION_NOTHING => pht('Do nothing'),
self::ACTION_ADD_CC => pht('Add me to CC'),
self::ACTION_REMOVE_CC => pht('Remove me from CC'),
self::ACTION_EMAIL => pht('Send me an email'),
self::ACTION_AUDIT => pht('Trigger an Audit by me'),
self::ACTION_FLAG => pht('Mark with flag'),
self::ACTION_ASSIGN_TASK => pht('Assign task to me'),
self::ACTION_ADD_PROJECTS => pht('Add projects'),
self::ACTION_ADD_REVIEWERS => pht('Add me as a reviewer'),
self::ACTION_ADD_BLOCKING_REVIEWERS =>
pht('Add me as a blocking reviewer'),
);
default:
throw new Exception("Unknown rule type '{$rule_type}'!");
}
}
public function willSaveAction(
HeraldRule $rule,
HeraldAction $action) {
$target = $action->getTarget();
if (is_array($target)) {
$target = array_keys($target);
}
$author_phid = $rule->getAuthorPHID();
$rule_type = $rule->getRuleType();
if ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) {
switch ($action->getAction()) {
case self::ACTION_EMAIL:
case self::ACTION_ADD_CC:
case self::ACTION_REMOVE_CC:
case self::ACTION_AUDIT:
case self::ACTION_ASSIGN_TASK:
case self::ACTION_ADD_REVIEWERS:
case self::ACTION_ADD_BLOCKING_REVIEWERS:
// For personal rules, force these actions to target the rule owner.
$target = array($author_phid);
break;
case self::ACTION_FLAG:
// Make sure flag color is valid; set to blue if not.
$color_map = PhabricatorFlagColor::getColorNameMap();
if (empty($color_map[$target])) {
$target = PhabricatorFlagColor::COLOR_BLUE;
}
break;
case self::ACTION_BLOCK:
case self::ACTION_NOTHING:
break;
default:
throw new HeraldInvalidActionException(
pht(
'Unrecognized action type "%s"!',
$action->getAction()));
}
}
$action->setTarget($target);
}
/* -( Values )------------------------------------------------------------- */
public function getValueTypeForFieldAndCondition($field, $condition) {
switch ($condition) {
case self::CONDITION_CONTAINS:
case self::CONDITION_NOT_CONTAINS:
case self::CONDITION_REGEXP:
case self::CONDITION_REGEXP_PAIR:
return self::VALUE_TEXT;
case self::CONDITION_IS:
case self::CONDITION_IS_NOT:
switch ($field) {
case self::FIELD_CONTENT_SOURCE:
return self::VALUE_CONTENT_SOURCE;
default:
return self::VALUE_TEXT;
}
break;
case self::CONDITION_IS_ANY:
case self::CONDITION_IS_NOT_ANY:
switch ($field) {
case self::FIELD_REPOSITORY:
return self::VALUE_REPOSITORY;
case self::FIELD_TASK_PRIORITY:
return self::VALUE_TASK_PRIORITY;
default:
return self::VALUE_USER;
}
break;
case self::CONDITION_INCLUDE_ALL:
case self::CONDITION_INCLUDE_ANY:
case self::CONDITION_INCLUDE_NONE:
switch ($field) {
case self::FIELD_REPOSITORY:
return self::VALUE_REPOSITORY;
case self::FIELD_CC:
return self::VALUE_EMAIL;
case self::FIELD_TAGS:
return self::VALUE_TAG;
case self::FIELD_AFFECTED_PACKAGE:
return self::VALUE_OWNERS_PACKAGE;
case self::FIELD_AUTHOR_PROJECTS:
case self::FIELD_PUSHER_PROJECTS:
case self::FIELD_PROJECTS:
case self::FIELD_REPOSITORY_PROJECTS:
return self::VALUE_PROJECT;
case self::FIELD_REVIEWERS:
return self::VALUE_USER_OR_PROJECT;
default:
return self::VALUE_USER;
}
break;
case self::CONDITION_IS_ME:
case self::CONDITION_IS_NOT_ME:
case self::CONDITION_EXISTS:
case self::CONDITION_NOT_EXISTS:
case self::CONDITION_UNCONDITIONALLY:
case self::CONDITION_IS_TRUE:
case self::CONDITION_IS_FALSE:
return self::VALUE_NONE;
case self::CONDITION_RULE:
case self::CONDITION_NOT_RULE:
return self::VALUE_RULE;
default:
throw new Exception("Unknown condition '{$condition}'.");
}
}
public static function getValueTypeForAction($action, $rule_type) {
$is_personal = ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL);
if ($is_personal) {
switch ($action) {
case self::ACTION_ADD_CC:
case self::ACTION_REMOVE_CC:
case self::ACTION_EMAIL:
case self::ACTION_NOTHING:
case self::ACTION_AUDIT:
case self::ACTION_ASSIGN_TASK:
case self::ACTION_ADD_REVIEWERS:
case self::ACTION_ADD_BLOCKING_REVIEWERS:
return self::VALUE_NONE;
case self::ACTION_FLAG:
return self::VALUE_FLAG_COLOR;
case self::ACTION_ADD_PROJECTS:
return self::VALUE_PROJECT;
default:
throw new Exception("Unknown or invalid action '{$action}'.");
}
} else {
switch ($action) {
case self::ACTION_ADD_CC:
case self::ACTION_REMOVE_CC:
case self::ACTION_EMAIL:
return self::VALUE_EMAIL;
case self::ACTION_NOTHING:
return self::VALUE_NONE;
case self::ACTION_ADD_PROJECTS:
return self::VALUE_PROJECT;
case self::ACTION_FLAG:
return self::VALUE_FLAG_COLOR;
case self::ACTION_ASSIGN_TASK:
return self::VALUE_USER;
case self::ACTION_AUDIT:
case self::ACTION_ADD_REVIEWERS:
case self::ACTION_ADD_BLOCKING_REVIEWERS:
return self::VALUE_USER_OR_PROJECT;
case self::ACTION_APPLY_BUILD_PLANS:
return self::VALUE_BUILD_PLAN;
case self::ACTION_BLOCK:
return self::VALUE_TEXT;
default:
throw new Exception("Unknown or invalid action '{$action}'.");
}
}
}
/* -( Repetition )--------------------------------------------------------- */
public function getRepetitionOptions() {
return array(
HeraldRepetitionPolicyConfig::EVERY,
);
}
public static function applyFlagEffect(HeraldEffect $effect, $phid) {
$color = $effect->getTarget();
// TODO: Silly that we need to load this again here.
$rule = id(new HeraldRule())->load($effect->getRuleID());
$user = id(new PhabricatorUser())->loadOneWhere(
'phid = %s',
$rule->getAuthorPHID());
$flag = PhabricatorFlagQuery::loadUserFlag($user, $phid);
if ($flag) {
return new HeraldApplyTranscript(
$effect,
false,
pht('Object already flagged.'));
}
$handle = id(new PhabricatorHandleQuery())
->setViewer($user)
->withPHIDs(array($phid))
->executeOne();
$flag = new PhabricatorFlag();
$flag->setOwnerPHID($user->getPHID());
$flag->setType($handle->getType());
$flag->setObjectPHID($handle->getPHID());
// TOOD: Should really be transcript PHID, but it doesn't exist yet.
$flag->setReasonPHID($user->getPHID());
$flag->setColor($color);
$flag->setNote(
pht('Flagged by Herald Rule "%s".', $rule->getName()));
$flag->save();
return new HeraldApplyTranscript(
$effect,
true,
pht('Added flag.'));
}
public static function getAllAdapters() {
static $adapters;
if (!$adapters) {
$adapters = id(new PhutilSymbolLoader())
->setAncestorClass(__CLASS__)
->loadObjects();
$adapters = msort($adapters, 'getAdapterSortKey');
}
return $adapters;
}
public static function getAdapterForContentType($content_type) {
$adapters = self::getAllAdapters();
foreach ($adapters as $adapter) {
if ($adapter->getAdapterContentType() == $content_type) {
return $adapter;
}
}
throw new Exception(
pht(
'No adapter exists for Herald content type "%s".',
$content_type));
}
public static function getEnabledAdapterMap(PhabricatorUser $viewer) {
$map = array();
$adapters = HeraldAdapter::getAllAdapters();
foreach ($adapters as $adapter) {
if (!$adapter->isAvailableToUser($viewer)) {
continue;
}
$type = $adapter->getAdapterContentType();
$name = $adapter->getAdapterContentName();
$map[$type] = $name;
}
return $map;
}
public function renderRuleAsText(HeraldRule $rule, array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$out = array();
if ($rule->getMustMatchAll()) {
$out[] = pht('When all of these conditions are met:');
} else {
$out[] = pht('When any of these conditions are met:');
}
$out[] = null;
foreach ($rule->getConditions() as $condition) {
$out[] = $this->renderConditionAsText($condition, $handles);
}
$out[] = null;
$integer_code_for_every = HeraldRepetitionPolicyConfig::toInt(
HeraldRepetitionPolicyConfig::EVERY);
if ($rule->getRepetitionPolicy() == $integer_code_for_every) {
$out[] = pht('Take these actions every time this rule matches:');
} else {
$out[] = pht('Take these actions the first time this rule matches:');
}
$out[] = null;
foreach ($rule->getActions() as $action) {
$out[] = $this->renderActionAsText($action, $handles);
}
return phutil_implode_html("\n", $out);
}
private function renderConditionAsText(
HeraldCondition $condition,
array $handles) {
$field_type = $condition->getFieldName();
$field_name = idx($this->getFieldNameMap(), $field_type);
$condition_type = $condition->getFieldCondition();
$condition_name = idx($this->getConditionNameMap(), $condition_type);
$value = $this->renderConditionValueAsText($condition, $handles);
return hsprintf(' %s %s %s', $field_name, $condition_name, $value);
}
private function renderActionAsText(
HeraldAction $action,
array $handles) {
$rule_global = HeraldRuleTypeConfig::RULE_TYPE_GLOBAL;
$action_type = $action->getAction();
$action_name = idx($this->getActionNameMap($rule_global), $action_type);
$target = $this->renderActionTargetAsText($action, $handles);
return hsprintf(' %s %s', $action_name, $target);
}
private function renderConditionValueAsText(
HeraldCondition $condition,
array $handles) {
$value = $condition->getValue();
if (!is_array($value)) {
$value = array($value);
}
switch ($condition->getFieldName()) {
case self::FIELD_TASK_PRIORITY:
$priority_map = ManiphestTaskPriority::getTaskPriorityMap();
foreach ($value as $index => $val) {
$name = idx($priority_map, $val);
if ($name) {
$value[$index] = $name;
}
}
break;
default:
foreach ($value as $index => $val) {
$handle = idx($handles, $val);
if ($handle) {
$value[$index] = $handle->renderLink();
}
}
break;
}
$value = phutil_implode_html(', ', $value);
return $value;
}
private function renderActionTargetAsText(
HeraldAction $action,
array $handles) {
$target = $action->getTarget();
if (!is_array($target)) {
$target = array($target);
}
foreach ($target as $index => $val) {
switch ($action->getAction()) {
case self::ACTION_FLAG:
$target[$index] = PhabricatorFlagColor::getColorName($val);
break;
default:
$handle = idx($handles, $val);
if ($handle) {
$target[$index] = $handle->renderLink();
}
break;
}
}
$target = phutil_implode_html(', ', $target);
return $target;
}
/**
* Given a @{class:HeraldRule}, this function extracts all the phids that
* we'll want to load as handles later.
*
* This function performs a somewhat hacky approach to figuring out what
* is and is not a phid - try to get the phid type and if the type is
* *not* unknown assume its a valid phid.
*
* Don't try this at home. Use more strongly typed data at home.
*
* Think of the children.
*/
public static function getHandlePHIDs(HeraldRule $rule) {
$phids = array($rule->getAuthorPHID());
foreach ($rule->getConditions() as $condition) {
$value = $condition->getValue();
if (!is_array($value)) {
$value = array($value);
}
foreach ($value as $val) {
if (phid_get_type($val) !=
PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) {
$phids[] = $val;
}
}
}
foreach ($rule->getActions() as $action) {
$target = $action->getTarget();
if (!is_array($target)) {
$target = array($target);
}
foreach ($target as $val) {
if (phid_get_type($val) !=
PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) {
$phids[] = $val;
}
}
}
if ($rule->isObjectRule()) {
$phids[] = $rule->getTriggerObjectPHID();
}
return $phids;
}
}
-
diff --git a/src/applications/herald/engine/HeraldEffect.php b/src/applications/herald/engine/HeraldEffect.php
index 1d4955c7ed..9863ae3f39 100644
--- a/src/applications/herald/engine/HeraldEffect.php
+++ b/src/applications/herald/engine/HeraldEffect.php
@@ -1,79 +1,78 @@
<?php
final class HeraldEffect {
private $objectPHID;
private $action;
private $target;
private $ruleID;
private $rulePHID;
private $effector;
private $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 setRulePHID($rule_phid) {
$this->rulePHID = $rule_phid;
return $this;
}
public function getRulePHID() {
return $this->rulePHID;
}
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/storage/HeraldRuleTransactionComment.php b/src/applications/herald/storage/HeraldRuleTransactionComment.php
index 5db8952403..56022ef863 100644
--- a/src/applications/herald/storage/HeraldRuleTransactionComment.php
+++ b/src/applications/herald/storage/HeraldRuleTransactionComment.php
@@ -1,11 +1,10 @@
<?php
final class HeraldRuleTransactionComment
extends PhabricatorApplicationTransactionComment {
public function getApplicationTransactionObject() {
return new HeraldRuleTransaction();
}
}
-
diff --git a/src/applications/herald/storage/__tests__/HeraldTranscriptTestCase.php b/src/applications/herald/storage/__tests__/HeraldTranscriptTestCase.php
index 799c8b9e34..1dcba15d01 100644
--- a/src/applications/herald/storage/__tests__/HeraldTranscriptTestCase.php
+++ b/src/applications/herald/storage/__tests__/HeraldTranscriptTestCase.php
@@ -1,47 +1,47 @@
<?php
final class HeraldTranscriptTestCase extends PhabricatorTestCase {
public function testTranscriptTruncation() {
$long_string = str_repeat('x', 1024 * 1024);
$short_string = str_repeat('x', 4096)."\n<...>";
$long_array = array(
'a' => $long_string,
'b' => $long_string,
);
$mixed_array = array(
'a' => 'abc',
'b' => 'def',
'c' => $long_string,
);
$fields = array(
'ls' => $long_string,
'la' => $long_array,
'ma' => $mixed_array,
);
$truncated_fields = id(new HeraldObjectTranscript())
->setFields($fields)
->getFields();
$this->assertEqual($short_string, $truncated_fields['ls']);
$this->assertEqual(
array('a', '<...>'),
array_keys($truncated_fields['la']));
$this->assertEqual(
$short_string.'!<...>',
implode('!', $truncated_fields['la']));
$this->assertEqual(
array('a', 'b', 'c'),
array_keys($truncated_fields['ma']));
$this->assertEqual(
'abc!def!'.substr($short_string, 6),
implode('!', $truncated_fields['ma']));
}
-}
\ No newline at end of file
+}
diff --git a/src/applications/macro/storage/PhabricatorFileImageMacro.php b/src/applications/macro/storage/PhabricatorFileImageMacro.php
index b8cdbf6a8f..578e0c3513 100644
--- a/src/applications/macro/storage/PhabricatorFileImageMacro.php
+++ b/src/applications/macro/storage/PhabricatorFileImageMacro.php
@@ -1,113 +1,112 @@
<?php
final class PhabricatorFileImageMacro extends PhabricatorFileDAO
implements
PhabricatorSubscribableInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorFlaggableInterface,
PhabricatorPolicyInterface {
protected $authorPHID;
protected $filePHID;
protected $name;
protected $isDisabled = 0;
protected $audioPHID;
protected $audioBehavior = self::AUDIO_BEHAVIOR_NONE;
protected $mailKey;
private $file = self::ATTACHABLE;
private $audio = self::ATTACHABLE;
const AUDIO_BEHAVIOR_NONE = 'audio:none';
const AUDIO_BEHAVIOR_ONCE = 'audio:once';
const AUDIO_BEHAVIOR_LOOP = 'audio:loop';
public function attachFile(PhabricatorFile $file) {
$this->file = $file;
return $this;
}
public function getFile() {
return $this->assertAttached($this->file);
}
public function attachAudio(PhabricatorFile $audio = null) {
$this->audio = $audio;
return $this;
}
public function getAudio() {
return $this->assertAttached($this->audio);
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorMacroPHIDTypeMacro::TYPECONST);
}
public function save() {
if (!$this->getMailKey()) {
$this->setMailKey(Filesystem::readRandomCharacters(20));
}
return parent::save();
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhabricatorMacroEditor();
}
public function getApplicationTransactionObject() {
return new PhabricatorMacroTransaction();
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */
public function isAutomaticallySubscribed($phid) {
return false;
}
public function shouldShowSubscribersProperty() {
return true;
}
public function shouldAllowSubscription($phid) {
return true;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::getMostOpenPolicy();
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}
-
diff --git a/src/applications/macro/storage/PhabricatorMacroTransaction.php b/src/applications/macro/storage/PhabricatorMacroTransaction.php
index bcac44ae19..c29cb92265 100644
--- a/src/applications/macro/storage/PhabricatorMacroTransaction.php
+++ b/src/applications/macro/storage/PhabricatorMacroTransaction.php
@@ -1,304 +1,303 @@
<?php
final class PhabricatorMacroTransaction
extends PhabricatorApplicationTransaction {
public function getApplicationName() {
return 'file';
}
public function getTableName() {
return 'macro_transaction';
}
public function getApplicationTransactionType() {
return PhabricatorMacroPHIDTypeMacro::TYPECONST;
}
public function getApplicationTransactionCommentObject() {
return new PhabricatorMacroTransactionComment();
}
public function getRequiredHandlePHIDs() {
$phids = parent::getRequiredHandlePHIDs();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case PhabricatorMacroTransactionType::TYPE_FILE:
case PhabricatorMacroTransactionType::TYPE_AUDIO:
if ($old !== null) {
$phids[] = $old;
}
$phids[] = $new;
break;
}
return $phids;
}
public function shouldHide() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case PhabricatorMacroTransactionType::TYPE_NAME:
return ($old === null);
}
return parent::shouldHide();
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case PhabricatorMacroTransactionType::TYPE_NAME:
return pht(
'%s renamed this macro from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$old,
$new);
break;
case PhabricatorMacroTransactionType::TYPE_DISABLED:
if ($new) {
return pht(
'%s disabled this macro.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s restored this macro.',
$this->renderHandleLink($author_phid));
}
break;
case PhabricatorMacroTransactionType::TYPE_AUDIO:
if (!$old) {
return pht(
'%s attached audio: %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($new));
} else {
return pht(
'%s changed the audio for this macro from %s to %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($old),
$this->renderHandleLink($new));
}
case PhabricatorMacroTransactionType::TYPE_AUDIO_BEHAVIOR:
switch ($new) {
case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_ONCE:
return pht(
'%s set the audio to play once.',
$this->renderHandleLink($author_phid));
case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_LOOP:
return pht(
'%s set the audio to loop.',
$this->renderHandleLink($author_phid));
default:
return pht(
'%s disabled the audio for this macro.',
$this->renderHandleLink($author_phid));
}
case PhabricatorMacroTransactionType::TYPE_FILE:
if ($old === null) {
return pht(
'%s created this macro.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s changed the image for this macro from %s to %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($old),
$this->renderHandleLink($new));
}
break;
}
return parent::getTitle();
}
public function getTitleForFeed(PhabricatorFeedStory $story) {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case PhabricatorMacroTransactionType::TYPE_NAME:
return pht(
'%s renamed %s from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$old,
$new);
case PhabricatorMacroTransactionType::TYPE_DISABLED:
if ($new) {
return pht(
'%s disabled %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
} else {
return pht(
'%s restored %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
case PhabricatorMacroTransactionType::TYPE_FILE:
if ($old === null) {
return pht(
'%s created %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
} else {
return pht(
'%s updated the image for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
case PhabricatorMacroTransactionType::TYPE_AUDIO:
if (!$old) {
return pht(
'%s attached audio to %s: %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$this->renderHandleLink($new));
} else {
return pht(
'%s changed the audio for %s from %s to %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$this->renderHandleLink($old),
$this->renderHandleLink($new));
}
case PhabricatorMacroTransactionType::TYPE_AUDIO_BEHAVIOR:
switch ($new) {
case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_ONCE:
return pht(
'%s set the audio for %s to play once.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_LOOP:
return pht(
'%s set the audio for %s to loop.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
default:
return pht(
'%s disabled the audio for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
}
return parent::getTitleForFeed($story);
}
public function getActionName() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case PhabricatorMacroTransactionType::TYPE_NAME:
if ($old === null) {
return pht('Created');
} else {
return pht('Renamed');
}
case PhabricatorMacroTransactionType::TYPE_DISABLED:
if ($new) {
return pht('Disabled');
} else {
return pht('Restored');
}
case PhabricatorMacroTransactionType::TYPE_FILE:
if ($old === null) {
return pht('Created');
} else {
return pht('Edited Image');
}
case PhabricatorMacroTransactionType::TYPE_AUDIO:
return pht('Audio');
case PhabricatorMacroTransactionType::TYPE_AUDIO_BEHAVIOR:
return pht('Audio Behavior');
}
return parent::getActionName();
}
public function getActionStrength() {
switch ($this->getTransactionType()) {
case PhabricatorMacroTransactionType::TYPE_DISABLED:
return 2.0;
case PhabricatorMacroTransactionType::TYPE_FILE:
return 1.5;
}
return parent::getActionStrength();
}
public function getIcon() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case PhabricatorMacroTransactionType::TYPE_NAME:
return 'edit';
case PhabricatorMacroTransactionType::TYPE_FILE:
if ($old === null) {
return 'create';
} else {
return 'edit';
}
case PhabricatorMacroTransactionType::TYPE_DISABLED:
if ($new) {
return 'delete';
} else {
return 'undo';
}
case PhabricatorMacroTransactionType::TYPE_AUDIO:
return 'herald';
}
return parent::getIcon();
}
public function getColor() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case PhabricatorMacroTransactionType::TYPE_NAME:
return PhabricatorTransactions::COLOR_BLUE;
case PhabricatorMacroTransactionType::TYPE_FILE:
if ($old === null) {
return PhabricatorTransactions::COLOR_GREEN;
} else {
return PhabricatorTransactions::COLOR_BLUE;
}
case PhabricatorMacroTransactionType::TYPE_DISABLED:
if ($new) {
return PhabricatorTransactions::COLOR_BLACK;
} else {
return PhabricatorTransactions::COLOR_SKY;
}
}
return parent::getColor();
}
}
-
diff --git a/src/applications/macro/storage/PhabricatorMacroTransactionComment.php b/src/applications/macro/storage/PhabricatorMacroTransactionComment.php
index 565f5040ee..1963a554a0 100644
--- a/src/applications/macro/storage/PhabricatorMacroTransactionComment.php
+++ b/src/applications/macro/storage/PhabricatorMacroTransactionComment.php
@@ -1,11 +1,10 @@
<?php
final class PhabricatorMacroTransactionComment
extends PhabricatorApplicationTransactionComment {
public function getApplicationTransactionObject() {
return new PhabricatorMacroTransaction();
}
}
-
diff --git a/src/applications/maniphest/application/PhabricatorApplicationManiphest.php b/src/applications/maniphest/application/PhabricatorApplicationManiphest.php
index c0b3eaad66..4cf58c76c3 100644
--- a/src/applications/maniphest/application/PhabricatorApplicationManiphest.php
+++ b/src/applications/maniphest/application/PhabricatorApplicationManiphest.php
@@ -1,127 +1,126 @@
<?php
final class PhabricatorApplicationManiphest extends PhabricatorApplication {
public function getShortDescription() {
return 'Tasks and Bugs';
}
public function getBaseURI() {
return '/maniphest/';
}
public function getIconName() {
return 'maniphest';
}
public function getApplicationGroup() {
return self::GROUP_CORE;
}
public function getApplicationOrder() {
return 0.110;
}
public function getFactObjectsForAnalysis() {
return array(
new ManiphestTask(),
);
}
public function getEventListeners() {
return array(
new ManiphestNameIndexEventListener(),
new ManiphestActionMenuEventListener(),
new ManiphestHovercardEventListener(),
);
}
public function getRemarkupRules() {
return array(
new ManiphestRemarkupRule(),
);
}
public function getRoutes() {
return array(
'/T(?P<id>[1-9]\d*)' => 'ManiphestTaskDetailController',
'/maniphest/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'ManiphestTaskListController',
'report/(?:(?P<view>\w+)/)?' => 'ManiphestReportController',
'batch/' => 'ManiphestBatchEditController',
'task/' => array(
'create/' => 'ManiphestTaskEditController',
'edit/(?P<id>[1-9]\d*)/' => 'ManiphestTaskEditController',
'descriptionpreview/' =>
'PhabricatorMarkupPreviewController',
),
'transaction/' => array(
'save/' => 'ManiphestTransactionSaveController',
'preview/(?P<id>[1-9]\d*)/'
=> 'ManiphestTransactionPreviewController',
),
'export/(?P<key>[^/]+)/' => 'ManiphestExportController',
'subpriority/' => 'ManiphestSubpriorityController',
'subscribe/(?P<action>add|rem)/(?P<id>[1-9]\d*)/'
=> 'ManiphestSubscribeController',
),
);
}
public function loadStatus(PhabricatorUser $user) {
$status = array();
$query = id(new ManiphestTaskQuery())
->setViewer($user)
->withStatuses(ManiphestTaskStatus::getOpenStatusConstants())
->withOwners(array($user->getPHID()));
$count = count($query->execute());
$type = PhabricatorApplicationStatusView::TYPE_WARNING;
$status[] = id(new PhabricatorApplicationStatusView())
->setType($type)
->setText(pht('%s Assigned Task(s)', new PhutilNumber($count)))
->setCount($count);
return $status;
}
public function getQuickCreateItems(PhabricatorUser $viewer) {
$items = array();
$item = id(new PHUIListItemView())
->setName(pht('Maniphest Task'))
->setAppIcon('maniphest-dark')
->setHref($this->getBaseURI().'task/create/');
$items[] = $item;
return $items;
}
protected function getCustomCapabilities() {
return array(
ManiphestCapabilityDefaultView::CAPABILITY => array(
'caption' => pht(
'Default view policy for newly created tasks.'),
),
ManiphestCapabilityDefaultEdit::CAPABILITY => array(
'caption' => pht(
'Default edit policy for newly created tasks.'),
),
ManiphestCapabilityEditStatus::CAPABILITY => array(
),
ManiphestCapabilityEditAssign::CAPABILITY => array(
),
ManiphestCapabilityEditPolicies::CAPABILITY => array(
),
ManiphestCapabilityEditPriority::CAPABILITY => array(
),
ManiphestCapabilityEditProjects::CAPABILITY => array(
),
ManiphestCapabilityBulkEdit::CAPABILITY => array(
),
);
}
}
-
diff --git a/src/applications/maniphest/storage/ManiphestCustomFieldNumericIndex.php b/src/applications/maniphest/storage/ManiphestCustomFieldNumericIndex.php
index 640552c63c..79d7a3a8e6 100644
--- a/src/applications/maniphest/storage/ManiphestCustomFieldNumericIndex.php
+++ b/src/applications/maniphest/storage/ManiphestCustomFieldNumericIndex.php
@@ -1,11 +1,10 @@
<?php
final class ManiphestCustomFieldNumericIndex
extends PhabricatorCustomFieldNumericIndexStorage {
public function getApplicationName() {
return 'maniphest';
}
}
-
diff --git a/src/applications/maniphest/storage/ManiphestCustomFieldStorage.php b/src/applications/maniphest/storage/ManiphestCustomFieldStorage.php
index f3b2596d11..d40ac89dc0 100644
--- a/src/applications/maniphest/storage/ManiphestCustomFieldStorage.php
+++ b/src/applications/maniphest/storage/ManiphestCustomFieldStorage.php
@@ -1,11 +1,10 @@
<?php
final class ManiphestCustomFieldStorage
extends PhabricatorCustomFieldStorage {
public function getApplicationName() {
return 'maniphest';
}
}
-
diff --git a/src/applications/maniphest/storage/ManiphestCustomFieldStringIndex.php b/src/applications/maniphest/storage/ManiphestCustomFieldStringIndex.php
index 9cb678ccec..5126a7bf50 100644
--- a/src/applications/maniphest/storage/ManiphestCustomFieldStringIndex.php
+++ b/src/applications/maniphest/storage/ManiphestCustomFieldStringIndex.php
@@ -1,11 +1,10 @@
<?php
final class ManiphestCustomFieldStringIndex
extends PhabricatorCustomFieldStringIndexStorage {
public function getApplicationName() {
return 'maniphest';
}
}
-
diff --git a/src/applications/maniphest/storage/ManiphestTransaction.php b/src/applications/maniphest/storage/ManiphestTransaction.php
index 09a3f936e5..a66b8500fe 100644
--- a/src/applications/maniphest/storage/ManiphestTransaction.php
+++ b/src/applications/maniphest/storage/ManiphestTransaction.php
@@ -1,670 +1,669 @@
<?php
final class ManiphestTransaction
extends PhabricatorApplicationTransaction {
const TYPE_TITLE = 'title';
const TYPE_STATUS = 'status';
const TYPE_DESCRIPTION = 'description';
const TYPE_OWNER = 'reassign';
const TYPE_CCS = 'ccs';
const TYPE_PROJECTS = 'projects';
const TYPE_PRIORITY = 'priority';
const TYPE_EDGE = 'edge';
const TYPE_ATTACH = 'attach';
public function getApplicationName() {
return 'maniphest';
}
public function getApplicationTransactionType() {
return ManiphestPHIDTypeTask::TYPECONST;
}
public function getApplicationTransactionCommentObject() {
return new ManiphestTransactionComment();
}
public function getRequiredHandlePHIDs() {
$phids = parent::getRequiredHandlePHIDs();
$new = $this->getNewValue();
$old = $this->getOldValue();
switch ($this->getTransactionType()) {
case self::TYPE_OWNER:
if ($new) {
$phids[] = $new;
}
if ($old) {
$phids[] = $old;
}
break;
case self::TYPE_CCS:
case self::TYPE_PROJECTS:
$phids = array_mergev(
array(
$phids,
nonempty($old, array()),
nonempty($new, array()),
));
break;
case self::TYPE_EDGE:
$phids = array_mergev(
array(
$phids,
array_keys(nonempty($old, array())),
array_keys(nonempty($new, array())),
));
break;
case self::TYPE_ATTACH:
$old = nonempty($old, array());
$new = nonempty($new, array());
$phids = array_mergev(
array(
$phids,
array_keys(idx($new, 'FILE', array())),
array_keys(idx($old, 'FILE', array())),
));
break;
}
return $phids;
}
public function shouldHide() {
switch ($this->getTransactionType()) {
case self::TYPE_TITLE:
case self::TYPE_DESCRIPTION:
case self::TYPE_PRIORITY:
if ($this->getOldValue() === null) {
return true;
} else {
return false;
}
break;
}
return false;
}
public function getActionStrength() {
switch ($this->getTransactionType()) {
case self::TYPE_STATUS:
return 1.3;
case self::TYPE_OWNER:
return 1.2;
case self::TYPE_PRIORITY:
return 1.1;
}
return parent::getActionStrength();
}
public function getColor() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_OWNER:
if ($this->getAuthorPHID() == $new) {
return 'green';
} else if (!$new) {
return 'black';
} else if (!$old) {
return 'green';
} else {
return 'green';
}
case self::TYPE_STATUS:
if ($new == ManiphestTaskStatus::STATUS_OPEN) {
return 'green';
} else {
return 'black';
}
case self::TYPE_PRIORITY:
if ($old == ManiphestTaskPriority::getDefaultPriority()) {
return 'green';
} else if ($old > $new) {
return 'grey';
} else {
return 'yellow';
}
}
return parent::getColor();
}
public function getActionName() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_TITLE:
return pht('Retitled');
case self::TYPE_STATUS:
switch ($new) {
case ManiphestTaskStatus::STATUS_OPEN:
if ($old === null) {
return pht('Created');
} else {
return pht('Reopened');
}
case ManiphestTaskStatus::STATUS_CLOSED_SPITE:
return pht('Spited');
case ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE:
return pht('Merged');
default:
return pht('Closed');
}
case self::TYPE_DESCRIPTION:
return pht('Edited');
case self::TYPE_OWNER:
if ($this->getAuthorPHID() == $new) {
return pht('Claimed');
} else if (!$new) {
return pht('Up For Grabs');
} else if (!$old) {
return pht('Assigned');
} else {
return pht('Reassigned');
}
case self::TYPE_CCS:
return pht('Changed CC');
case self::TYPE_PROJECTS:
return pht('Changed Projects');
case self::TYPE_PRIORITY:
if ($old == ManiphestTaskPriority::getDefaultPriority()) {
return pht('Triaged');
} else if ($old > $new) {
return pht('Lowered Priority');
} else {
return pht('Raised Priority');
}
case self::TYPE_EDGE:
case self::TYPE_ATTACH:
return pht('Attached');
}
return parent::getActionName();
}
public function getIcon() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_OWNER:
return 'user';
case self::TYPE_CCS:
return 'meta-mta';
case self::TYPE_TITLE:
return 'edit';
case self::TYPE_STATUS:
switch ($new) {
case ManiphestTaskStatus::STATUS_OPEN:
return 'create';
case ManiphestTaskStatus::STATUS_CLOSED_SPITE:
return 'dislike';
case ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE:
return 'delete';
default:
return 'check';
}
case self::TYPE_DESCRIPTION:
return 'edit';
case self::TYPE_PROJECTS:
return 'project';
case self::TYPE_PRIORITY:
if ($old == ManiphestTaskPriority::getDefaultPriority()) {
return 'normal-priority';
return pht('Triaged');
} else if ($old > $new) {
return 'lower-priority';
} else {
return 'raise-priority';
}
case self::TYPE_EDGE:
case self::TYPE_ATTACH:
return 'attach';
}
return parent::getIcon();
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_TITLE:
return pht(
'%s changed the title from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$old,
$new);
case self::TYPE_DESCRIPTION:
return pht(
'%s edited the task description.',
$this->renderHandleLink($author_phid));
case self::TYPE_STATUS:
switch ($new) {
case ManiphestTaskStatus::STATUS_OPEN:
if ($old === null) {
return pht(
'%s created this task.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s reopened this task.',
$this->renderHandleLink($author_phid));
}
case ManiphestTaskStatus::STATUS_CLOSED_SPITE:
return pht(
'%s closed this task out of spite.',
$this->renderHandleLink($author_phid));
case ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE:
return pht(
'%s closed this task as a duplicate.',
$this->renderHandleLink($author_phid));
default:
$status_name = idx(
ManiphestTaskStatus::getTaskStatusMap(),
$new,
'???');
return pht(
'%s closed this task as "%s".',
$this->renderHandleLink($author_phid),
$status_name);
}
case self::TYPE_OWNER:
if ($author_phid == $new) {
return pht(
'%s claimed this task.',
$this->renderHandleLink($author_phid));
} else if (!$new) {
return pht(
'%s placed this task up for grabs.',
$this->renderHandleLink($author_phid));
} else if (!$old) {
return pht(
'%s assigned this task to %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($new));
} else {
return pht(
'%s reassigned this task from %s to %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($old),
$this->renderHandleLink($new));
}
case self::TYPE_PROJECTS:
$added = array_diff($new, $old);
$removed = array_diff($old, $new);
if ($added && !$removed) {
return pht(
'%s added %d project(s): %s',
$this->renderHandleLink($author_phid),
count($added),
$this->renderHandleList($added));
} else if ($removed && !$added) {
return pht(
'%s removed %d project(s): %s',
$this->renderHandleLink($author_phid),
count($removed),
$this->renderHandleList($removed));
} else if ($removed && $added) {
return pht(
'%s changed project(s), added %d: %s; removed %d: %s',
$this->renderHandleLink($author_phid),
count($added),
$this->renderHandleList($added),
count($removed),
$this->renderHandleList($removed));
} else {
// This is hit when rendering previews.
return pht(
'%s changed projects...',
$this->renderHandleLink($author_phid));
}
case self::TYPE_PRIORITY:
$old_name = ManiphestTaskPriority::getTaskPriorityName($old);
$new_name = ManiphestTaskPriority::getTaskPriorityName($new);
if ($old == ManiphestTaskPriority::getDefaultPriority()) {
return pht(
'%s triaged this task as "%s" priority.',
$this->renderHandleLink($author_phid),
$new_name);
} else if ($old > $new) {
return pht(
'%s lowered the priority of this task from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$old_name,
$new_name);
} else {
return pht(
'%s raised the priority of this task from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$old_name,
$new_name);
}
case self::TYPE_CCS:
// TODO: Remove this when we switch to subscribers. Just reuse the
// code in the parent.
$clone = clone $this;
$clone->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS);
return $clone->getTitle();
case self::TYPE_EDGE:
// TODO: Remove this when we switch to real edges. Just reuse the
// code in the parent;
$clone = clone $this;
$clone->setTransactionType(PhabricatorTransactions::TYPE_EDGE);
return $clone->getTitle();
case self::TYPE_ATTACH:
$old = nonempty($old, array());
$new = nonempty($new, array());
$new = array_keys(idx($new, 'FILE', array()));
$old = array_keys(idx($old, 'FILE', array()));
$added = array_diff($new, $old);
$removed = array_diff($old, $new);
if ($added && !$removed) {
return pht(
'%s attached %d file(s): %s',
$this->renderHandleLink($author_phid),
count($added),
$this->renderHandleList($added));
} else if ($removed && !$added) {
return pht(
'%s detached %d file(s): %s',
$this->renderHandleLink($author_phid),
count($removed),
$this->renderHandleList($removed));
} else {
return pht(
'%s changed file(s), attached %d: %s; detached %d: %s',
$this->renderHandleLink($author_phid),
count($added),
$this->renderHandleList($added),
count($removed),
$this->renderHandleList($removed));
}
}
return parent::getTitle();
}
public function getTitleForFeed(PhabricatorFeedStory $story) {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_TITLE:
return pht(
'%s renamed %s from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$old,
$new);
case self::TYPE_DESCRIPTION:
return pht(
'%s edited the description of %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case self::TYPE_STATUS:
switch ($new) {
case ManiphestTaskStatus::STATUS_OPEN:
if ($old === null) {
return pht(
'%s created %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
} else {
return pht(
'%s reopened %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
case ManiphestTaskStatus::STATUS_CLOSED_SPITE:
return pht(
'%s closed %s out of spite.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE:
return pht(
'%s closed %s as a duplicate.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
default:
$status_name = idx(
ManiphestTaskStatus::getTaskStatusMap(),
$new,
'???');
return pht(
'%s closed %s as "%s".',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$status_name);
}
case self::TYPE_OWNER:
if ($author_phid == $new) {
return pht(
'%s claimed %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
} else if (!$new) {
return pht(
'%s placed %s up for grabs.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
} else if (!$old) {
return pht(
'%s assigned %s to %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$this->renderHandleLink($new));
} else {
return pht(
'%s reassigned %s from %s to %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$this->renderHandleLink($old),
$this->renderHandleLink($new));
}
case self::TYPE_PROJECTS:
$added = array_diff($new, $old);
$removed = array_diff($old, $new);
if ($added && !$removed) {
return pht(
'%s added %d project(s) to %s: %s',
$this->renderHandleLink($author_phid),
count($added),
$this->renderHandleLink($object_phid),
$this->renderHandleList($added));
} else if ($removed && !$added) {
return pht(
'%s removed %d project(s) from %s: %s',
$this->renderHandleLink($author_phid),
count($removed),
$this->renderHandleLink($object_phid),
$this->renderHandleList($removed));
} else if ($removed && $added) {
return pht(
'%s changed project(s) of %s, added %d: %s; removed %d: %s',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
count($added),
$this->renderHandleList($added),
count($removed),
$this->renderHandleList($removed));
}
case self::TYPE_PRIORITY:
$old_name = ManiphestTaskPriority::getTaskPriorityName($old);
$new_name = ManiphestTaskPriority::getTaskPriorityName($new);
if ($old == ManiphestTaskPriority::getDefaultPriority()) {
return pht(
'%s triaged %s as "%s" priority.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$new_name);
} else if ($old > $new) {
return pht(
'%s lowered the priority of %s from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$old_name,
$new_name);
} else {
return pht(
'%s raised the priority of %s from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$old_name,
$new_name);
}
case self::TYPE_CCS:
// TODO: Remove this when we switch to subscribers. Just reuse the
// code in the parent.
$clone = clone $this;
$clone->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS);
return $clone->getTitleForFeed($story);
case self::TYPE_EDGE:
// TODO: Remove this when we switch to real edges. Just reuse the
// code in the parent;
$clone = clone $this;
$clone->setTransactionType(PhabricatorTransactions::TYPE_EDGE);
return $clone->getTitleForFeed($story);
case self::TYPE_ATTACH:
$old = nonempty($old, array());
$new = nonempty($new, array());
$new = array_keys(idx($new, 'FILE', array()));
$old = array_keys(idx($old, 'FILE', array()));
$added = array_diff($new, $old);
$removed = array_diff($old, $new);
if ($added && !$removed) {
return pht(
'%s attached %d file(s) of %s: %s',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
count($added),
$this->renderHandleList($added));
} else if ($removed && !$added) {
return pht(
'%s detached %d file(s) of %s: %s',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
count($removed),
$this->renderHandleList($removed));
} else {
return pht(
'%s changed file(s) for %s, attached %d: %s; detached %d: %s',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
count($added),
$this->renderHandleList($added),
count($removed),
$this->renderHandleList($removed));
}
}
return parent::getTitleForFeed($story);
}
public function hasChangeDetails() {
switch ($this->getTransactionType()) {
case self::TYPE_DESCRIPTION:
return true;
}
return parent::hasChangeDetails();
}
public function renderChangeDetails(PhabricatorUser $viewer) {
return $this->renderTextCorpusChangeDetails(
$viewer,
$this->getOldValue(),
$this->getNewValue());
}
public function getMailTags() {
$tags = array();
switch ($this->getTransactionType()) {
case self::TYPE_STATUS:
$tags[] = MetaMTANotificationType::TYPE_MANIPHEST_STATUS;
break;
case self::TYPE_OWNER:
$tags[] = MetaMTANotificationType::TYPE_MANIPHEST_OWNER;
break;
case self::TYPE_CCS:
$tags[] = MetaMTANotificationType::TYPE_MANIPHEST_CC;
break;
case self::TYPE_PROJECTS:
$tags[] = MetaMTANotificationType::TYPE_MANIPHEST_PROJECTS;
break;
case self::TYPE_PRIORITY:
$tags[] = MetaMTANotificationType::TYPE_MANIPHEST_PRIORITY;
break;
case PhabricatorTransactions::TYPE_COMMENT:
$tags[] = MetaMTANotificationType::TYPE_MANIPHEST_COMMENT;
break;
default:
$tags[] = MetaMTANotificationType::TYPE_MANIPHEST_OTHER;
break;
}
return $tags;
}
}
-
diff --git a/src/applications/meta/application/PhabricatorApplicationApplications.php b/src/applications/meta/application/PhabricatorApplicationApplications.php
index 765e5e0b0d..66c611406c 100644
--- a/src/applications/meta/application/PhabricatorApplicationApplications.php
+++ b/src/applications/meta/application/PhabricatorApplicationApplications.php
@@ -1,46 +1,45 @@
<?php
final class PhabricatorApplicationApplications extends PhabricatorApplication {
public function canUninstall() {
return false;
}
public function getBaseURI() {
return '/applications/';
}
public function getShortDescription() {
return 'Installed Applications';
}
public function getIconName() {
return 'application';
}
public function getTitleGlyph() {
return "\xE0\xBC\x84";
}
public function getApplicationGroup() {
return self::GROUP_ADMIN;
}
public function getRoutes() {
return array(
'/applications/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' =>
'PhabricatorApplicationsListController',
'view/(?P<application>\w+)/' =>
'PhabricatorApplicationDetailViewController',
'edit/(?P<application>\w+)/' =>
'PhabricatorApplicationEditController',
'(?P<application>\w+)/(?P<action>install|uninstall)/' =>
'PhabricatorApplicationUninstallController',
),
);
}
}
-
diff --git a/src/applications/meta/controller/PhabricatorApplicationUninstallController.php b/src/applications/meta/controller/PhabricatorApplicationUninstallController.php
index 7c480ad81e..8445526a04 100644
--- a/src/applications/meta/controller/PhabricatorApplicationUninstallController.php
+++ b/src/applications/meta/controller/PhabricatorApplicationUninstallController.php
@@ -1,93 +1,92 @@
<?php
final class PhabricatorApplicationUninstallController
extends PhabricatorApplicationsController {
private $application;
private $action;
public function willProcessRequest(array $data) {
$this->application = $data['application'];
$this->action = $data['action'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$selected = PhabricatorApplication::getByClass($this->application);
if (!$selected) {
return new Aphront404Response();
}
$view_uri = $this->getApplicationURI('view/'.$this->application);
$beta_enabled = PhabricatorEnv::getEnvConfig(
'phabricator.show-beta-applications');
$dialog = id(new AphrontDialogView())
->setUser($user)
->addCancelButton($view_uri);
if ($selected->isBeta() && !$beta_enabled) {
$dialog
->setTitle(pht('Beta Applications Not Enabled'))
->appendChild(
pht(
'To manage beta applications, enable them by setting %s in your '.
'Phabricator configuration.',
phutil_tag('tt', array(), 'phabricator.show-beta-applications')));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
if ($request->isDialogFormPost()) {
$this->manageApplication();
return id(new AphrontRedirectResponse())->setURI($view_uri);
}
if ($this->action == 'install') {
if ($selected->canUninstall()) {
$dialog->setTitle('Confirmation')
->appendChild(
'Install '. $selected->getName(). ' application?')
->addSubmitButton('Install');
} else {
$dialog->setTitle('Information')
->appendChild('You cannot install an installed application.');
}
} else {
if ($selected->canUninstall()) {
$dialog->setTitle('Confirmation')
->appendChild(
'Really Uninstall '. $selected->getName(). ' application?')
->addSubmitButton('Uninstall');
} else {
$dialog->setTitle('Information')
->appendChild(
'This application cannot be uninstalled,
because it is required for Phabricator to work.');
}
}
return id(new AphrontDialogResponse())->setDialog($dialog);
}
public function manageApplication() {
$key = 'phabricator.uninstalled-applications';
$config_entry = PhabricatorConfigEntry::loadConfigEntry($key);
$list = $config_entry->getValue();
$uninstalled = PhabricatorEnv::getEnvConfig($key);
if (isset($uninstalled[$this->application])) {
unset($list[$this->application]);
} else {
$list[$this->application] = true;
}
PhabricatorConfigEditor::storeNewValue(
$config_entry, $list, $this->getRequest());
}
}
-
diff --git a/src/applications/metamta/adapter/PhabricatorMailImplementationSendGridAdapter.php b/src/applications/metamta/adapter/PhabricatorMailImplementationSendGridAdapter.php
index 7e41491ace..44c8acd2f5 100644
--- a/src/applications/metamta/adapter/PhabricatorMailImplementationSendGridAdapter.php
+++ b/src/applications/metamta/adapter/PhabricatorMailImplementationSendGridAdapter.php
@@ -1,158 +1,157 @@
<?php
/**
* Mail adapter that uses SendGrid's web API to deliver email.
*/
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/nuance/application/PhabricatorApplicationNuance.php b/src/applications/nuance/application/PhabricatorApplicationNuance.php
index b2d123ee2e..ac962b1982 100644
--- a/src/applications/nuance/application/PhabricatorApplicationNuance.php
+++ b/src/applications/nuance/application/PhabricatorApplicationNuance.php
@@ -1,73 +1,72 @@
<?php
final class PhabricatorApplicationNuance extends PhabricatorApplication {
public function getIconName() {
return 'nuance';
}
public function getTitleGlyph() {
return "\xE2\x98\x8E";
}
public function isBeta() {
return true;
}
public function shouldAppearInLaunchView() {
// try to hide this even more for now
return false;
}
public function canUninstall() {
return true;
}
public function getBaseURI() {
return '/nuance/';
}
public function getRoutes() {
return array(
'/nuance/' => array(
'item/' => array(
'view/(?P<id>[1-9]\d*)/' => 'NuanceItemViewController',
'edit/(?P<id>[1-9]\d*)/' => 'NuanceItemEditController',
'new/' => 'NuanceItemEditController',
),
'source/' => array(
'view/(?P<id>[1-9]\d*)/' => 'NuanceSourceViewController',
'edit/(?P<id>[1-9]\d*)/' => 'NuanceSourceEditController',
'new/' => 'NuanceSourceEditController',
),
'queue/' => array(
'view/(?P<id>[1-9]\d*)/' => 'NuanceQueueViewController',
'edit/(?P<id>[1-9]\d*)/' => 'NuanceQueueEditController',
'new/' => 'NuanceQueueEditController',
),
'requestor/' => array(
'view/(?P<id>[1-9]\d*)/' => 'NuanceRequestorViewController',
'edit/(?P<id>[1-9]\d*)/' => 'NuanceRequestorEditController',
'new/' => 'NuanceRequestorEditController',
),
),
);
}
protected function getCustomCapabilities() {
return array(
NuanceCapabilitySourceDefaultView::CAPABILITY => array(
'caption' => pht(
'Default view policy for newly created sources.'),
),
NuanceCapabilitySourceDefaultEdit::CAPABILITY => array(
'caption' => pht(
'Default edit policy for newly created sources.'),
),
NuanceCapabilitySourceManage::CAPABILITY => array(
),
);
}
}
-
diff --git a/src/applications/nuance/query/NuanceItemQuery.php b/src/applications/nuance/query/NuanceItemQuery.php
index 18a96b9f79..d37c2176f8 100644
--- a/src/applications/nuance/query/NuanceItemQuery.php
+++ b/src/applications/nuance/query/NuanceItemQuery.php
@@ -1,71 +1,70 @@
<?php
final class NuanceItemQuery
extends NuanceQuery {
private $ids;
private $phids;
private $sourceIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withSourceIDs($source_ids) {
$this->sourceIDs = $source_ids;
return $this;
}
public function loadPage() {
$table = new NuanceItem();
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT FROM %T %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data);
}
protected function buildWhereClause($conn_r) {
$where = array();
$where[] = $this->buildPagingClause($conn_r);
if ($this->sourceID) {
$where[] = qsprintf(
$conn_r,
'sourceID IN (%Ld)',
$this->sourceIDs);
}
if ($this->ids) {
$where[] = qsprintf(
$conn_r,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids) {
$where[] = qsprintf(
$conn_r,
'phid IN (%Ls)',
$this->phids);
}
return $this->formatWhereClause($where);
}
}
-
diff --git a/src/applications/nuance/query/NuanceQuery.php b/src/applications/nuance/query/NuanceQuery.php
index c286cdbd3b..617cada36d 100644
--- a/src/applications/nuance/query/NuanceQuery.php
+++ b/src/applications/nuance/query/NuanceQuery.php
@@ -1,11 +1,10 @@
<?php
abstract class NuanceQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
public function getQueryApplicationClass() {
return 'PhabricatorApplicationNuance';
}
}
-
diff --git a/src/applications/nuance/query/NuanceQueueQuery.php b/src/applications/nuance/query/NuanceQueueQuery.php
index ce5ce30a5c..942753dbf4 100644
--- a/src/applications/nuance/query/NuanceQueueQuery.php
+++ b/src/applications/nuance/query/NuanceQueueQuery.php
@@ -1,57 +1,56 @@
<?php
final class NuanceQueueQuery
extends NuanceQuery {
private $ids;
private $phids;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function loadPage() {
$table = new NuanceQueue();
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT FROM %T %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data);
}
protected function buildWhereClause($conn_r) {
$where = array();
$where[] = $this->buildPagingClause($conn_r);
if ($this->ids) {
$where[] = qsprintf(
$conn_r,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids) {
$where[] = qsprintf(
$conn_r,
'phid IN (%Ls)',
$this->phids);
}
return $this->formatWhereClause($where);
}
}
-
diff --git a/src/applications/nuance/query/NuanceRequestorQuery.php b/src/applications/nuance/query/NuanceRequestorQuery.php
index c9e8fc5ea4..a1df9b98f3 100644
--- a/src/applications/nuance/query/NuanceRequestorQuery.php
+++ b/src/applications/nuance/query/NuanceRequestorQuery.php
@@ -1,57 +1,56 @@
<?php
final class NuanceRequestorQuery
extends NuanceQuery {
private $ids;
private $phids;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function loadPage() {
$table = new NuanceRequestor();
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT FROM %T %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data);
}
protected function buildWhereClause($conn_r) {
$where = array();
$where[] = $this->buildPagingClause($conn_r);
if ($this->ids) {
$where[] = qsprintf(
$conn_r,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids) {
$where[] = qsprintf(
$conn_r,
'phid IN (%Ls)',
$this->phids);
}
return $this->formatWhereClause($where);
}
}
-
diff --git a/src/applications/nuance/source/NuanceSourceDefinition.php b/src/applications/nuance/source/NuanceSourceDefinition.php
index 86eea6ddeb..e643daa0b1 100644
--- a/src/applications/nuance/source/NuanceSourceDefinition.php
+++ b/src/applications/nuance/source/NuanceSourceDefinition.php
@@ -1,263 +1,262 @@
<?php
abstract class NuanceSourceDefinition extends Phobject {
private $actor;
private $sourceObject;
public function setActor(PhabricatorUser $actor) {
$this->actor = $actor;
return $this;
}
public function getActor() {
return $this->actor;
}
public function requireActor() {
$actor = $this->getActor();
if (!$actor) {
throw new Exception('You must "setActor()" first!');
}
return $actor;
}
public function setSourceObject(NuanceSource $source) {
$source->setType($this->getSourceTypeConstant());
$this->sourceObject = $source;
return $this;
}
public function getSourceObject() {
return $this->sourceObject;
}
public function requireSourceObject() {
$source = $this->getSourceObject();
if (!$source) {
throw new Exception('You must "setSourceObject()" first!');
}
return $source;
}
public static function getSelectOptions() {
$definitions = self::getAllDefinitions();
$options = array();
foreach ($definitions as $definition) {
$key = $definition->getSourceTypeConstant();
$name = $definition->getName();
$options[$key] = $name;
}
return $options;
}
/**
* Gives a @{class:NuanceSourceDefinition} object for a given
* @{class:NuanceSource}. Note you still need to @{method:setActor}
* before the @{class:NuanceSourceDefinition} object will be useful.
*/
public static function getDefinitionForSource(NuanceSource $source) {
$definitions = self::getAllDefinitions();
$map = mpull($definitions, null, 'getSourceTypeConstant');
$definition = $map[$source->getType()];
$definition->setSourceObject($source);
return $definition;
}
public static function getAllDefinitions() {
static $definitions;
if ($definitions === null) {
$objects = id(new PhutilSymbolLoader())
->setAncestorClass(__CLASS__)
->loadObjects();
foreach ($objects as $definition) {
$key = $definition->getSourceTypeConstant();
$name = $definition->getName();
if (isset($definitions[$key])) {
$conflict = $definitions[$key];
throw new Exception(sprintf(
'Defintion %s conflicts with definition %s. This is a programming '.
'error.',
$conflict,
$name));
}
}
$definitions = $objects;
}
return $definitions;
}
/**
* A human readable string like "Twitter" or "Phabricator Form".
*/
abstract public function getName();
/**
* This should be a any VARCHAR(32).
*
* @{method:getAllDefinitions} will throw if you choose a string that
* collides with another @{class:NuanceSourceDefinition} class.
*/
abstract public function getSourceTypeConstant();
/**
* Code to create and update @{class:NuanceItem}s and
* @{class:NuanceRequestor}s via daemons goes here.
*
* If that does not make sense for the @{class:NuanceSource} you are
* defining, simply return null. For example,
* @{class:NuancePhabricatorFormSourceDefinition} since these are one-way
* contact forms.
*/
abstract public function updateItems();
private function loadSourceObjectPolicies(
PhabricatorUser $user,
NuanceSource $source) {
$user = $this->requireActor();
$source = $this->requireSourceObject();
return id(new PhabricatorPolicyQuery())
->setViewer($user)
->setObject($source)
->execute();
}
final public function getEditTitle() {
$source = $this->requireSourceObject();
if ($source->getPHID()) {
$title = pht('Edit "%s" source.', $source->getName());
} else {
$title = pht('Create a new "%s" source.', $this->getName());
}
return $title;
}
final public function buildEditLayout(AphrontRequest $request) {
$actor = $this->requireActor();
$source = $this->requireSourceObject();
$form_errors = array();
$error_messages = array();
$transactions = array();
$validation_exception = null;
if ($request->isFormPost()) {
$transactions = $this->buildTransactions($request);
try {
$editor = id(new NuanceSourceEditor())
->setActor($actor)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->applyTransactions($source, $transactions);
return id(new AphrontRedirectResponse())
->setURI($source->getURI());
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
}
}
$form = $this->renderEditForm($validation_exception);
$layout = id(new PHUIObjectBoxView())
->setHeaderText($this->getEditTitle())
->setValidationException($validation_exception)
->setFormErrors($error_messages)
->setForm($form);
return $layout;
}
/**
* Code to create a form to edit the @{class:NuanceItem} you are defining.
*
* return @{class:AphrontFormView}
*/
private function renderEditForm(
PhabricatorApplicationTransactionValidationException $ex = null) {
$user = $this->requireActor();
$source = $this->requireSourceObject();
$policies = $this->loadSourceObjectPolicies($user, $source);
$e_name = null;
if ($ex) {
$e_name = $ex->getShortMessage(NuanceSourceTransaction::TYPE_NAME);
}
$form = id(new AphrontFormView())
->setUser($user)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
->setError($e_name)
->setValue($source->getName()))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Type'))
->setName('type')
->setOptions(self::getSelectOptions())
->setValue($source->getType()));
$form = $this->augmentEditForm($form, $ex);
$form
->appendChild(
id(new AphrontFormPolicyControl())
->setUser($user)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setPolicyObject($source)
->setPolicies($policies)
->setName('viewPolicy'))
->appendChild(
id(new AphrontFormPolicyControl())
->setUser($user)
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicyObject($source)
->setPolicies($policies)
->setName('editPolicy'))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($source->getURI())
->setValue(pht('Save')));
return $form;
}
/**
* return @{class:AphrontFormView}
*/
protected function augmentEditForm(
AphrontFormView $form,
PhabricatorApplicationTransactionValidationException $ex = null) {
return $form;
}
/**
* Hook to build up @{class:PhabricatorTransactions}.
*
* return array $transactions
*/
protected function buildTransactions(AphrontRequest $request) {
$transactions = array();
$transactions[] = id(new NuanceSourceTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
->setNewValue($request->getStr('editPolicy'));
$transactions[] = id(new NuanceSourceTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
->setNewValue($request->getStr('viewPolicy'));
$transactions[] = id(new NuanceSourceTransaction())
->setTransactionType(NuanceSourceTransaction::TYPE_NAME)
->setNewvalue($request->getStr('name'));
return $transactions;
}
abstract public function renderView();
abstract public function renderListView();
}
-
diff --git a/src/applications/owners/mail/OwnersPackageReplyHandler.php b/src/applications/owners/mail/OwnersPackageReplyHandler.php
index 31bdf4cb00..e530ee41ed 100644
--- a/src/applications/owners/mail/OwnersPackageReplyHandler.php
+++ b/src/applications/owners/mail/OwnersPackageReplyHandler.php
@@ -1,32 +1,30 @@
<?php
final class OwnersPackageReplyHandler extends PhabricatorMailReplyHandler {
public function validateMailReceiver($mail_receiver) {
if (!($mail_receiver instanceof PhabricatorOwnersPackage)) {
throw new Exception("Receiver is not a PhabricatorOwnersPackage!");
}
}
public function getPrivateReplyHandlerEmailAddress(
PhabricatorObjectHandle $handle) {
return null;
}
public function getPublicReplyHandlerEmailAddress() {
return null;
}
public function getReplyHandlerDomain() {
return null;
}
public function getReplyHandlerInstructions() {
return null;
}
protected function receiveEmail(PhabricatorMetaMTAReceivedMail $mail) {
return;
}
}
-
-
diff --git a/src/applications/people/storage/PhabricatorUserConfiguredCustomFieldStorage.php b/src/applications/people/storage/PhabricatorUserConfiguredCustomFieldStorage.php
index fe5f21b6bd..432938d3a4 100644
--- a/src/applications/people/storage/PhabricatorUserConfiguredCustomFieldStorage.php
+++ b/src/applications/people/storage/PhabricatorUserConfiguredCustomFieldStorage.php
@@ -1,11 +1,10 @@
<?php
final class PhabricatorUserConfiguredCustomFieldStorage
extends PhabricatorCustomFieldStorage {
public function getApplicationName() {
return 'user';
}
}
-
diff --git a/src/applications/people/storage/PhabricatorUserCustomFieldNumericIndex.php b/src/applications/people/storage/PhabricatorUserCustomFieldNumericIndex.php
index 0f36bd8e57..eaf9334d02 100644
--- a/src/applications/people/storage/PhabricatorUserCustomFieldNumericIndex.php
+++ b/src/applications/people/storage/PhabricatorUserCustomFieldNumericIndex.php
@@ -1,11 +1,10 @@
<?php
final class PhabricatorUserCustomFieldNumericIndex
extends PhabricatorCustomFieldNumericIndexStorage {
public function getApplicationName() {
return 'user';
}
}
-
diff --git a/src/applications/people/storage/PhabricatorUserCustomFieldStringIndex.php b/src/applications/people/storage/PhabricatorUserCustomFieldStringIndex.php
index 399385c8cc..25e4d64de6 100644
--- a/src/applications/people/storage/PhabricatorUserCustomFieldStringIndex.php
+++ b/src/applications/people/storage/PhabricatorUserCustomFieldStringIndex.php
@@ -1,11 +1,10 @@
<?php
final class PhabricatorUserCustomFieldStringIndex
extends PhabricatorCustomFieldStringIndexStorage {
public function getApplicationName() {
return 'user';
}
}
-
diff --git a/src/applications/people/storage/PhabricatorUserTransaction.php b/src/applications/people/storage/PhabricatorUserTransaction.php
index eb7e29929e..86551be428 100644
--- a/src/applications/people/storage/PhabricatorUserTransaction.php
+++ b/src/applications/people/storage/PhabricatorUserTransaction.php
@@ -1,19 +1,18 @@
<?php
final class PhabricatorUserTransaction
extends PhabricatorApplicationTransaction {
public function getApplicationName() {
return 'user';
}
public function getApplicationTransactionType() {
return PhabricatorPeoplePHIDTypeUser::TYPECONST;
}
public function getApplicationTransactionCommentObject() {
return null;
}
}
-
diff --git a/src/applications/pholio/event/PholioActionMenuEventListener.php b/src/applications/pholio/event/PholioActionMenuEventListener.php
index 6e55b065c1..15954520b6 100644
--- a/src/applications/pholio/event/PholioActionMenuEventListener.php
+++ b/src/applications/pholio/event/PholioActionMenuEventListener.php
@@ -1,52 +1,51 @@
<?php
final class PholioActionMenuEventListener
extends PhabricatorEventListener {
public function register() {
$this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS);
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS:
$this->handleActionsEvent($event);
break;
}
}
private function handleActionsEvent(PhutilEvent $event) {
$object = $event->getValue('object');
$actions = null;
if ($object instanceof ManiphestTask) {
$actions = $this->renderTaskItems($event);
}
$this->addActionMenuItems($event, $actions);
}
private function renderTaskItems(PhutilEvent $event) {
if (!$this->canUseApplication($event->getUser())) {
return;
}
$task = $event->getValue('object');
$phid = $task->getPHID();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$event->getUser(),
$task,
PhabricatorPolicyCapability::CAN_EDIT);
return id(new PhabricatorActionView())
->setName(pht('Edit Pholio Mocks'))
->setHref("/search/attach/{$phid}/MOCK/edge/")
->setWorkflow(true)
->setIcon('attach')
->setDisabled(!$can_edit)
->setWorkflow(true);
}
}
-
diff --git a/src/applications/phragment/application/PhabricatorApplicationPhragment.php b/src/applications/phragment/application/PhabricatorApplicationPhragment.php
index aaa8f3c4bc..d82e2dba5b 100644
--- a/src/applications/phragment/application/PhabricatorApplicationPhragment.php
+++ b/src/applications/phragment/application/PhabricatorApplicationPhragment.php
@@ -1,68 +1,67 @@
<?php
final class PhabricatorApplicationPhragment extends PhabricatorApplication {
public function getBaseURI() {
return '/phragment/';
}
public function getShortDescription() {
return pht('Versioned Artifact Storage');
}
public function getIconName() {
return 'phragment';
}
public function getTitleGlyph() {
return "\xE2\x26\xB6";
}
public function getApplicationGroup() {
return self::GROUP_UTILITIES;
}
public function isBeta() {
return true;
}
public function canUninstall() {
return true;
}
public function getRoutes() {
return array(
'/phragment/' => array(
'' => 'PhragmentBrowseController',
'browse/(?P<dblob>.*)' => 'PhragmentBrowseController',
'create/(?P<dblob>.*)' => 'PhragmentCreateController',
'update/(?P<dblob>.*)' => 'PhragmentUpdateController',
'policy/(?P<dblob>.*)' => 'PhragmentPolicyController',
'history/(?P<dblob>.*)' => 'PhragmentHistoryController',
'zip/(?P<dblob>.*)' => 'PhragmentZIPController',
'zip@(?P<snapshot>[^/]+)/(?P<dblob>.*)' => 'PhragmentZIPController',
'version/(?P<id>[0-9]*)/' => 'PhragmentVersionController',
'patch/(?P<aid>[0-9x]*)/(?P<bid>[0-9]*)/' => 'PhragmentPatchController',
'revert/(?P<id>[0-9]*)/(?P<dblob>.*)' => 'PhragmentRevertController',
'snapshot/' => array(
'create/(?P<dblob>.*)' => 'PhragmentSnapshotCreateController',
'view/(?P<id>[0-9]*)/' => 'PhragmentSnapshotViewController',
'delete/(?P<id>[0-9]*)/' => 'PhragmentSnapshotDeleteController',
'promote/' => array(
'latest/(?P<dblob>.*)' => 'PhragmentSnapshotPromoteController',
'(?P<id>[0-9]*)/' => 'PhragmentSnapshotPromoteController',
),
),
),
);
}
protected function getCustomCapabilities() {
return array(
PhragmentCapabilityCanCreate::CAPABILITY => array(
),
);
}
}
-
diff --git a/src/applications/phrequent/application/PhabricatorApplicationPhrequent.php b/src/applications/phrequent/application/PhabricatorApplicationPhrequent.php
index bf406cd703..35f2dd9fd9 100644
--- a/src/applications/phrequent/application/PhabricatorApplicationPhrequent.php
+++ b/src/applications/phrequent/application/PhabricatorApplicationPhrequent.php
@@ -1,62 +1,61 @@
<?php
final class PhabricatorApplicationPhrequent extends PhabricatorApplication {
public function getShortDescription() {
return pht('Track Time');
}
public function getBaseURI() {
return '/phrequent/';
}
public function isBeta() {
return true;
}
public function getIconName() {
return 'phrequent';
}
public function getApplicationGroup() {
return self::GROUP_ORGANIZATION;
}
public function getApplicationOrder() {
return 0.110;
}
public function getEventListeners() {
return array(
new PhrequentUIEventListener(),
);
}
public function getRoutes() {
return array(
'/phrequent/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'PhrequentListController',
'track/(?P<verb>[a-z]+)/(?P<phid>[^/]+)/'
=> 'PhrequentTrackController'
),
);
}
public function loadStatus(PhabricatorUser $user) {
$status = array();
// Show number of objects that are currently
// being tracked for a user.
$count = PhrequentUserTimeQuery::getUserTotalObjectsTracked($user);
$type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION;
$status[] = id(new PhabricatorApplicationStatusView())
->setType($type)
->setText(pht('%d Object(s) Tracked', $count))
->setCount($count);
return $status;
}
}
-
diff --git a/src/applications/phrequent/query/PhrequentSearchEngine.php b/src/applications/phrequent/query/PhrequentSearchEngine.php
index 5e3d152f3a..48fd907b51 100644
--- a/src/applications/phrequent/query/PhrequentSearchEngine.php
+++ b/src/applications/phrequent/query/PhrequentSearchEngine.php
@@ -1,114 +1,113 @@
<?php
final class PhrequentSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getPageSize(PhabricatorSavedQuery $saved) {
return $saved->getParameter('limit', 1000);
}
public function buildSavedQueryFromRequest(AphrontRequest $request) {
$saved = new PhabricatorSavedQuery();
$saved->setParameter(
'userPHIDs',
$this->readUsersFromRequest($request, 'users'));
$saved->setParameter('ended', $request->getStr('ended'));
$saved->setParameter('order', $request->getStr('order'));
return $saved;
}
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
$query = id(new PhrequentUserTimeQuery());
$user_phids = $saved->getParameter('userPHIDs');
if ($user_phids) {
$query->withUserPHIDs($user_phids);
}
$ended = $saved->getParameter('ended');
if ($ended != null) {
$query->withEnded($ended);
}
$order = $saved->getParameter('order');
if ($order != null) {
$query->setOrder($order);
}
return $query;
}
public function buildSearchForm(
AphrontFormView $form,
PhabricatorSavedQuery $saved_query) {
$user_phids = $saved_query->getParameter('userPHIDs', array());
$ended = $saved_query->getParameter(
'ended', PhrequentUserTimeQuery::ENDED_ALL);
$order = $saved_query->getParameter(
'order', PhrequentUserTimeQuery::ORDER_ENDED_DESC);
$phids = array_merge($user_phids);
$handles = id(new PhabricatorHandleQuery())
->setViewer($this->requireViewer())
->withPHIDs($phids)
->execute();
$form
->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/users/')
->setName('users')
->setLabel(pht('Users'))
->setValue($handles))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Ended'))
->setName('ended')
->setValue($ended)
->setOptions(PhrequentUserTimeQuery::getEndedSearchOptions()))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Order'))
->setName('order')
->setValue($order)
->setOptions(PhrequentUserTimeQuery::getOrderSearchOptions()));
}
protected function getURI($path) {
return '/phrequent/'.$path;
}
public function getBuiltinQueryNames() {
$names = array(
'tracking' => pht('Currently Tracking'),
'all' => pht('All Tracked'),
);
return $names;
}
public function buildSavedQueryFromBuiltin($query_key) {
$query = $this->newSavedQuery();
$query->setQueryKey($query_key);
switch ($query_key) {
case 'all':
return $query
->setParameter('order', PhrequentUserTimeQuery::ORDER_ENDED_DESC);
case 'tracking':
return $query
->setParameter('ended', PhrequentUserTimeQuery::ENDED_NO)
->setParameter('order', PhrequentUserTimeQuery::ORDER_ENDED_DESC);
}
return parent::buildSavedQueryFromBuiltin($query_key);
}
}
-
diff --git a/src/applications/phriction/application/PhabricatorApplicationPhriction.php b/src/applications/phriction/application/PhabricatorApplicationPhriction.php
index d0573e872b..5ede3d5229 100644
--- a/src/applications/phriction/application/PhabricatorApplicationPhriction.php
+++ b/src/applications/phriction/application/PhabricatorApplicationPhriction.php
@@ -1,70 +1,69 @@
<?php
final class PhabricatorApplicationPhriction extends PhabricatorApplication {
public function getShortDescription() {
return pht('Wiki');
}
public function getBaseURI() {
return '/w/';
}
public function getIconName() {
return 'phriction';
}
public function getHelpURI() {
return PhabricatorEnv::getDoclink('article/Phriction_User_Guide.html');
}
public function getTitleGlyph() {
return "\xE2\x9A\xA1";
}
public function getRemarkupRules() {
return array(
new PhrictionRemarkupRule(),
);
}
public function getEventListeners() {
return array(
new PhrictionActionMenuEventListener(),
);
}
public function getRoutes() {
return array(
// Match "/w/" with slug "/".
'/w(?P<slug>/)' => 'PhrictionDocumentController',
// Match "/w/x/y/z/" with slug "x/y/z/".
'/w/(?P<slug>.+/)' => 'PhrictionDocumentController',
'/phriction/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'PhrictionListController',
'history(?P<slug>/)' => 'PhrictionHistoryController',
'history/(?P<slug>.+/)' => 'PhrictionHistoryController',
'edit/(?:(?P<id>[1-9]\d*)/)?' => 'PhrictionEditController',
'delete/(?P<id>[1-9]\d*)/' => 'PhrictionDeleteController',
'new/' => 'PhrictionNewController',
'move/(?:(?P<id>[1-9]\d*)/)?' => 'PhrictionMoveController',
'preview/' => 'PhabricatorMarkupPreviewController',
'diff/(?P<id>[1-9]\d*)/' => 'PhrictionDiffController',
),
);
}
public function getApplicationGroup() {
return self::GROUP_COMMUNICATION;
}
public function getApplicationOrder() {
return 0.140;
}
}
-
diff --git a/src/applications/policy/application/PhabricatorApplicationPolicy.php b/src/applications/policy/application/PhabricatorApplicationPolicy.php
index 6ecf69c9c9..7d0ff9df2a 100644
--- a/src/applications/policy/application/PhabricatorApplicationPolicy.php
+++ b/src/applications/policy/application/PhabricatorApplicationPolicy.php
@@ -1,24 +1,23 @@
<?php
final class PhabricatorApplicationPolicy extends PhabricatorApplication {
public function shouldAppearInLaunchView() {
return false;
}
public function canUninstall() {
return false;
}
public function getRoutes() {
return array(
'/policy/' => array(
'explain/(?P<phid>[^/]+)/(?P<capability>[^/]+)/'
=> 'PhabricatorPolicyExplainController',
'edit/(?:(?P<phid>[^/]+)/)?' => 'PhabricatorPolicyEditController',
),
);
}
}
-
diff --git a/src/applications/policy/capability/PhabricatorPolicyCapability.php b/src/applications/policy/capability/PhabricatorPolicyCapability.php
index a34e0f76bd..1cff309a64 100644
--- a/src/applications/policy/capability/PhabricatorPolicyCapability.php
+++ b/src/applications/policy/capability/PhabricatorPolicyCapability.php
@@ -1,72 +1,70 @@
<?php
abstract class PhabricatorPolicyCapability extends Phobject {
const CAN_VIEW = 'view';
const CAN_EDIT = 'edit';
const CAN_JOIN = 'join';
/**
* Get the unique key identifying this capability. This key must be globally
* unique. Application capabilities should be namespaced. For example:
*
* application.create
*
* @return string Globally unique capability key.
*/
abstract public function getCapabilityKey();
/**
* Return a human-readable descriptive name for this capability, like
* "Can View".
*
* @return string Human-readable name describing the capability.
*/
abstract public function getCapabilityName();
/**
* Return a human-readable string describing what not having this capability
* prevents the user from doing. For example:
*
* - You do not have permission to edit this object.
* - You do not have permission to create new tasks.
*
* @return string Human-readable name describing what failing a check for this
* capability prevents the user from doing.
*/
public function describeCapabilityRejection() {
return null;
}
/**
* Can this capability be set to "public"? Broadly, this is only appropriate
* for view and view-related policies.
*
* @return bool True to allow the "public" policy. Returns false by default.
*/
public function shouldAllowPublicPolicySetting() {
return false;
}
final public static function getCapabilityByKey($key) {
return idx(self::getCapabilityMap(), $key);
}
final public static function getCapabilityMap() {
static $map;
if ($map === null) {
$capabilities = id(new PhutilSymbolLoader())
->setAncestorClass(__CLASS__)
->loadObjects();
$map = mpull($capabilities, null, 'getCapabilityKey');
}
return $map;
}
}
-
-
diff --git a/src/applications/policy/query/PhabricatorPolicyQuery.php b/src/applications/policy/query/PhabricatorPolicyQuery.php
index 37c38a5d6b..ac2c600305 100644
--- a/src/applications/policy/query/PhabricatorPolicyQuery.php
+++ b/src/applications/policy/query/PhabricatorPolicyQuery.php
@@ -1,237 +1,236 @@
<?php
final class PhabricatorPolicyQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $object;
private $phids;
public function setObject(PhabricatorPolicyInterface $object) {
$this->object = $object;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public static function loadPolicies(
PhabricatorUser $viewer,
PhabricatorPolicyInterface $object) {
$results = array();
$map = array();
foreach ($object->getCapabilities() as $capability) {
$map[$capability] = $object->getPolicy($capability);
}
$policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->withPHIDs($map)
->execute();
foreach ($map as $capability => $phid) {
$results[$capability] = $policies[$phid];
}
return $results;
}
public static function renderPolicyDescriptions(
PhabricatorUser $viewer,
PhabricatorPolicyInterface $object,
$icon = false) {
$policies = self::loadPolicies($viewer, $object);
foreach ($policies as $capability => $policy) {
$policies[$capability] = $policy->renderDescription($icon);
}
return $policies;
}
public function loadPage() {
if ($this->object && $this->phids) {
throw new Exception(
"You can not issue a policy query with both setObject() and ".
"setPHIDs().");
} else if ($this->object) {
$phids = $this->loadObjectPolicyPHIDs();
} else {
$phids = $this->phids;
}
$phids = array_fuse($phids);
$results = array();
// First, load global policies.
foreach ($this->getGlobalPolicies() as $phid => $policy) {
if (isset($phids[$phid])) {
$results[$phid] = $policy;
unset($phids[$phid]);
}
}
// If we still need policies, we're going to have to fetch data. Bucket
// the remaining policies into rule-based policies and handle-based
// policies.
if ($phids) {
$rule_policies = array();
$handle_policies = array();
foreach ($phids as $phid) {
$phid_type = phid_get_type($phid);
if ($phid_type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) {
$rule_policies[$phid] = $phid;
} else {
$handle_policies[$phid] = $phid;
}
}
if ($handle_policies) {
$handles = id(new PhabricatorHandleQuery())
->setViewer($this->getViewer())
->withPHIDs($handle_policies)
->execute();
foreach ($handle_policies as $phid) {
$results[$phid] = PhabricatorPolicy::newFromPolicyAndHandle(
$phid,
$handles[$phid]);
}
}
if ($rule_policies) {
$rules = id(new PhabricatorPolicy())->loadAllWhere(
'phid IN (%Ls)',
$rule_policies);
$results += mpull($rules, null, 'getPHID');
}
}
$results = msort($results, 'getSortKey');
return $results;
}
public static function isGlobalPolicy($policy) {
$globalPolicies = self::getGlobalPolicies();
if (isset($globalPolicies[$policy])) {
return true;
}
return false;
}
public static function getGlobalPolicy($policy) {
if (!self::isGlobalPolicy($policy)) {
throw new Exception("Policy '{$policy}' is not a global policy!");
}
return idx(self::getGlobalPolicies(), $policy);
}
private static function getGlobalPolicies() {
static $constants = array(
PhabricatorPolicies::POLICY_PUBLIC,
PhabricatorPolicies::POLICY_USER,
PhabricatorPolicies::POLICY_ADMIN,
PhabricatorPolicies::POLICY_NOONE,
);
$results = array();
foreach ($constants as $constant) {
$results[$constant] = id(new PhabricatorPolicy())
->setType(PhabricatorPolicyType::TYPE_GLOBAL)
->setPHID($constant)
->setName(self::getGlobalPolicyName($constant))
->setShortName(self::getGlobalPolicyShortName($constant))
->makeEphemeral();
}
return $results;
}
private static function getGlobalPolicyName($policy) {
switch ($policy) {
case PhabricatorPolicies::POLICY_PUBLIC:
return pht('Public (No Login Required)');
case PhabricatorPolicies::POLICY_USER:
return pht('All Users');
case PhabricatorPolicies::POLICY_ADMIN:
return pht('Administrators');
case PhabricatorPolicies::POLICY_NOONE:
return pht('No One');
default:
return pht('Unknown Policy');
}
}
private static function getGlobalPolicyShortName($policy) {
switch ($policy) {
case PhabricatorPolicies::POLICY_PUBLIC:
return pht('Public');
default:
return null;
}
}
private function loadObjectPolicyPHIDs() {
$phids = array();
$viewer = $this->getViewer();
if ($viewer->getPHID()) {
$projects = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withMemberPHIDs(array($viewer->getPHID()))
->execute();
foreach ($projects as $project) {
$phids[] = $project->getPHID();
}
// Include the "current viewer" policy. This improves consistency, but
// is also useful for creating private instances of normally-shared object
// types, like repositories.
$phids[] = $viewer->getPHID();
}
$capabilities = $this->object->getCapabilities();
foreach ($capabilities as $capability) {
$policy = $this->object->getPolicy($capability);
if (!$policy) {
continue;
}
$phids[] = $policy;
}
// If this install doesn't have "Public" enabled, don't include it as an
// option unless the object already has a "Public" policy. In this case we
// retain the policy but enforce it as though it was "All Users".
$show_public = PhabricatorEnv::getEnvConfig('policy.allow-public');
foreach ($this->getGlobalPolicies() as $phid => $policy) {
if ($phid == PhabricatorPolicies::POLICY_PUBLIC) {
if (!$show_public) {
continue;
}
}
$phids[] = $phid;
}
return $phids;
}
protected function shouldDisablePolicyFiltering() {
// Policy filtering of policies is currently perilous and not required by
// the application.
return true;
}
public function getQueryApplicationClass() {
return 'PhabricatorApplicationPolicy';
}
}
-
diff --git a/src/applications/ponder/remarkup/PonderRemarkupRule.php b/src/applications/ponder/remarkup/PonderRemarkupRule.php
index 9102600cfc..840c98fc0a 100644
--- a/src/applications/ponder/remarkup/PonderRemarkupRule.php
+++ b/src/applications/ponder/remarkup/PonderRemarkupRule.php
@@ -1,31 +1,30 @@
<?php
final class PonderRemarkupRule
extends PhabricatorRemarkupRuleObject {
protected function getObjectNamePrefix() {
return 'Q';
}
protected function loadObjects(array $ids) {
$viewer = $this->getEngine()->getConfig('viewer');
return id(new PonderQuestionQuery())
->setViewer($viewer)
->withIDs($ids)
->execute();
}
protected function shouldMarkupObject(array $params) {
// NOTE: Q1, Q2, Q3 and Q4 are often used to refer to quarters of the year;
// mark them up only in the {Q1} format.
if ($params['type'] == 'ref') {
if ($params['id'] <= 4) {
return false;
}
}
return true;
}
}
-
diff --git a/src/applications/ponder/storage/PonderAnswerTransaction.php b/src/applications/ponder/storage/PonderAnswerTransaction.php
index f12303b3a7..24a8a86ba1 100644
--- a/src/applications/ponder/storage/PonderAnswerTransaction.php
+++ b/src/applications/ponder/storage/PonderAnswerTransaction.php
@@ -1,105 +1,104 @@
<?php
final class PonderAnswerTransaction
extends PhabricatorApplicationTransaction {
const TYPE_CONTENT = 'ponder.answer:content';
public function getApplicationName() {
return 'ponder';
}
public function getTableName() {
return 'ponder_answertransaction';
}
public function getApplicationTransactionType() {
return PonderPHIDTypeAnswer::TYPECONST;
}
public function getApplicationTransactionCommentObject() {
return new PonderAnswerTransactionComment();
}
public function getRequiredHandlePHIDs() {
$phids = parent::getRequiredHandlePHIDs();
switch ($this->getTransactionType()) {
case self::TYPE_CONTENT:
$phids[] = $this->getObjectPHID();
break;
}
return $phids;
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
switch ($this->getTransactionType()) {
case self::TYPE_CONTENT:
return pht(
'%s edited %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
return parent::getTitle();
}
public function getTitleForFeed(PhabricatorFeedStory $story) {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
switch ($this->getTransactionType()) {
case self::TYPE_CONTENT:
$answer = $story->getObject($object_phid);
$question = $answer->getQuestion();
$answer_handle = $this->getHandle($object_phid);
$link = $answer_handle->renderLink(
$question->getFullTitle());
return pht(
'%s updated their answer to %s',
$this->renderHandleLink($author_phid),
$link);
}
return parent::getTitleForFeed($story);
}
public function getBodyForFeed(PhabricatorFeedStory $story) {
$new = $this->getNewValue();
$body = null;
switch ($this->getTransactionType()) {
case self::TYPE_CONTENT:
return phutil_escape_html_newlines(
phutil_utf8_shorten($new, 128));
break;
}
return parent::getBodyForFeed($story);
}
public function hasChangeDetails() {
$old = $this->getOldValue();
switch ($this->getTransactionType()) {
case self::TYPE_CONTENT:
return $old !== null;
}
return parent::hasChangeDetails();
}
public function renderChangeDetails(PhabricatorUser $viewer) {
return $this->renderTextCorpusChangeDetails(
$viewer,
$this->getOldValue(),
$this->getNewValue());
}
}
-
diff --git a/src/applications/ponder/storage/PonderAnswerTransactionComment.php b/src/applications/ponder/storage/PonderAnswerTransactionComment.php
index 034798a20b..2dc601a3d6 100644
--- a/src/applications/ponder/storage/PonderAnswerTransactionComment.php
+++ b/src/applications/ponder/storage/PonderAnswerTransactionComment.php
@@ -1,11 +1,10 @@
<?php
final class PonderAnswerTransactionComment
extends PhabricatorApplicationTransactionComment {
public function getApplicationTransactionObject() {
return new PonderAnswerTransaction();
}
}
-
diff --git a/src/applications/ponder/storage/PonderComment.php b/src/applications/ponder/storage/PonderComment.php
index b1abf2fa9d..a877672bea 100644
--- a/src/applications/ponder/storage/PonderComment.php
+++ b/src/applications/ponder/storage/PonderComment.php
@@ -1,41 +1,40 @@
<?php
final class PonderComment extends PonderDAO
implements PhabricatorMarkupInterface {
const MARKUP_FIELD_CONTENT = 'markup:content';
protected $targetPHID;
protected $authorPHID;
protected $content;
public function getMarkupFieldKey($field) {
$hash = PhabricatorHash::digest($this->getMarkupText($field));
$id = $this->getID();
return "ponder:c{$id}:{$field}:{$hash}";
}
public function getMarkupText($field) {
return $this->getContent();
}
public function newMarkupEngine($field) {
return PhabricatorMarkupEngine::getEngine();
}
public function didMarkupText(
$field,
$output,
PhutilMarkupEngine $engine) {
return $output;
}
public function shouldUseMarkupCache($field) {
return (bool)$this->getID();
}
public function getMarkupField() {
return self::MARKUP_FIELD_CONTENT;
}
}
-
diff --git a/src/applications/ponder/storage/PonderQuestionTransactionComment.php b/src/applications/ponder/storage/PonderQuestionTransactionComment.php
index c5e0c0f0e3..46e20ff1ed 100644
--- a/src/applications/ponder/storage/PonderQuestionTransactionComment.php
+++ b/src/applications/ponder/storage/PonderQuestionTransactionComment.php
@@ -1,11 +1,10 @@
<?php
final class PonderQuestionTransactionComment
extends PhabricatorApplicationTransactionComment {
public function getApplicationTransactionObject() {
return new PonderQuestionTransaction();
}
}
-
diff --git a/src/applications/project/storage/PhabricatorProjectCustomFieldNumericIndex.php b/src/applications/project/storage/PhabricatorProjectCustomFieldNumericIndex.php
index 095155a0b4..8ddec8396a 100644
--- a/src/applications/project/storage/PhabricatorProjectCustomFieldNumericIndex.php
+++ b/src/applications/project/storage/PhabricatorProjectCustomFieldNumericIndex.php
@@ -1,11 +1,10 @@
<?php
final class PhabricatorProjectCustomFieldNumericIndex
extends PhabricatorCustomFieldNumericIndexStorage {
public function getApplicationName() {
return 'project';
}
}
-
diff --git a/src/applications/project/storage/PhabricatorProjectCustomFieldStorage.php b/src/applications/project/storage/PhabricatorProjectCustomFieldStorage.php
index a65483e428..4bfe884813 100644
--- a/src/applications/project/storage/PhabricatorProjectCustomFieldStorage.php
+++ b/src/applications/project/storage/PhabricatorProjectCustomFieldStorage.php
@@ -1,11 +1,10 @@
<?php
final class PhabricatorProjectCustomFieldStorage
extends PhabricatorCustomFieldStorage {
public function getApplicationName() {
return 'project';
}
}
-
diff --git a/src/applications/project/storage/PhabricatorProjectCustomFieldStringIndex.php b/src/applications/project/storage/PhabricatorProjectCustomFieldStringIndex.php
index f2441e695f..80280ffae6 100644
--- a/src/applications/project/storage/PhabricatorProjectCustomFieldStringIndex.php
+++ b/src/applications/project/storage/PhabricatorProjectCustomFieldStringIndex.php
@@ -1,11 +1,10 @@
<?php
final class PhabricatorProjectCustomFieldStringIndex
extends PhabricatorCustomFieldStringIndexStorage {
public function getApplicationName() {
return 'project';
}
}
-
diff --git a/src/applications/releeph/storage/ReleephBranchTransaction.php b/src/applications/releeph/storage/ReleephBranchTransaction.php
index d9805aff88..1b453c2583 100644
--- a/src/applications/releeph/storage/ReleephBranchTransaction.php
+++ b/src/applications/releeph/storage/ReleephBranchTransaction.php
@@ -1,15 +1,14 @@
<?php
final class ReleephBranchTransaction
extends PhabricatorApplicationTransaction {
public function getApplicationName() {
return 'releeph';
}
public function getApplicationTransactionType() {
return ReleephPHIDTypeBranch::TYPECONST;
}
}
-
diff --git a/src/applications/releeph/storage/ReleephProjectTransaction.php b/src/applications/releeph/storage/ReleephProjectTransaction.php
index fc9fd0c5c6..800d7e3569 100644
--- a/src/applications/releeph/storage/ReleephProjectTransaction.php
+++ b/src/applications/releeph/storage/ReleephProjectTransaction.php
@@ -1,15 +1,14 @@
<?php
final class ReleephProjectTransaction
extends PhabricatorApplicationTransaction {
public function getApplicationName() {
return 'releeph';
}
public function getApplicationTransactionType() {
return ReleephPHIDTypeProject::TYPECONST;
}
}
-
diff --git a/src/applications/repository/search/PhabricatorRepositoryCommitSearchIndexer.php b/src/applications/repository/search/PhabricatorRepositoryCommitSearchIndexer.php
index 7147b37a66..b76b49fc9e 100644
--- a/src/applications/repository/search/PhabricatorRepositoryCommitSearchIndexer.php
+++ b/src/applications/repository/search/PhabricatorRepositoryCommitSearchIndexer.php
@@ -1,83 +1,82 @@
<?php
final class PhabricatorRepositoryCommitSearchIndexer
extends PhabricatorSearchDocumentIndexer {
public function getIndexableObject() {
return new PhabricatorRepositoryCommit();
}
protected function buildAbstractDocumentByPHID($phid) {
$commit = $this->loadDocumentByPHID($phid);
$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 PhabricatorRepositoryQuery())
->setViewer($this->getViewer())
->withIDs(array($commit->getRepositoryID()))
->executeOne();
if (!$repository) {
throw new Exception("No such repository!");
}
$title = 'r'.$repository->getCallsign().$commit->getCommitIdentifier().
" ".$commit_data->getSummary();
$doc = new PhabricatorSearchAbstractDocument();
$doc->setPHID($commit->getPHID());
$doc->setDocumentType(PhabricatorRepositoryPHIDTypeCommit::TYPECONST);
$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,
PhabricatorPeoplePHIDTypeUser::TYPECONST,
$date_created);
}
$project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$commit->getPHID(),
PhabricatorEdgeConfig::TYPE_COMMIT_HAS_PROJECT);
if ($project_phids) {
foreach ($project_phids as $project_phid) {
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_PROJECT,
$project_phid,
PhabricatorProjectPHIDTypeProject::TYPECONST,
$date_created);
}
}
$doc->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY,
$repository->getPHID(),
PhabricatorRepositoryPHIDTypeRepository::TYPECONST,
$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());
}
}
return $doc;
}
}
-
diff --git a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php
index 446dcb19e6..1cec405290 100644
--- a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php
+++ b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php
@@ -1,391 +1,390 @@
<?php
final class PhabricatorRepositoryTransaction
extends PhabricatorApplicationTransaction {
const TYPE_VCS = 'repo:vcs';
const TYPE_ACTIVATE = 'repo:activate';
const TYPE_NAME = 'repo:name';
const TYPE_DESCRIPTION = 'repo:description';
const TYPE_ENCODING = 'repo:encoding';
const TYPE_DEFAULT_BRANCH = 'repo:default-branch';
const TYPE_TRACK_ONLY = 'repo:track-only';
const TYPE_AUTOCLOSE_ONLY = 'repo:autoclose-only';
const TYPE_SVN_SUBPATH = 'repo:svn-subpath';
const TYPE_UUID = 'repo:uuid';
const TYPE_NOTIFY = 'repo:notify';
const TYPE_AUTOCLOSE = 'repo:autoclose';
const TYPE_REMOTE_URI = 'repo:remote-uri';
const TYPE_LOCAL_PATH = 'repo:local-path';
const TYPE_HOSTING = 'repo:hosting';
const TYPE_PROTOCOL_HTTP = 'repo:serve-http';
const TYPE_PROTOCOL_SSH = 'repo:serve-ssh';
const TYPE_PUSH_POLICY = 'repo:push-policy';
const TYPE_CREDENTIAL = 'repo:credential';
const TYPE_DANGEROUS = 'repo:dangerous';
const TYPE_CLONE_NAME = 'repo:clone-name';
// TODO: Clean up these legacy transaction types.
const TYPE_SSH_LOGIN = 'repo:ssh-login';
const TYPE_SSH_KEY = 'repo:ssh-key';
const TYPE_SSH_KEYFILE = 'repo:ssh-keyfile';
const TYPE_HTTP_LOGIN = 'repo:http-login';
const TYPE_HTTP_PASS = 'repo:http-pass';
public function getApplicationName() {
return 'repository';
}
public function getApplicationTransactionType() {
return PhabricatorRepositoryPHIDTypeRepository::TYPECONST;
}
public function getApplicationTransactionCommentObject() {
return null;
}
public function getRequiredHandlePHIDs() {
$phids = parent::getRequiredHandlePHIDs();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_PUSH_POLICY:
$phids[] = $old;
$phids[] = $new;
break;
}
return $phids;
}
public function shouldHide() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_REMOTE_URI:
case self::TYPE_SSH_LOGIN:
case self::TYPE_SSH_KEY:
case self::TYPE_SSH_KEYFILE:
case self::TYPE_HTTP_LOGIN:
case self::TYPE_HTTP_PASS:
// Hide null vs empty string changes.
return (!strlen($old) && !strlen($new));
case self::TYPE_LOCAL_PATH:
case self::TYPE_NAME:
// Hide these on create, they aren't interesting and we have an
// explicit "create" transaction.
if (!strlen($old)) {
return true;
}
break;
}
return parent::shouldHide();
}
public function getIcon() {
switch ($this->getTransactionType()) {
case self::TYPE_VCS:
return 'create';
}
return parent::getIcon();
}
public function getColor() {
switch ($this->getTransactionType()) {
case self::TYPE_VCS:
return 'green';
}
return parent::getIcon();
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_VCS:
return pht(
'%s created this repository.',
$this->renderHandleLink($author_phid));
case self::TYPE_ACTIVATE:
if ($new) {
return pht(
'%s activated this repository.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s deactivated this repository.',
$this->renderHandleLink($author_phid));
}
case self::TYPE_NAME:
return pht(
'%s renamed this repository from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$old,
$new);
case self::TYPE_DESCRIPTION:
return pht(
'%s updated the description of this repository.',
$this->renderHandleLink($author_phid));
case self::TYPE_ENCODING:
if (strlen($old) && !strlen($new)) {
return pht(
'%s removed the "%s" encoding configured for this repository.',
$this->renderHandleLink($author_phid),
$old);
} else if (strlen($new) && !strlen($old)) {
return pht(
'%s set the encoding for this repository to "%s".',
$this->renderHandleLink($author_phid),
$new);
} else {
return pht(
'%s changed the repository encoding from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$old,
$new);
}
case self::TYPE_DEFAULT_BRANCH:
if (!strlen($new)) {
return pht(
'%s removed "%s" as the default branch.',
$this->renderHandleLink($author_phid),
$old);
} else if (!strlen($old)) {
return pht(
'%s set the default branch to "%s".',
$this->renderHandleLink($author_phid),
$new);
} else {
return pht(
'%s changed the default branch from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$old,
$new);
}
break;
case self::TYPE_TRACK_ONLY:
if (!$new) {
return pht(
'%s set this repository to track all branches.',
$this->renderHandleLink($author_phid));
} else if (!$old) {
return pht(
'%s set this repository to track branches: %s.',
$this->renderHandleLink($author_phid),
implode(', ', $new));
} else {
return pht(
'%s changed track branches from "%s" to "%s".',
$this->renderHandleLink($author_phid),
implode(', ', $old),
implode(', ', $new));
}
break;
case self::TYPE_AUTOCLOSE_ONLY:
if (!$new) {
return pht(
'%s set this repository to autoclose on all branches.',
$this->renderHandleLink($author_phid));
} else if (!$old) {
return pht(
'%s set this repository to autoclose on branches: %s.',
$this->renderHandleLink($author_phid),
implode(', ', $new));
} else {
return pht(
'%s changed autoclose branches from "%s" to "%s".',
$this->renderHandleLink($author_phid),
implode(', ', $old),
implode(', ', $new));
}
break;
case self::TYPE_UUID:
if (!strlen($new)) {
return pht(
'%s removed "%s" as the repository UUID.',
$this->renderHandleLink($author_phid),
$old);
} else if (!strlen($old)) {
return pht(
'%s set the repository UUID to "%s".',
$this->renderHandleLink($author_phid),
$new);
} else {
return pht(
'%s changed the repository UUID from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$old,
$new);
}
break;
case self::TYPE_SVN_SUBPATH:
if (!strlen($new)) {
return pht(
'%s removed "%s" as the Import Only path.',
$this->renderHandleLink($author_phid),
$old);
} else if (!strlen($old)) {
return pht(
'%s set the repository to import only "%s".',
$this->renderHandleLink($author_phid),
$new);
} else {
return pht(
'%s changed the import path from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$old,
$new);
}
break;
case self::TYPE_NOTIFY:
if ($new) {
return pht(
'%s enabled notifications and publishing for this repository.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s disabled notifications and publishing for this repository.',
$this->renderHandleLink($author_phid));
}
break;
case self::TYPE_AUTOCLOSE:
if ($new) {
return pht(
'%s enabled autoclose for this repository.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s disabled autoclose for this repository.',
$this->renderHandleLink($author_phid));
}
break;
case self::TYPE_REMOTE_URI:
if (!strlen($old)) {
return pht(
'%s set the remote URI for this repository to "%s".',
$this->renderHandleLink($author_phid),
$new);
} else if (!strlen($new)) {
return pht(
'%s removed the remote URI for this repository.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s changed the remote URI for this repository from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$old,
$new);
}
break;
case self::TYPE_SSH_LOGIN:
return pht(
'%s updated the SSH login for this repository.',
$this->renderHandleLink($author_phid));
case self::TYPE_SSH_KEY:
return pht(
'%s updated the SSH key for this repository.',
$this->renderHandleLink($author_phid));
case self::TYPE_SSH_KEYFILE:
return pht(
'%s updated the SSH keyfile for this repository.',
$this->renderHandleLink($author_phid));
case self::TYPE_HTTP_LOGIN:
return pht(
'%s updated the HTTP login for this repository.',
$this->renderHandleLink($author_phid));
case self::TYPE_HTTP_PASS:
return pht(
'%s updated the HTTP password for this repository.',
$this->renderHandleLink($author_phid));
case self::TYPE_LOCAL_PATH:
return pht(
'%s changed the local path from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$old,
$new);
case self::TYPE_HOSTING:
if ($new) {
return pht(
'%s changed this repository to be hosted on Phabricator.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s changed this repository to track a remote elsewhere.',
$this->renderHandleLink($author_phid));
}
case self::TYPE_PROTOCOL_HTTP:
return pht(
'%s changed the availability of this repository over HTTP from '.
'"%s" to "%s".',
$this->renderHandleLink($author_phid),
PhabricatorRepository::getProtocolAvailabilityName($old),
PhabricatorRepository::getProtocolAvailabilityName($new));
case self::TYPE_PROTOCOL_SSH:
return pht(
'%s changed the availability of this repository over SSH from '.
'"%s" to "%s".',
$this->renderHandleLink($author_phid),
PhabricatorRepository::getProtocolAvailabilityName($old),
PhabricatorRepository::getProtocolAvailabilityName($new));
case self::TYPE_PUSH_POLICY:
return pht(
'%s changed the push policy of this repository from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$this->renderPolicyName($old),
$this->renderPolicyName($new));
case self::TYPE_DANGEROUS:
if ($new) {
return pht(
'%s disabled protection against dangerous changes.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s enabled protection against dangerous changes.',
$this->renderHandleLink($author_phid));
}
case self::TYPE_CLONE_NAME:
if (strlen($old) && !strlen($new)) {
return pht(
'%s removed the clone name of this repository.',
$this->renderHandleLink($author_phid));
} else if (strlen($new) && !strlen($old)) {
return pht(
'%s set the clone name of this repository to "%s".',
$this->renderHandleLink($author_phid),
$new);
} else {
return pht(
'%s changed the clone name of this repository from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$old,
$new);
}
}
return parent::getTitle();
}
public function hasChangeDetails() {
switch ($this->getTransactionType()) {
case self::TYPE_DESCRIPTION:
return true;
}
return parent::hasChangeDetails();
}
public function renderChangeDetails(PhabricatorUser $viewer) {
return $this->renderTextCorpusChangeDetails(
$viewer,
$this->getOldValue(),
$this->getNewValue());
}
}
-
diff --git a/src/applications/search/controller/PhabricatorSearchAttachController.php b/src/applications/search/controller/PhabricatorSearchAttachController.php
index b0b7bae84c..a2ec3b2fb1 100644
--- a/src/applications/search/controller/PhabricatorSearchAttachController.php
+++ b/src/applications/search/controller/PhabricatorSearchAttachController.php
@@ -1,337 +1,337 @@
<?php
/**
* @group search
*/
final class PhabricatorSearchAttachController
extends PhabricatorSearchBaseController {
private $phid;
private $type;
private $action;
const ACTION_ATTACH = 'attach';
const ACTION_MERGE = 'merge';
const ACTION_DEPENDENCIES = 'dependencies';
const ACTION_EDGE = 'edge';
public function willProcessRequest(array $data) {
$this->phid = $data['phid'];
$this->type = $data['type'];
$this->action = idx($data, 'action', self::ACTION_ATTACH);
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
- $handle = id(New PhabricatorHandleQuery())
+ $handle = id(new PhabricatorHandleQuery())
->setViewer($user)
->withPHIDs(array($this->phid))
->executeOne();
$object_type = $handle->getType();
$attach_type = $this->type;
$object = id(new PhabricatorObjectQuery())
->setViewer($user)
->withPHIDs(array($this->phid))
->executeOne();
if (!$object) {
return new Aphront404Response();
}
$edge_type = null;
switch ($this->action) {
case self::ACTION_EDGE:
case self::ACTION_DEPENDENCIES:
case self::ACTION_ATTACH:
$edge_type = $this->getEdgeType($object_type, $attach_type);
break;
}
if ($request->isFormPost()) {
$phids = explode(';', $request->getStr('phids'));
$phids = array_filter($phids);
$phids = array_values($phids);
if ($edge_type) {
$do_txn = $object instanceof PhabricatorApplicationTransactionInterface;
$old_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$this->phid,
$edge_type);
$add_phids = $phids;
$rem_phids = array_diff($old_phids, $add_phids);
if ($do_txn) {
$txn_editor = $object->getApplicationTransactionEditor()
->setActor($user)
->setContentSourceFromRequest($request);
$txn_template = $object->getApplicationTransactionObject()
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $edge_type)
->setNewValue(array(
'+' => array_fuse($add_phids),
'-' => array_fuse($rem_phids)));
$txn_editor->applyTransactions($object, array($txn_template));
} else {
$editor = id(new PhabricatorEdgeEditor());
$editor->setActor($user);
foreach ($add_phids as $phid) {
$editor->addEdge($this->phid, $edge_type, $phid);
}
foreach ($rem_phids as $phid) {
$editor->removeEdge($this->phid, $edge_type, $phid);
}
try {
$editor->save();
} catch (PhabricatorEdgeCycleException $ex) {
$this->raiseGraphCycleException($ex);
}
}
return id(new AphrontReloadResponse())->setURI($handle->getURI());
} else {
return $this->performMerge($object, $handle, $phids);
}
} else {
if ($edge_type) {
$phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$this->phid,
$edge_type);
} else {
// This is a merge.
$phids = array();
}
}
$strings = $this->getStrings();
$handles = $this->loadViewerHandles($phids);
$obj_dialog = new PhabricatorObjectSelectorDialog();
$obj_dialog
->setUser($user)
->setHandles($handles)
->setFilters($this->getFilters($strings))
->setSelectedFilter($strings['selected'])
->setExcluded($this->phid)
->setCancelURI($handle->getURI())
->setSearchURI('/search/select/'.$attach_type.'/')
->setTitle($strings['title'])
->setHeader($strings['header'])
->setButtonText($strings['button'])
->setInstructions($strings['instructions']);
$dialog = $obj_dialog->buildDialog();
return id(new AphrontDialogResponse())->setDialog($dialog);
}
private function performMerge(
ManiphestTask $task,
PhabricatorObjectHandle $handle,
array $phids) {
$user = $this->getRequest()->getUser();
$response = id(new AphrontReloadResponse())->setURI($handle->getURI());
$phids = array_fill_keys($phids, true);
unset($phids[$task->getPHID()]); // Prevent merging a task into itself.
if (!$phids) {
return $response;
}
$targets = id(new ManiphestTaskQuery())
->setViewer($user)
->withPHIDs(array_keys($phids))
->execute();
if (empty($targets)) {
return $response;
}
$editor = id(new ManiphestTransactionEditor())
->setActor($user)
->setContentSourceFromRequest($this->getRequest())
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$task_names = array();
$merge_into_name = 'T'.$task->getID();
$cc_vector = array();
$cc_vector[] = $task->getCCPHIDs();
foreach ($targets as $target) {
$cc_vector[] = $target->getCCPHIDs();
$cc_vector[] = array(
$target->getAuthorPHID(),
$target->getOwnerPHID());
$close_task = id(new ManiphestTransaction())
->setTransactionType(ManiphestTransaction::TYPE_STATUS)
->setNewValue(ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE);
$merge_comment = id(new ManiphestTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
->attachComment(
id(new ManiphestTransactionComment())
->setContent("\xE2\x9C\x98 Merged into {$merge_into_name}."));
$editor->applyTransactions(
$target,
array(
$close_task,
$merge_comment,
));
$task_names[] = 'T'.$target->getID();
}
$all_ccs = array_mergev($cc_vector);
$all_ccs = array_filter($all_ccs);
$all_ccs = array_unique($all_ccs);
$task_names = implode(', ', $task_names);
$add_ccs = id(new ManiphestTransaction())
->setTransactionType(ManiphestTransaction::TYPE_CCS)
->setNewValue($all_ccs);
$merged_comment = id(new ManiphestTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
->attachComment(
id(new ManiphestTransactionComment())
->setContent("\xE2\x97\x80 Merged tasks: {$task_names}."));
$editor->applyTransactions($task, array($add_ccs, $merged_comment));
return $response;
}
private function getStrings() {
switch ($this->type) {
case DifferentialPHIDTypeRevision::TYPECONST:
$noun = 'Revisions';
$selected = 'created';
break;
case ManiphestPHIDTypeTask::TYPECONST:
$noun = 'Tasks';
$selected = 'assigned';
break;
case PhabricatorRepositoryPHIDTypeCommit::TYPECONST:
$noun = 'Commits';
$selected = 'created';
break;
case PholioPHIDTypeMock::TYPECONST:
$noun = 'Mocks';
$selected = 'created';
break;
}
switch ($this->action) {
case self::ACTION_EDGE:
case self::ACTION_ATTACH:
$dialog_title = "Manage Attached {$noun}";
$header_text = "Currently Attached {$noun}";
$button_text = "Save {$noun}";
$instructions = null;
break;
case self::ACTION_MERGE:
$dialog_title = "Merge Duplicate Tasks";
$header_text = "Tasks To Merge";
$button_text = "Merge {$noun}";
$instructions =
"These tasks will be merged into the current task and then closed. ".
"The current task will grow stronger.";
break;
case self::ACTION_DEPENDENCIES:
$dialog_title = "Edit Dependencies";
$header_text = "Current Dependencies";
$button_text = "Save Dependencies";
$instructions = null;
break;
}
return array(
'target_plural_noun' => $noun,
'selected' => $selected,
'title' => $dialog_title,
'header' => $header_text,
'button' => $button_text,
'instructions' => $instructions,
);
}
private function getFilters(array $strings) {
if ($this->type == PholioPHIDTypeMock::TYPECONST) {
$filters = array(
'created' => 'Created By Me',
'all' => 'All '.$strings['target_plural_noun'],
);
} else {
$filters = array(
'assigned' => 'Assigned to Me',
'created' => 'Created By Me',
'open' => 'All Open '.$strings['target_plural_noun'],
'all' => 'All '.$strings['target_plural_noun'],
);
}
return $filters;
}
private function getEdgeType($src_type, $dst_type) {
$t_cmit = PhabricatorRepositoryPHIDTypeCommit::TYPECONST;
$t_task = ManiphestPHIDTypeTask::TYPECONST;
$t_drev = DifferentialPHIDTypeRevision::TYPECONST;
$t_mock = PholioPHIDTypeMock::TYPECONST;
$map = array(
$t_cmit => array(
$t_task => PhabricatorEdgeConfig::TYPE_COMMIT_HAS_TASK,
),
$t_task => array(
$t_cmit => PhabricatorEdgeConfig::TYPE_TASK_HAS_COMMIT,
$t_task => PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK,
$t_drev => PhabricatorEdgeConfig::TYPE_TASK_HAS_RELATED_DREV,
$t_mock => PhabricatorEdgeConfig::TYPE_TASK_HAS_MOCK,
),
$t_drev => array(
$t_drev => PhabricatorEdgeConfig::TYPE_DREV_DEPENDS_ON_DREV,
$t_task => PhabricatorEdgeConfig::TYPE_DREV_HAS_RELATED_TASK,
),
$t_mock => array(
$t_task => PhabricatorEdgeConfig::TYPE_MOCK_HAS_TASK,
),
);
if (empty($map[$src_type][$dst_type])) {
return null;
}
return $map[$src_type][$dst_type];
}
private function raiseGraphCycleException(PhabricatorEdgeCycleException $ex) {
$cycle = $ex->getCycle();
$handles = $this->loadViewerHandles($cycle);
$names = array();
foreach ($cycle as $cycle_phid) {
$names[] = $handles[$cycle_phid]->getFullName();
}
$names = implode(" \xE2\x86\x92 ", $names);
throw new Exception(
"You can not create that dependency, because it would create a ".
"circular dependency: {$names}.");
}
}
diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelConpherencePreferences.php b/src/applications/settings/panel/PhabricatorSettingsPanelConpherencePreferences.php
index 95c19d9d5e..2085a3eb5c 100644
--- a/src/applications/settings/panel/PhabricatorSettingsPanelConpherencePreferences.php
+++ b/src/applications/settings/panel/PhabricatorSettingsPanelConpherencePreferences.php
@@ -1,69 +1,68 @@
<?php
final class PhabricatorSettingsPanelConpherencePreferences
extends PhabricatorSettingsPanel {
public function isEnabled() {
return PhabricatorApplication::isClassInstalled(
'PhabricatorApplicationConpherence');
}
public function getPanelKey() {
return 'conpherence';
}
public function getPanelName() {
return pht('Conpherence Preferences');
}
public function getPanelGroup() {
return pht('Application Settings');
}
public function processRequest(AphrontRequest $request) {
$user = $request->getUser();
$preferences = $user->loadPreferences();
$pref = PhabricatorUserPreferences::PREFERENCE_CONPH_NOTIFICATIONS;
if ($request->isFormPost()) {
$notifications = $request->getInt($pref);
$preferences->setPreference($pref, $notifications);
$preferences->save();
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI('?saved=true'));
}
$form = id(new AphrontFormView())
->setUser($user)
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Conpherence Notifications'))
->setName($pref)
->setValue($preferences->getPreference($pref))
->setOptions(
array(
ConpherenceSettings::EMAIL_ALWAYS
=> pht('Email Always'),
ConpherenceSettings::NOTIFICATIONS_ONLY
=> pht('Notifications Only'),
))
->setCaption(
pht('Should Conpherence send emails for updates or '.
'notifications only? This global setting can be overridden '.
'on a per-thread basis within Conpherence.')))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save Preferences')));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Conpherence Preferences'))
->setForm($form)
->setFormSaved($request->getBool('saved'));
return array(
$form_box,
);
}
}
-
diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelDeveloperPreferences.php b/src/applications/settings/panel/PhabricatorSettingsPanelDeveloperPreferences.php
index 3c23cc05a8..e5ba13fdbe 100644
--- a/src/applications/settings/panel/PhabricatorSettingsPanelDeveloperPreferences.php
+++ b/src/applications/settings/panel/PhabricatorSettingsPanelDeveloperPreferences.php
@@ -1,98 +1,97 @@
<?php
final class PhabricatorSettingsPanelDeveloperPreferences
extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'developer';
}
public function getPanelName() {
return pht('Developer Settings');
}
public function getPanelGroup() {
return pht('Developer');
}
public function processRequest(AphrontRequest $request) {
$user = $request->getUser();
$preferences = $user->loadPreferences();
$pref_dark_console = PhabricatorUserPreferences::PREFERENCE_DARK_CONSOLE;
$dark_console_value = $preferences->getPreference($pref_dark_console);
if ($request->isFormPost()) {
$new_dark_console = $request->getBool($pref_dark_console);
$preferences->setPreference($pref_dark_console, $new_dark_console);
// If the user turned Dark Console on, enable it (as though they had hit
// "`").
if ($new_dark_console && !$dark_console_value) {
$user->setConsoleVisible(true);
$user->save();
}
$preferences->save();
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI('?saved=true'));
}
$is_console_enabled = PhabricatorEnv::getEnvConfig('darkconsole.enabled');
$preamble = pht(
'**DarkConsole** is a developer console which can help build and '.
'debug Phabricator applications. It includes tools for understanding '.
'errors, performance, service calls, and other low-level aspects of '.
'Phabricator\'s inner workings.');
if ($is_console_enabled) {
$instructions = pht(
"%s\n\n".
'You can enable it for your account below. Enabling DarkConsole will '.
'slightly decrease performance, but give you access to debugging '.
'tools. You may want to disable it again later if you only need it '.
'temporarily.'.
"\n\n".
'NOTE: After enabling DarkConsole, **press the ##`## key on your '.
'keyboard** to show or hide it.',
$preamble);
} else {
$instructions = pht(
"%s\n\n".
'Before you can turn on DarkConsole, it needs to be enabled in '.
'the configuration for this install (`darkconsole.enabled`).',
$preamble);
}
$form = id(new AphrontFormView())
->setUser($user)
->appendRemarkupInstructions($instructions)
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Dark Console'))
->setName($pref_dark_console)
->setValue($dark_console_value)
->setOptions(
array(
0 => pht('Disable DarkConsole'),
1 => pht('Enable DarkConsole'),
))
->setDisabled(!$is_console_enabled))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save Preferences')));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Developer Settings'))
->setFormSaved($request->getBool('saved'))
->setForm($form);
return array(
$form_box,
);
}
}
-
diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelDiffPreferences.php b/src/applications/settings/panel/PhabricatorSettingsPanelDiffPreferences.php
index 26115e3e77..16ba31bd3d 100644
--- a/src/applications/settings/panel/PhabricatorSettingsPanelDiffPreferences.php
+++ b/src/applications/settings/panel/PhabricatorSettingsPanelDiffPreferences.php
@@ -1,71 +1,70 @@
<?php
final class PhabricatorSettingsPanelDiffPreferences
extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'diff';
}
public function getPanelName() {
return pht('Diff Preferences');
}
public function getPanelGroup() {
return pht('Application Settings');
}
public function processRequest(AphrontRequest $request) {
$user = $request->getUser();
$preferences = $user->loadPreferences();
$pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE;
if ($request->isFormPost()) {
$filetree = $request->getInt($pref_filetree);
if ($filetree && !$preferences->getPreference($pref_filetree)) {
$preferences->setPreference(
PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED,
false);
}
$preferences->setPreference($pref_filetree, $filetree);
$preferences->save();
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI('?saved=true'));
}
$form = id(new AphrontFormView())
->setUser($user)
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Show Filetree'))
->setName($pref_filetree)
->setValue($preferences->getPreference($pref_filetree))
->setOptions(
array(
0 => pht('Disable Filetree'),
1 => pht('Enable Filetree'),
))
->setCaption(
pht("When looking at a revision or commit, enable a sidebar ".
"showing affected files. You can press %s to show or hide ".
"the sidebar.",
phutil_tag('tt', array(), 'f'))))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save Preferences')));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Diff Preferences'))
->setFormSaved($request->getBool('saved'))
->setForm($form);
return array(
$form_box,
);
}
}
-
diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelDisplayPreferences.php b/src/applications/settings/panel/PhabricatorSettingsPanelDisplayPreferences.php
index c97f9d6046..d03a73b423 100644
--- a/src/applications/settings/panel/PhabricatorSettingsPanelDisplayPreferences.php
+++ b/src/applications/settings/panel/PhabricatorSettingsPanelDisplayPreferences.php
@@ -1,150 +1,149 @@
<?php
final class PhabricatorSettingsPanelDisplayPreferences
extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'display';
}
public function getPanelName() {
return pht('Display Preferences');
}
public function getPanelGroup() {
return pht('Application Settings');
}
public function processRequest(AphrontRequest $request) {
$user = $request->getUser();
$preferences = $user->loadPreferences();
$pref_monospaced = PhabricatorUserPreferences::PREFERENCE_MONOSPACED;
$pref_editor = PhabricatorUserPreferences::PREFERENCE_EDITOR;
$pref_multiedit = PhabricatorUserPreferences::PREFERENCE_MULTIEDIT;
$pref_titles = PhabricatorUserPreferences::PREFERENCE_TITLES;
$pref_monospaced_textareas =
PhabricatorUserPreferences::PREFERENCE_MONOSPACED_TEXTAREAS;
if ($request->isFormPost()) {
$monospaced = $request->getStr($pref_monospaced);
// Prevent the user from doing stupid things.
$monospaced = preg_replace('/[^a-z0-9 ,"]+/i', '', $monospaced);
$preferences->setPreference($pref_titles, $request->getStr($pref_titles));
$preferences->setPreference($pref_editor, $request->getStr($pref_editor));
$preferences->setPreference(
$pref_multiedit,
$request->getStr($pref_multiedit));
$preferences->setPreference($pref_monospaced, $monospaced);
$preferences->setPreference(
$pref_monospaced_textareas,
$request->getStr($pref_monospaced_textareas));
$preferences->save();
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI('?saved=true'));
}
$example_string = <<<EXAMPLE
// This is what your monospaced font currently looks like.
function helloWorld() {
alert("Hello world!");
}
EXAMPLE;
$editor_doc_link = phutil_tag(
'a',
array(
'href' => PhabricatorEnv::getDoclink(
'article/User_Guide_Configuring_an_External_Editor.html'),
),
pht('User Guide: Configuring an External Editor'));
$font_default = PhabricatorEnv::getEnvConfig('style.monospace');
$pref_monospaced_textareas_value = $preferences
->getPreference($pref_monospaced_textareas);
if (!$pref_monospaced_textareas_value) {
$pref_monospaced_textareas_value = 'disabled';
}
$editor_instructions = pht('Link to edit files in external editor. '.
'%%f is replaced by filename, %%l by line number, %%r by repository '.
'callsign, %%%% by literal %%. For documentation, see: %s',
$editor_doc_link);
$form = id(new AphrontFormView())
->setUser($user)
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Page Titles'))
->setName($pref_titles)
->setValue($preferences->getPreference($pref_titles))
->setOptions(
array(
'glyph' =>
pht("In page titles, show Tool names as unicode glyphs: " .
"\xE2\x9A\x99"),
'text' =>
pht('In page titles, show Tool names as plain text: ' .
'[Differential]'),
)))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Editor Link'))
->setName($pref_editor)
// How to pht()
->setCaption($editor_instructions)
->setValue($preferences->getPreference($pref_editor)))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Edit Multiple Files'))
->setName($pref_multiedit)
->setOptions(array(
'' => pht('Supported (paths separated by spaces)'),
'disable' => pht('Not Supported'),
))
->setValue($preferences->getPreference($pref_multiedit)))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Monospaced Font'))
->setName($pref_monospaced)
// Check plz
->setCaption(hsprintf(
'%s<br />(%s: %s)',
pht('Overrides default fonts in tools like Differential.'),
pht('Default'),
$font_default))
->setValue($preferences->getPreference($pref_monospaced)))
->appendChild(
id(new AphrontFormMarkupControl())
->setValue(phutil_tag(
'pre',
array('class' => 'PhabricatorMonospaced'),
$example_string)))
->appendChild(
id(new AphrontFormRadioButtonControl())
->setLabel(pht('Monospaced Textareas'))
->setName($pref_monospaced_textareas)
->setValue($pref_monospaced_textareas_value)
->addButton('enabled', pht('Enabled'),
pht('Show all textareas using the monospaced font defined above.'))
->addButton('disabled', pht('Disabled'), null));
$form->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save Preferences')));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Display Preferences'))
->setFormSaved($request->getStr('saved') === 'true')
->setForm($form);
return array(
$form_box,
);
}
}
-
diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelHomePreferences.php b/src/applications/settings/panel/PhabricatorSettingsPanelHomePreferences.php
index 82a7b13a55..276463861f 100644
--- a/src/applications/settings/panel/PhabricatorSettingsPanelHomePreferences.php
+++ b/src/applications/settings/panel/PhabricatorSettingsPanelHomePreferences.php
@@ -1,219 +1,218 @@
<?php
final class PhabricatorSettingsPanelHomePreferences
extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'home';
}
public function getPanelName() {
return pht('Home Page');
}
public function getPanelGroup() {
return pht('Application Settings');
}
public function processRequest(AphrontRequest $request) {
$user = $request->getUser();
$preferences = $user->loadPreferences();
require_celerity_resource('phabricator-settings-css');
$apps = id(new PhabricatorApplicationQuery())
->setViewer($user)
->withUnlisted(false)
->execute();
$pref_tiles = PhabricatorUserPreferences::PREFERENCE_APP_TILES;
$tiles = $preferences->getPreference($pref_tiles, array());
if ($request->isFormPost()) {
$values = $request->getArr('tile');
foreach ($apps as $app) {
$key = get_class($app);
$value = idx($values, $key);
switch ($value) {
case PhabricatorApplication::TILE_FULL:
case PhabricatorApplication::TILE_SHOW:
case PhabricatorApplication::TILE_HIDE:
$tiles[$key] = $value;
break;
default:
unset($tiles[$key]);
break;
}
}
$preferences->setPreference($pref_tiles, $tiles);
$preferences->save();
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI('?saved=true'));
}
$form = id(new AphrontFormView())
->setUser($user);
$group_map = PhabricatorApplication::getApplicationGroups();
$output = array();
$applications = PhabricatorApplication::getAllInstalledApplications();
$applications = mgroup($applications, 'getApplicationGroup');
$applications = array_select_keys(
$applications,
array_keys($group_map));
foreach ($applications as $group => $apps) {
$group_name = $group_map[$group];
$rows = array();
foreach ($apps as $app) {
if (!$app->shouldAppearInLaunchView()) {
continue;
}
$default = $app->getDefaultTileDisplay($user);
if ($default == PhabricatorApplication::TILE_INVISIBLE) {
continue;
}
$default_name = PhabricatorApplication::getTileDisplayName($default);
$hide = PhabricatorApplication::TILE_HIDE;
$show = PhabricatorApplication::TILE_SHOW;
$full = PhabricatorApplication::TILE_FULL;
$key = get_class($app);
$default_radio_button_status =
(idx($tiles, $key, 'default') == 'default') ? 'checked' : null;
$hide_radio_button_status =
(idx($tiles, $key, 'default') == $hide) ? 'checked' : null;
$show_radio_button_status =
(idx($tiles, $key, 'default') == $show) ? 'checked' : null;
$full_radio_button_status =
(idx($tiles, $key, 'default') == $full) ? 'checked' : null;
$default_radio_button = phutil_tag(
'input',
array(
'type' => 'radio',
'name' => 'tile['.$key.']',
'value' => 'default',
'checked' => $default_radio_button_status,
));
$hide_radio_button = phutil_tag(
'input',
array(
'type' => 'radio',
'name' => 'tile['.$key.']',
'value' => $hide,
'checked' => $hide_radio_button_status,
));
$show_radio_button = phutil_tag(
'input',
array(
'type' => 'radio',
'name' => 'tile['.$key.']',
'value' => $show,
'checked' => $show_radio_button_status,
));
$full_radio_button = phutil_tag(
'input',
array(
'type' => 'radio',
'name' => 'tile['.$key.']',
'value' => $full,
'checked' => $full_radio_button_status,
));
$desc = $app->getShortDescription();
$app_column = hsprintf(
"<strong>%s</strong><br/ >%s, <em>Default: %s</em>",
$app->getName(), $desc, $default_name);
$rows[] = array(
$app_column,
$default_radio_button,
$hide_radio_button,
$show_radio_button,
$full_radio_button,
);
}
if (empty($rows)) {
continue;
}
$table = new AphrontTableView($rows);
$table
->setClassName('phabricator-settings-homepagetable')
->setHeaders(
array(
pht('Applications'),
pht('Default'),
pht('Hidden'),
pht('Small'),
pht('Large'),
))
->setColumnClasses(
array(
'',
'fixed',
'fixed',
'fixed',
'fixed',
));
$panel = id(new PHUIObjectBoxView())
->setHeaderText($group_name)
->appendChild($table);
$output[] = $panel;
}
$save_button =
id(new AphrontFormSubmitControl())
->setValue(pht('Save Preferences'));
$output[] = id(new PHUIBoxView())
->addPadding(PHUI::PADDING_LARGE)
->addClass('phabricator-settings-homepagetable-button')
->appendChild($save_button);
$form->appendChild($output);
$error_view = null;
if ($request->getStr('saved') === 'true') {
$error_view = id(new AphrontErrorView())
->setTitle(pht('Preferences Saved'))
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setErrors(array(pht('Your preferences have been saved.')));
}
$header = id(new PHUIHeaderView())
->setHeader(pht('Home Page Preferences'));
$form = id(new PHUIBoxView())
->addClass('phabricator-settings-homepagetable-wrap')
->appendChild($form);
return array($header, $error_view, $form);
}
}
-
diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelSearchPreferences.php b/src/applications/settings/panel/PhabricatorSettingsPanelSearchPreferences.php
index 4330a6dc93..0c77448895 100644
--- a/src/applications/settings/panel/PhabricatorSettingsPanelSearchPreferences.php
+++ b/src/applications/settings/panel/PhabricatorSettingsPanelSearchPreferences.php
@@ -1,63 +1,62 @@
<?php
final class PhabricatorSettingsPanelSearchPreferences
extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'search';
}
public function getPanelName() {
return pht('Search Preferences');
}
public function getPanelGroup() {
return pht('Application Settings');
}
public function processRequest(AphrontRequest $request) {
$user = $request->getUser();
$preferences = $user->loadPreferences();
$pref_jump = PhabricatorUserPreferences::PREFERENCE_SEARCHBAR_JUMP;
$pref_shortcut = PhabricatorUserPreferences::PREFERENCE_SEARCH_SHORTCUT;
if ($request->isFormPost()) {
$preferences->setPreference($pref_jump,
$request->getBool($pref_jump));
$preferences->setPreference($pref_shortcut,
$request->getBool($pref_shortcut));
$preferences->save();
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI('?saved=true'));
}
$form = id(new AphrontFormView())
->setUser($user)
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox($pref_jump,
1,
pht('Enable jump nav functionality in all search boxes.'),
$preferences->getPreference($pref_jump, 1))
->addCheckbox($pref_shortcut,
1,
pht("Press '/' to focus the search input."),
$preferences->getPreference($pref_shortcut, 1)))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save')));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Search Preferences'))
->setFormSaved($request->getStr('saved') === 'true')
->setForm($form);
return array(
$form_box,
);
}
}
-
diff --git a/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php b/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php
index 9370ea361a..9769767bbb 100644
--- a/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php
+++ b/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php
@@ -1,127 +1,126 @@
<?php
final class PhabricatorSlowvoteTransaction
extends PhabricatorApplicationTransaction {
const TYPE_QUESTION = 'vote:question';
const TYPE_DESCRIPTION = 'vote:description';
const TYPE_RESPONSES = 'vote:responses';
const TYPE_SHUFFLE = 'vote:shuffle';
public function getApplicationName() {
return 'slowvote';
}
public function getApplicationTransactionType() {
return PhabricatorSlowvotePHIDTypePoll::TYPECONST;
}
public function getApplicationTransactionCommentObject() {
return new PhabricatorSlowvoteTransactionComment();
}
public function shouldHide() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION:
case PhabricatorSlowvoteTransaction::TYPE_RESPONSES:
case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE:
return ($old === null);
}
return parent::shouldHide();
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case PhabricatorSlowvoteTransaction::TYPE_QUESTION:
if ($old === null) {
return pht(
'%s created this poll.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s changed the poll question from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$old,
$new);
}
break;
case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION:
return pht(
'%s updated the description for this poll.',
$this->renderHandleLink($author_phid));
case PhabricatorSlowvoteTransaction::TYPE_RESPONSES:
// TODO: This could be more detailed
return pht(
'%s changed who can see the responses.',
$this->renderHandleLink($author_phid));
case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE:
if ($new) {
return pht(
'%s made poll responses appear in a random order.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s made poll responses appear in a fixed order.',
$this->renderHandleLink($author_phid));
}
break;
}
return parent::getTitle();
}
public function getIcon() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case PhabricatorSlowvoteTransaction::TYPE_QUESTION:
case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION:
case PhabricatorSlowvoteTransaction::TYPE_RESPONSES:
case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE:
return 'edit';
}
return parent::getIcon();
}
public function getColor() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case PhabricatorSlowvoteTransaction::TYPE_QUESTION:
case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION:
case PhabricatorSlowvoteTransaction::TYPE_RESPONSES:
case PhabricatorSlowvoteTransaction::TYPE_SHUFFLE:
return PhabricatorTransactions::COLOR_BLUE;
}
return parent::getColor();
}
public function hasChangeDetails() {
switch ($this->getTransactionType()) {
case PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION:
return true;
}
return parent::hasChangeDetails();
}
public function renderChangeDetails(PhabricatorUser $viewer) {
return $this->renderTextCorpusChangeDetails($viewer);
}
}
-
diff --git a/src/applications/slowvote/storage/PhabricatorSlowvoteTransactionComment.php b/src/applications/slowvote/storage/PhabricatorSlowvoteTransactionComment.php
index 876ee015de..0dc033e2fd 100644
--- a/src/applications/slowvote/storage/PhabricatorSlowvoteTransactionComment.php
+++ b/src/applications/slowvote/storage/PhabricatorSlowvoteTransactionComment.php
@@ -1,11 +1,10 @@
<?php
final class PhabricatorSlowvoteTransactionComment
extends PhabricatorApplicationTransactionComment {
public function getApplicationTransactionObject() {
return new PhabricatorSlowvoteTransaction();
}
}
-
diff --git a/src/applications/subscriptions/application/PhabricatorApplicationSubscriptions.php b/src/applications/subscriptions/application/PhabricatorApplicationSubscriptions.php
index ad4b4eea15..e569be0d8f 100644
--- a/src/applications/subscriptions/application/PhabricatorApplicationSubscriptions.php
+++ b/src/applications/subscriptions/application/PhabricatorApplicationSubscriptions.php
@@ -1,29 +1,28 @@
<?php
final class PhabricatorApplicationSubscriptions extends PhabricatorApplication {
public function shouldAppearInLaunchView() {
return false;
}
public function canUninstall() {
return false;
}
public function getEventListeners() {
return array(
new PhabricatorSubscriptionsUIEventListener(),
);
}
public function getRoutes() {
return array(
'/subscriptions/' => array(
'(?P<action>add|delete)/'.
'(?P<phid>[^/]+)/' => 'PhabricatorSubscriptionsEditController',
),
);
}
}
-
diff --git a/src/applications/transactions/application/PhabricatorApplicationTransactions.php b/src/applications/transactions/application/PhabricatorApplicationTransactions.php
index 96d76ca0e0..e91cb3dc80 100644
--- a/src/applications/transactions/application/PhabricatorApplicationTransactions.php
+++ b/src/applications/transactions/application/PhabricatorApplicationTransactions.php
@@ -1,27 +1,26 @@
<?php
final class PhabricatorApplicationTransactions extends PhabricatorApplication {
public function shouldAppearInLaunchView() {
return false;
}
public function canUninstall() {
return false;
}
public function getRoutes() {
return array(
'/transactions/' => array(
'edit/(?<phid>[^/]+)/'
=> 'PhabricatorApplicationTransactionCommentEditController',
'history/(?<phid>[^/]+)/'
=> 'PhabricatorApplicationTransactionCommentHistoryController',
'detail/(?<phid>[^/]+)/'
=> 'PhabricatorApplicationTransactionDetailController',
),
);
}
}
-
diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
index 3c35d017b3..6ff13c78a6 100644
--- a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
+++ b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
@@ -1,237 +1,236 @@
<?php
/**
* @concrete-extensible
*/
class PhabricatorApplicationTransactionCommentView extends AphrontView {
private $submitButtonName;
private $action;
private $previewPanelID;
private $previewTimelineID;
private $previewToggleID;
private $formID;
private $statusID;
private $commentID;
private $draft;
private $requestURI;
private $showPreview = true;
private $objectPHID;
private $headerText;
public function setObjectPHID($object_phid) {
$this->objectPHID = $object_phid;
return $this;
}
public function getObjectPHID() {
return $this->objectPHID;
}
public function setShowPreview($show_preview) {
$this->showPreview = $show_preview;
return $this;
}
public function getShowPreview() {
return $this->showPreview;
}
public function setRequestURI(PhutilURI $request_uri) {
$this->requestURI = $request_uri;
return $this;
}
public function getRequestURI() {
return $this->requestURI;
}
public function setDraft(PhabricatorDraft $draft) {
$this->draft = $draft;
return $this;
}
public function getDraft() {
return $this->draft;
}
public function setSubmitButtonName($submit_button_name) {
$this->submitButtonName = $submit_button_name;
return $this;
}
public function getSubmitButtonName() {
return $this->submitButtonName;
}
public function setAction($action) {
$this->action = $action;
return $this;
}
public function getAction() {
return $this->action;
}
public function setHeaderText($text) {
$this->headerText = $text;
return $this;
}
public function render() {
$user = $this->getUser();
if (!$user->isLoggedIn()) {
$uri = id(new PhutilURI('/login/'))
->setQueryParam('next', (string) $this->getRequestURI());
return id(new PHUIObjectBoxView())
->setFlush(true)
->setHeaderText(pht('Add Comment'))
->appendChild(
javelin_tag(
'a',
array(
'class' => 'login-to-comment button',
'sigil' => 'workflow',
'href' => $uri
),
pht('Login to Comment')));
}
$data = array();
$comment = $this->renderCommentPanel();
if ($this->getShowPreview()) {
$preview = $this->renderPreviewPanel();
} else {
$preview = null;
}
Javelin::initBehavior(
'phabricator-transaction-comment-form',
array(
'formID' => $this->getFormID(),
'timelineID' => $this->getPreviewTimelineID(),
'panelID' => $this->getPreviewPanelID(),
'statusID' => $this->getStatusID(),
'commentID' => $this->getCommentID(),
'loadingString' => pht('Loading Preview...'),
'savingString' => pht('Saving Draft...'),
'draftString' => pht('Saved Draft'),
'showPreview' => $this->getShowPreview(),
'actionURI' => $this->getAction(),
'draftKey' => $this->getDraft()
? $this->getDraft()->getDraftKey()
: null,
));
$comment_box = id(new PHUIObjectBoxView())
->setFlush(true)
->setHeaderText($this->headerText)
->appendChild($comment);
return array($comment_box, $preview);
}
private function renderCommentPanel() {
$status = phutil_tag(
'div',
array(
'id' => $this->getStatusID(),
),
'');
$draft_comment = '';
if ($this->getDraft()) {
$draft_comment = $this->getDraft()->getDraft();
}
if (!$this->getObjectPHID()) {
throw new Exception("Call setObjectPHID() before render()!");
}
return id(new AphrontFormView())
->setUser($this->getUser())
->addSigil('transaction-append')
->setWorkflow(true)
->setMetadata(
array(
'objectPHID' => $this->getObjectPHID(),
))
->setAction($this->getAction())
->setID($this->getFormID())
->appendChild(
id(new PhabricatorRemarkupControl())
->setID($this->getCommentID())
->setName('comment')
->setLabel(pht('Comment'))
->setUser($this->getUser())
->setValue($draft_comment))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue($this->getSubmitButtonName()))
->appendChild(
id(new AphrontFormMarkupControl())
->setValue($status));
}
private function renderPreviewPanel() {
$preview = id(new PHUITimelineView())
->setID($this->getPreviewTimelineID());
return phutil_tag(
'div',
array(
'id' => $this->getPreviewPanelID(),
'style' => 'display: none',
),
$preview);
}
private function getPreviewPanelID() {
if (!$this->previewPanelID) {
$this->previewPanelID = celerity_generate_unique_node_id();
}
return $this->previewPanelID;
}
private function getPreviewTimelineID() {
if (!$this->previewTimelineID) {
$this->previewTimelineID = celerity_generate_unique_node_id();
}
return $this->previewTimelineID;
}
public function setFormID($id) {
$this->formID = $id;
return $this;
}
private function getFormID() {
if (!$this->formID) {
$this->formID = celerity_generate_unique_node_id();
}
return $this->formID;
}
private function getStatusID() {
if (!$this->statusID) {
$this->statusID = celerity_generate_unique_node_id();
}
return $this->statusID;
}
private function getCommentID() {
if (!$this->commentID) {
$this->commentID = celerity_generate_unique_node_id();
}
return $this->commentID;
}
}
-
diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionTextDiffDetailView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionTextDiffDetailView.php
index 83f016cb2b..55fe586bc6 100644
--- a/src/applications/transactions/view/PhabricatorApplicationTransactionTextDiffDetailView.php
+++ b/src/applications/transactions/view/PhabricatorApplicationTransactionTextDiffDetailView.php
@@ -1,57 +1,56 @@
<?php
final class PhabricatorApplicationTransactionTextDiffDetailView
extends AphrontView {
private $oldText;
private $newText;
public function setNewText($new_text) {
$this->newText = $new_text;
return $this;
}
public function setOldText($old_text) {
$this->oldText = $old_text;
return $this;
}
public function render() {
$old = $this->oldText;
$new = $this->newText;
// TODO: On mobile, or perhaps by default, we should switch to 1-up once
// that is built.
if (strlen($old)) {
$old = phutil_utf8_hard_wrap($old, 80);
$old = implode("\n", $old)."\n";
}
if (strlen($new)) {
$new = phutil_utf8_hard_wrap($new, 80);
$new = implode("\n", $new)."\n";
}
try {
$engine = new PhabricatorDifferenceEngine();
$changeset = $engine->generateChangesetFromFileContent($old, $new);
$whitespace_mode = DifferentialChangesetParser::WHITESPACE_SHOW_ALL;
$markup_engine = new PhabricatorMarkupEngine();
$markup_engine->setViewer($this->getUser());
$parser = new DifferentialChangesetParser();
$parser->setChangeset($changeset);
$parser->setMarkupEngine($markup_engine);
$parser->setWhitespaceMode($whitespace_mode);
return $parser->render(0, PHP_INT_MAX, array());
} catch (Exception $ex) {
return $ex->getMessage();
}
}
}
-
diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionView.php
index 38592a2978..b02c6119ad 100644
--- a/src/applications/transactions/view/PhabricatorApplicationTransactionView.php
+++ b/src/applications/transactions/view/PhabricatorApplicationTransactionView.php
@@ -1,352 +1,351 @@
<?php
/**
* @concrete-extensible
*/
class PhabricatorApplicationTransactionView extends AphrontView {
private $transactions;
private $engine;
private $anchorOffset = 1;
private $showEditActions = true;
private $isPreview;
private $objectPHID;
public function setObjectPHID($object_phid) {
$this->objectPHID = $object_phid;
return $this;
}
public function getObjectPHID() {
return $this->objectPHID;
}
public function setIsPreview($is_preview) {
$this->isPreview = $is_preview;
return $this;
}
public function setShowEditActions($show_edit_actions) {
$this->showEditActions = $show_edit_actions;
return $this;
}
public function getShowEditActions() {
return $this->showEditActions;
}
public function setAnchorOffset($anchor_offset) {
$this->anchorOffset = $anchor_offset;
return $this;
}
public function setMarkupEngine(PhabricatorMarkupEngine $engine) {
$this->engine = $engine;
return $this;
}
public function setTransactions(array $transactions) {
assert_instances_of($transactions, 'PhabricatorApplicationTransaction');
$this->transactions = $transactions;
return $this;
}
public function buildEvents($with_hiding = false) {
$user = $this->getUser();
$anchor = $this->anchorOffset;
$xactions = $this->transactions;
$xactions = $this->filterHiddenTransactions($xactions);
$xactions = $this->groupRelatedTransactions($xactions);
$groups = $this->groupDisplayTransactions($xactions);
// If the viewer has interacted with this object, we hide things from
// before their most recent interaction by default. This tends to make
// very long threads much more manageable, because you don't have to
// scroll through a lot of history and can focus on just new stuff.
$show_group = null;
if ($with_hiding) {
// Find the most recent comment by the viewer.
$group_keys = array_keys($groups);
$group_keys = array_reverse($group_keys);
// If we would only hide a small number of transactions, don't hide
// anything. Just don't examine the last few keys. Also, we always
// want to show the most recent pieces of activity, so don't examine
// the first few keys either.
$group_keys = array_slice($group_keys, 2, -2);
$type_comment = PhabricatorTransactions::TYPE_COMMENT;
foreach ($group_keys as $group_key) {
$group = $groups[$group_key];
foreach ($group as $xaction) {
if ($xaction->getAuthorPHID() == $user->getPHID() &&
$xaction->getTransactionType() == $type_comment) {
// This is the most recent group where the user commented.
$show_group = $group_key;
break 2;
}
}
}
}
$events = array();
$hide_by_default = ($show_group !== null);
foreach ($groups as $group_key => $group) {
if ($hide_by_default && ($show_group === $group_key)) {
$hide_by_default = false;
}
$group_event = null;
foreach ($group as $xaction) {
$event = $this->renderEvent($xaction, $group, $anchor);
$event->setHideByDefault($hide_by_default);
$anchor++;
if (!$group_event) {
$group_event = $event;
} else {
$group_event->addEventToGroup($event);
}
}
$events[] = $group_event;
}
return $events;
}
public function render() {
if (!$this->getObjectPHID()) {
throw new Exception("Call setObjectPHID() before render()!");
}
$view = new PHUITimelineView();
$events = $this->buildEvents($with_hiding = true);
foreach ($events as $event) {
$view->addEvent($event);
}
if ($this->getShowEditActions()) {
$list_id = celerity_generate_unique_node_id();
$view->setID($list_id);
Javelin::initBehavior(
'phabricator-transaction-list',
array(
'listID' => $list_id,
'objectPHID' => $this->getObjectPHID(),
'nextAnchor' => $this->anchorOffset + count($events),
));
}
return $view->render();
}
protected function getOrBuildEngine() {
if ($this->engine) {
return $this->engine;
}
$field = PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT;
$engine = id(new PhabricatorMarkupEngine())
->setViewer($this->getUser());
foreach ($this->transactions as $xaction) {
if (!$xaction->hasComment()) {
continue;
}
$engine->addObject($xaction->getComment(), $field);
}
$engine->process();
return $engine;
}
private function buildChangeDetailsLink(
PhabricatorApplicationTransaction $xaction) {
return javelin_tag(
'a',
array(
'href' => '/transactions/detail/'.$xaction->getPHID().'/',
'sigil' => 'workflow',
),
pht('(Show Details)'));
}
protected function shouldGroupTransactions(
PhabricatorApplicationTransaction $u,
PhabricatorApplicationTransaction $v) {
return false;
}
protected function renderTransactionContent(
PhabricatorApplicationTransaction $xaction) {
$field = PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT;
$engine = $this->getOrBuildEngine();
$comment = $xaction->getComment();
if ($xaction->hasComment()) {
if ($comment->getIsDeleted()) {
return phutil_tag(
'em',
array(),
pht('This comment has been deleted.'));
} else {
return $engine->getOutput($comment, $field);
}
}
return null;
}
private function filterHiddenTransactions(array $xactions) {
foreach ($xactions as $key => $xaction) {
if ($xaction->shouldHide()) {
unset($xactions[$key]);
}
}
return $xactions;
}
private function groupRelatedTransactions(array $xactions) {
$last = null;
$last_key = null;
$groups = array();
foreach ($xactions as $key => $xaction) {
if ($last && $this->shouldGroupTransactions($last, $xaction)) {
$groups[$last_key][] = $xaction;
unset($xactions[$key]);
} else {
$last = $xaction;
$last_key = $key;
}
}
foreach ($xactions as $key => $xaction) {
$xaction->attachTransactionGroup(idx($groups, $key, array()));
}
return $xactions;
}
private function groupDisplayTransactions(array $xactions) {
$groups = array();
$group = array();
foreach ($xactions as $xaction) {
if ($xaction->shouldDisplayGroupWith($group)) {
$group[] = $xaction;
} else {
if ($group) {
$groups[] = $group;
}
$group = array($xaction);
}
}
if ($group) {
$groups[] = $group;
}
foreach ($groups as $key => $group) {
$group = msort($group, 'getActionStrength');
$group = array_reverse($group);
$groups[$key] = $group;
}
return $groups;
}
private function renderEvent(
PhabricatorApplicationTransaction $xaction,
array $group,
$anchor) {
$viewer = $this->getUser();
$event = id(new PHUITimelineEventView())
->setUser($viewer)
->setTransactionPHID($xaction->getPHID())
->setUserHandle($xaction->getHandle($xaction->getAuthorPHID()))
->setIcon($xaction->getIcon())
->setColor($xaction->getColor());
if (!$this->shouldSuppressTitle($xaction, $group)) {
$title = $xaction->getTitle();
if ($xaction->hasChangeDetails()) {
if (!$this->isPreview) {
$details = $this->buildChangeDetailsLink($xaction);
$title = array(
$title,
' ',
$details,
);
}
}
$event->setTitle($title);
}
if ($this->isPreview) {
$event->setIsPreview(true);
} else {
$event
->setDateCreated($xaction->getDateCreated())
->setContentSource($xaction->getContentSource())
->setAnchor($anchor);
}
$has_deleted_comment = $xaction->getComment() &&
$xaction->getComment()->getIsDeleted();
if ($this->getShowEditActions() && !$this->isPreview) {
if ($xaction->getCommentVersion() > 1) {
$event->setIsEdited(true);
}
$can_edit = PhabricatorPolicyCapability::CAN_EDIT;
if ($xaction->hasComment() || $has_deleted_comment) {
$has_edit_capability = PhabricatorPolicyFilter::hasCapability(
$viewer,
$xaction,
$can_edit);
if ($has_edit_capability) {
$event->setIsEditable(true);
}
}
}
$content = $this->renderTransactionContent($xaction);
if ($content) {
$event->appendChild($content);
}
return $event;
}
private function shouldSuppressTitle(
PhabricatorApplicationTransaction $xaction,
array $group) {
// This is a little hard-coded, but we don't have any other reasonable
// cases for now. Suppress "commented on" if there are other actions in
// the display group.
if (count($group) > 1) {
$type_comment = PhabricatorTransactions::TYPE_COMMENT;
if ($xaction->getTransactionType() == $type_comment) {
return true;
}
}
return false;
}
}
-
diff --git a/src/applications/typeahead/storage/PhabricatorTypeaheadResult.php b/src/applications/typeahead/storage/PhabricatorTypeaheadResult.php
index 581d26d566..9ec4955be1 100644
--- a/src/applications/typeahead/storage/PhabricatorTypeaheadResult.php
+++ b/src/applications/typeahead/storage/PhabricatorTypeaheadResult.php
@@ -1,86 +1,85 @@
<?php
final class PhabricatorTypeaheadResult {
private $name;
private $uri;
private $phid;
private $priorityString;
private $displayName;
private $displayType;
private $imageURI;
private $priorityType;
private $icon;
private $closed;
public function setIcon($icon) {
$this->icon = $icon;
return $this;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function setURI($uri) {
$this->uri = $uri;
return $this;
}
public function setPHID($phid) {
$this->phid = $phid;
return $this;
}
public function setPriorityString($priority_string) {
$this->priorityString = $priority_string;
return $this;
}
public function setDisplayName($display_name) {
$this->displayName = $display_name;
return $this;
}
public function setDisplayType($display_type) {
$this->displayType = $display_type;
return $this;
}
public function setImageURI($image_uri) {
$this->imageURI = $image_uri;
return $this;
}
public function setPriorityType($priority_type) {
$this->priorityType = $priority_type;
return $this;
}
public function setClosed($closed) {
$this->closed = $closed;
return $this;
}
public function getWireFormat() {
$data = array(
$this->name,
$this->uri ? (string)$this->uri : null,
$this->phid,
$this->priorityString,
$this->displayName,
$this->displayType,
$this->imageURI ? (string)$this->imageURI : null,
$this->priorityType,
$this->icon,
$this->closed,
);
while (end($data) === null) {
array_pop($data);
}
return $data;
}
}
-
diff --git a/src/docs/user/configuration/troubleshooting_https.diviner b/src/docs/user/configuration/troubleshooting_https.diviner
index 6950377219..19e335e615 100644
--- a/src/docs/user/configuration/troubleshooting_https.diviner
+++ b/src/docs/user/configuration/troubleshooting_https.diviner
@@ -1,74 +1,74 @@
@title Troubleshooting HTTPS
@group config
Detailed instructions for troubleshooting HTTPS connection problems.
= Overview =
If you're having trouble connecting to an HTTPS install of Phabricator, and
particularly if you're receiving a "There was an error negotiating the SSL
connection." error, this document may be able to help you diagnose and resolve
the problem.
-Connection negotation can fail for several reasons. The major ones are:
+Connection negotiation can fail for several reasons. The major ones are:
- You have not added the the Certificate Authority as a trusted authority
(this is the most common problem, and usually the issue for self-signed
certificates).
- The SSL certificate is signed for the wrong domain. For example, a
certificate signed for `www.example.com` will not work for
`phabricator.example.com`.
- The server rejects TLSv1 SNI connections for the domain (this is
complicated, see below).
= Certificate Authority Problems =
SSL certificates need to be signed by a trusted authority (called a Certificate
Authority or "CA") to be accepted. If the CA for a certificate is untrusted, the
connection will fail (this defends the connection from an eavesdropping attack
called "man in the middle"). Normally, you purchase a certificate from a known
authority and clients have a list of trusted authorities.
You can self-sign a certificate by creating your own CA, but clients will not trust it by default. They need to add the CA as a trusted authority.
For instructions on adding CAs, see `libphutil/resources/ssl/README`.
Although it is possible to accept certificates that aren't signed by trusted
CAs, this is not currently supported because it compromises the ability of SSL
to protect the connection against eavesdropping.
= Domain Problems =
Verify the domain the certificate was issued for. You can generally do this
with:
$ openssl x509 -text -in <certificate>
If the certificate was accidentally generated for, e.g. `www.example.com` but
you installed Phabricator on `phabricator.example.com`, you need to generate a
new certificate for the right domain.
= SNI Problems =
Server Name Identification ("SNI") is a feature of TLSv1 which works a bit like
Apache VirtualHosts, and allows a server to present different certificates to
clients who are connecting to it using different names.
Servers that are not configured properly may reject TSLv1 SNI requests because
they do not recognize the name the client is connecting with. This
topic is complicated, but you can test for it by running:
$ openssl s_client -connect example.com:443 -servername example.com
Replace **both** instances of "example.com" with your domain. If you receive
an error in `SSL23_GET_SERVER_HELLO` with `reason(1112)`, like this:
CONNECTED(00000003)
87871:error:14077458:SSL routines:SSL23_GET_SERVER_HELLO:reason(1112):
/SourceCache/OpenSSL098/OpenSSL098-44/src/ssl/s23_clnt.c:602:
...it indicates server is misconfigured. The most common cause of this problem
is an Apache server that does not explicitly name the Phabricator domain as a
valid VirtualHost.
This error occurs only for some versions of the OpenSSL client library (from v0.9.8r or earlier until 1.0.0), so only some users may experience it.
diff --git a/src/docs/user/contributing/php_coding_standards.diviner b/src/docs/user/contributing/php_coding_standards.diviner
index 72a849fce5..cb3406e15b 100644
--- a/src/docs/user/contributing/php_coding_standards.diviner
+++ b/src/docs/user/contributing/php_coding_standards.diviner
@@ -1,170 +1,169 @@
@title PHP Coding Standards
@group contrib
This document describes PHP coding standards for Phabricator and related projects (like Arcanist and libphutil).
= Overview =
This document outlines technical and style guidelines which are followed in
libphutil. Contributors should also follow these guidelines. Many of these
guidelines are automatically enforced by lint.
These guidelines are essentially identical to the Facebook guidelines, since I
basically copy-pasted them. If you are already familiar with the Facebook
guidelines, you probably don't need to read this super thoroughly.
= Spaces, Linebreaks and Indentation =
- Use two spaces for indentation. Don't use tab literal characters.
- Use Unix linebreaks ("\n"), not MSDOS ("\r\n") or OS9 ("\r").
- Use K&R style braces and spacing.
- Put a space after control keywords like ##if## and ##for##.
- Put a space after commas in argument lists.
- Put a space around operators like ##=##, ##<##, etc.
- Don't put spaces after function names.
- Parentheses should hug their contents.
- Generally, prefer to wrap code at 80 columns.
= Case and Capitalization =
- Name variables and functions using ##lowercase_with_underscores##.
- Name classes using ##UpperCamelCase##.
- Name methods and properties using ##lowerCamelCase##.
- Use uppercase for common acronyms like ID and HTML.
- Name constants using ##UPPERCASE##.
- Write ##true##, ##false## and ##null## in lowercase.
= Comments =
- Do not use "#" (shell-style) comments.
- Prefer "//" comments inside function and method bodies.
= PHP Language Style =
- Use "<?php", not the "<?" short form. Omit the closing "?>" tag.
- Prefer casts like ##(string)## to casting functions like ##strval()##.
- Prefer type checks like ##$v === null## to type functions like
##is_null()##.
- Avoid all crazy alternate forms of language constructs like "endwhile"
and "<>".
- Always put braces around conditional and loop blocks.
= PHP Language Features =
- Use PHP as a programming language, not a templating language.
- Avoid globals.
- Avoid extract().
- Avoid eval().
- Avoid variable variables.
- Prefer classes over functions.
- Prefer class constants over defines.
- Avoid naked class properties; instead, define accessors.
- Use exceptions for error conditions.
- Use type hints, use `assert_instances_of()` for arrays holding objects.
= Examples =
**if/else:**
if ($some_variable > 3) {
// ...
} else if ($some_variable === null) {
// ...
} else {
// ...
}
You should always put braces around the body of an if clause, even if it is only
one line long. Note spaces around operators and after control statements. Do not
use the "endif" construct, and write "else if" as two words.
**for:**
for ($ii = 0; $ii < 10; $ii++) {
// ...
}
Prefer $ii, $jj, $kk, etc., as iterators, since they're easier to pick out
visually and react better to "Find Next..." in editors.
**foreach:**
foreach ($map as $key => $value) {
// ...
}
**switch:**
switch ($value) {
case 1:
// ...
break;
case 2:
if ($flag) {
// ...
break;
}
break;
default:
// ...
break;
}
##break## statements should be indented to block level.
**array literals:**
$junk = array(
'nuts',
'bolts',
'refuse',
);
Use a trailing comma and put the closing parenthesis on a separate line so that
diffs which add elements to the array affect only one line.
**operators:**
$a + $b; // Put spaces around operators.
$omg.$lol; // Exception: no spaces around string concatenation.
$arr[] = $element; // Couple [] with the array when appending.
$obj = new Thing(); // Always use parens.
**function/method calls:**
// One line
eject($cargo);
// Multiline
AbstractFireFactoryFactoryEngine::promulgateConflagrationInstance(
$fuel,
$ignition_source);
**function/method definitions:**
function example_function($base_value, $additional_value) {
return $base_value + $additional_value;
}
class C {
public static function promulgateConflagrationInstance(
IFuel $fuel,
IgnitionSource $source) {
// ...
}
}
**class:**
class Dog extends Animal {
const CIRCLES_REQUIRED_TO_LIE_DOWN = 3;
private $favoriteFood = 'dirt';
public function getFavoriteFood() {
return $this->favoriteFood;
}
}
-
diff --git a/src/docs/user/developer/adding_new_css_and_js.diviner b/src/docs/user/developer/adding_new_css_and_js.diviner
index 2443f9d5d6..83a80aca40 100644
--- a/src/docs/user/developer/adding_new_css_and_js.diviner
+++ b/src/docs/user/developer/adding_new_css_and_js.diviner
@@ -1,86 +1,85 @@
@title Adding New CSS and JS
@group developer
Explains how to add new CSS and JS files to Phabricator.
= Overview =
Phabricator uses a system called **Celerity** to manage static resources. If you
are a current or former Facebook employee, Celerity is based on the Haste system
used at Facebook and generally behaves similarly.
= Adding a New File =
To add a new CSS or JS file, create it in an appropriate location in
##webroot/rsrc/css/## or ##webroot/rsrc/js/## inside your ##phabricator/##
directory.
Each file must ##@provides## itself as a component, declared in a header
comment:
LANG=css
/**
* @provides duck-styles-css
*/
.duck-header {
font-size: 9001px;
}
Note that this comment must be a Javadoc-style comment, not just any comment.
If your component depends on other components (which is common in JS but
rare and inadvisable in CSS), declare then with ##@requires##:
LANG=js
/**
* @requires javelin-stratcom
* @provides duck
*/
/**
* Put class documentation here, NOT in the header block.
*/
JX.install('Duck', {
...
});
Then rebuild the Celerity map (see the next section).
= Changing an Existing File =
When you add, move or remove a file, or change the contents of existing JS or
CSS file, you should rebuild the Celerity map:
phabricator/ $ ./scripts/celerity_mapper.php ./webroot
If you've only changed file content things will generally work even if you
don't, but they might start not working as well in the future if you skip this
step.
The generated file `resources/celerity/map.php` causes merge conflicts
quite often. They can be resolved by running the Celerity mapper. You can
automate this process by running:
phabricator/ $ ./scripts/celerity/install_merge.sh
This will install Git merge driver which will run when a conflict in this file
occurs.
= Including a File =
To include a CSS or JS file in a page, use
@{function:require_celerity_resource}:
require_celerity_resource('duck-style-css');
require_celerity_resource('duck');
If your map is up to date, the resource should now be included correctly when
the page is rendered.
You should place this call as close to the code which actually uses the resource
as possible, i.e. **not** at the top of your Controller. The idea is that you
should @{function:require_celerity_resource} a resource only if you are actually
using it on a specific rendering of the page, not just because some views of the
page might require it.
-
diff --git a/src/docs/user/developer/unit_tests.diviner b/src/docs/user/developer/unit_tests.diviner
index 55a67403f8..6aeaaab225 100644
--- a/src/docs/user/developer/unit_tests.diviner
+++ b/src/docs/user/developer/unit_tests.diviner
@@ -1,87 +1,86 @@
@title Writing Unit Tests
@group developer
Simple guide to libphutil, Arcanist and Phabricator unit tests.
= Overview =
libphutil, Arcanist and Phabricator provide and use a simple unit test
framework. This document is aimed at project contributors and describes how to
use it to add and run tests in these projects or other libphutil libraries.
In the general case, you can integrate ##arc## with a custom unit test engine
(like PHPUnit or any other unit testing library) to run tests in other projects.
See @{article:Arcanist User Guide: Customizing Lint, Unit Tests and Workflows}
for information on customizing engines.
= Adding Tests =
To add new tests to a libphutil, Arcanist or Phabricator module:
- Create a ##__tests__/## directory in the module if it doesn't exist yet.
- Add classes to the ##__tests__/## directory which extend from
@{class:PhabricatorTestCase} (in Phabricator) or
@{class@arcanist:ArcanistPhutilTestCase} (elsewhere).
- Run ##arc liberate## on the library root so your classes are loadable.
= Running Tests =
Once you've added test classes, you can run them with:
- ##arc unit path/to/module/##, to explicitly run module tests.
- ##arc unit##, to run tests for all modules affected by changes in the
working copy.
- ##arc diff## will also run ##arc unit## for you.
= Example Test Case =
Here's a simple example test:
lang=php
class PhabricatorTrivialTestCase extends PhabricatorTestCase {
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');
}
}
You can see this class at @{class:PhabricatorTrivialTestCase} and run it with:
phabricator/ $ arc unit src/infrastructure/testing/testcase/
PASS <1ms* testAllIsRightWithTheWorld
For more information on writing tests, see
@{class@arcanist:ArcanistPhutilTestCase} and @{class:PhabricatorTestCase}.
= Database Isolation =
By default, Phabricator isolates unit tests from the database. It makes a crude
effort to simulate some side effects (principally, ID assignment on insert), but
any queries which read data will fail to select any rows and throw an exception
about isolation. In general, isolation is good, but this can make certain types
of tests difficult to write. When you encounter issues, you can deal with them
in a number of ways. From best to worst:
- Encounter no issues; your tests are fast and isolated.
- Add more simulated side effects if you encounter minor issues and simulation
is reasonable.
- Build a real database simulation layer (fairly complex).
- Disable isolation for a single test by using
##LiskDAO::endIsolateAllLiskEffectsToCurrentProcess();## before your test
and ##LiskDAO::beginIsolateAllLiskEffectsToCurrentProcess();## after your
test. This will disable isolation for one test. NOT RECOMMENDED.
- Disable isolation for your entire test case by overriding
##getPhabricatorTestCaseConfiguration()## and providing
##self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK => false## in the configuration
dictionary you return. This will disable isolation entirely. STRONGLY NOT
RECOMMENDED.
-
diff --git a/src/docs/user/developer/using_oauthserver.diviner b/src/docs/user/developer/using_oauthserver.diviner
index 9a1a989e66..9587d23410 100644
--- a/src/docs/user/developer/using_oauthserver.diviner
+++ b/src/docs/user/developer/using_oauthserver.diviner
@@ -1,120 +1,120 @@
@title Using the Phabricator OAuth Server
@group developer
How to use the Phabricator OAuth Server.
= Overview =
-Phabricator includes an OAuth Server which supports the
-##Authorization Code Grant## flow as described in the OAuth 2.0
+Phabricator includes an OAuth Server which supports the
+##Authorization Code Grant## flow as described in the OAuth 2.0
specification:
http://tools.ietf.org/html/draft-ietf-oauth-v2-23
-This functionality can allow clients to integrate with a given
-Phabricator instance in a secure way with granular data access.
-For example, Phabricator can be used as a central identity store for any
+This functionality can allow clients to integrate with a given
+Phabricator instance in a secure way with granular data access.
+For example, Phabricator can be used as a central identity store for any
clients that implement OAuth 2.0.
= Vocabulary =
-- **Access token** - a token which allows a client to ask for data on
-behalf of a resource owner. A given client will only be able to access
+- **Access token** - a token which allows a client to ask for data on
+behalf of a resource owner. A given client will only be able to access
data included in the scope(s) the resource owner authorized that client for.
-- **Authorization code** - a short-lived code which allows an authenticated
+- **Authorization code** - a short-lived code which allows an authenticated
client to ask for an access token on behalf of some resource owner.
-- **Client** - this is the application or system asking for data from the
+- **Client** - this is the application or system asking for data from the
OAuth Server on behalf of the resource owner.
-- **Resource owner** - this is the user the client and OAuth Server are
+- **Resource owner** - this is the user the client and OAuth Server are
concerned with on a given request.
-- **Scope** - this defines a specific piece of granular data a client can
-or can not access on behalf of a user. For example, if authorized for the
-"whoami" scope on behalf of a given resource owner, the client can get the
-results of Conduit.whoami for that resource owner when authenticated with
+- **Scope** - this defines a specific piece of granular data a client can
+or can not access on behalf of a user. For example, if authorized for the
+"whoami" scope on behalf of a given resource owner, the client can get the
+results of Conduit.whoami for that resource owner when authenticated with
a valid access token.
= Setup - Creating a Client =
# Visit https://phabricator.example.com/oauthserver/client/create/
# Fill out the form
# Profit
= Obtaining an Authorization Code =
-POST or GET https://phabricator.example.com/oauthserver/auth/ with the
+POST or GET https://phabricator.example.com/oauthserver/auth/ with the
following parameters:
- Required - **client_id** - the id of the newly registered client.
-- Required - **response_type** - the desired type of authorization code
+- Required - **response_type** - the desired type of authorization code
response. Only code is supported at this time.
-- Optional - **redirect_uri** - override the redirect_uri the client
-registered. This redirect_uri must have the same fully-qualified domain
-and have at least the same query parameters as the redirect_uri the client
+- Optional - **redirect_uri** - override the redirect_uri the client
+registered. This redirect_uri must have the same fully-qualified domain
+and have at least the same query parameters as the redirect_uri the client
registered, as well as have no fragments.
- Optional - **scope** - specify what scope(s) the client needs access to
in a space-delimited list.
-- Optional - **state** - an opaque value the client can send to the server
-for programmatic excellence. Some clients use this value to implement XSRF
+- Optional - **state** - an opaque value the client can send to the server
+for programmatic excellence. Some clients use this value to implement XSRF
protection or for debugging purposes.
-If done correctly and the resource owner has not yet authorized the client
-for the desired scope, then the resource owner will be presented with an
-interface to authorize the client for the desired scope. The OAuth Server
-will redirect to the pertinent redirect_uri with an authorization code or
+If done correctly and the resource owner has not yet authorized the client
+for the desired scope, then the resource owner will be presented with an
+interface to authorize the client for the desired scope. The OAuth Server
+will redirect to the pertinent redirect_uri with an authorization code or
an error indicating the resource owner did not authorize the client, depending.
-If done correctly and the resource owner has already authorized the client for
-the desired scope, then the OAuth Server will redirect to the pertinent
-redirect_uri with a valid authorization code.
+If done correctly and the resource owner has already authorized the client for
+the desired scope, then the OAuth Server will redirect to the pertinent
+redirect_uri with a valid authorization code.
-If there is an error, the OAuth Server will return a descriptive error
-message. This error will be presented to the resource owner on the
-Phabricator domain if there is reason to believe there is something fishy
-with the client. For example, if there is an issue with the redirect_uri.
-Otherwise, the OAuth Server will redirect to the pertinent redirect_uri
+If there is an error, the OAuth Server will return a descriptive error
+message. This error will be presented to the resource owner on the
+Phabricator domain if there is reason to believe there is something fishy
+with the client. For example, if there is an issue with the redirect_uri.
+Otherwise, the OAuth Server will redirect to the pertinent redirect_uri
and include the pertinent error information.
= Obtaining an Access Token =
-POST or GET https://phabricator.example.com/oauthserver/token/
+POST or GET https://phabricator.example.com/oauthserver/token/
with the following parameters:
- Required - **client_id** - the id of the client
-- Required - **client_secret** - the secret of the client.
+- Required - **client_secret** - the secret of the client.
This is used to authenticate the client.
- Required - **code** - the authorization code obtained earlier.
-- Required - **grant_type** - the desired type of access grant.
+- Required - **grant_type** - the desired type of access grant.
Only token is supported at this time.
-- Optional - **redirect_uri** - should be the exact same redirect_uri as
-the redirect_uri specified to obtain the authorization code. If no
-redirect_uri was specified to obtain the authorization code then this
+- Optional - **redirect_uri** - should be the exact same redirect_uri as
+the redirect_uri specified to obtain the authorization code. If no
+redirect_uri was specified to obtain the authorization code then this
should not be specified.
-If done correctly, the OAuth Server will redirect to the pertinent
+If done correctly, the OAuth Server will redirect to the pertinent
redirect_uri with an access token.
-If there is an error, the OAuth Server will return a descriptive error
+If there is an error, the OAuth Server will return a descriptive error
message.
= Using an Access Token =
-Simply include a query param with the key of "access_token" and the value
+Simply include a query param with the key of "access_token" and the value
as the earlier obtained access token. For example:
https://phabricator.example.com/api/user.whoami?access_token=ykc7ly7vtibj334oga4fnfbuvnwz4ocp
If the token has expired or is otherwise invalid, the client will receive
-an error indicating as such. In these cases, the client should re-initiate
+an error indicating as such. In these cases, the client should re-initiate
the entire ##Authorization Code Grant## flow.
-NOTE: See "Scopes" section below for more information on what data is
+NOTE: See "Scopes" section below for more information on what data is
currently exposed through the OAuth Server.
= Scopes =
There are only two scopes supported at this time.
-- **offline_access** - allows an access token to work indefinitely without
+- **offline_access** - allows an access token to work indefinitely without
expiring.
-- **whoami** - allows the client to access the results of Conduit.whoami on
+- **whoami** - allows the client to access the results of Conduit.whoami on
behalf of the resource owner.
diff --git a/src/docs/user/flavortext/javascript_object_array.diviner b/src/docs/user/flavortext/javascript_object_array.diviner
index 19cca9973c..fde8b33ecf 100644
--- a/src/docs/user/flavortext/javascript_object_array.diviner
+++ b/src/docs/user/flavortext/javascript_object_array.diviner
@@ -1,153 +1,152 @@
@title Javascript Object and Array
@group flavortext
This document describes the behaviors of Object and Array in Javascript, and
a specific approach to their use which produces basically reasonable language
behavior.
= Primitives =
Javascript has two native datatype primitives, Object and Array. Both are
classes, so you can use ##new## to instantiate new objects and arrays:
COUNTEREXAMPLE
var a = new Array(); // Not preferred.
var o = new Object();
However, **you should prefer the shorthand notation** because it's more concise:
lang=js
var a = []; // Preferred.
var o = {};
(A possible exception to this rule is if you want to use the allocation behavior
of the Array constructor, but you almost certainly don't.)
The language relationship between Object and Array is somewhat tricky. Object
and Array are both classes, but "object" is also a primitive type. Object is
//also// the base class of all classes.
lang=js
typeof Object; // "function"
typeof Array; // "function"
typeof {}; // "object"
typeof []; // "object"
var a = [], o = {};
o instanceof Object; // true
o instanceof Array; // false
a instanceof Object; // true
a instanceof Array; // true
= Objects are Maps, Arrays are Lists =
PHP has a single ##array## datatype which behaves like as both map and a list,
and a common mistake is to treat Javascript arrays (or objects) in the same way.
**Don't do this.** It sort of works until it doesn't. Instead, learn how
Javascript's native datatypes work and use them properly.
In Javascript, you should think of Objects as maps ("dictionaries") and Arrays
as lists ("vectors").
You store keys-value pairs in a map, and store ordered values in a list. So,
store key-value pairs in Objects.
var o = { // Good, an object is a map.
name: 'Hubert',
species: 'zebra'
};
console.log(o.name);
...and store ordered values in Arrays.
var a = [1, 2, 3]; // Good, an array is a list.
a.push(4);
Don't store key-value pairs in Arrays and don't expect Objects to be ordered.
COUNTEREXAMPLE
var a = [];
a['name'] = 'Hubert'; // No! Don't do this!
This technically works because Arrays are Objects and you think everything is
fine and dandy, but it won't do what you want and will burn you.
= Iterating over Maps and Lists =
Iterate over a map like this:
lang=js
for (var k in object) {
f(object[k]);
}
NOTE: There's some hasOwnProperty nonsense being omitted here, see below.
Iterate over a list like this:
lang=js
for (var ii = 0; ii < list.length; ii++) {
f(list[ii]);
}
NOTE: There's some sparse array nonsense being omitted here, see below.
If you try to use ##for (var k in ...)## syntax to iterate over an Array, you'll
pick up a whole pile of keys you didn't intend to and it won't work. If you try
to use ##for (var ii = 0; ...)## syntax to iterate over an Object, it won't work
at all.
If you consistently treat Arrays as lists and Objects as maps and use the
corresponding iterators, everything will pretty much always work in a reasonable
way.
= hasOwnProperty() =
An issue with this model is that if you write stuff to Object.prototype, it will
show up every time you use enumeration ##for##:
COUNTEREXAMPLE
var o = {};
Object.prototype.duck = "quack";
for (var k in o) {
console.log(o[k]); // Logs "quack"
}
There are two ways to avoid this:
- test that ##k## exists on ##o## by calling ##o.hasOwnProperty(k)## in every
single loop everywhere in your program and only use libraries which also do
this and never forget to do it ever; or
- don't write to Object.prototype.
Of these, the first option is terrible garbage. Go with the second option.
= Sparse Arrays =
Another wrench in this mess is that Arrays aren't precisely like lists, because
they do have indexes and may be sparse:
var a = [];
a[2] = 1;
console.log(a); // [undefined, undefined, 1]
The correct way to deal with this is:
for (var ii = 0; ii < list.length; ii++) {
if (list[ii] == undefined) {
continue;
}
f(list[ii]);
}
Avoid sparse arrays if possible.
= Ordered Maps =
If you need an ordered map, you need to have a map for key-value associations
and a list for key order. Don't try to build an ordered map using one Object or
one Array. This generally applies for other complicated datatypes, as well; you
need to build them out of more than one primitive.
-
diff --git a/src/docs/user/flavortext/javascript_pitfalls.diviner b/src/docs/user/flavortext/javascript_pitfalls.diviner
index bee2b94675..a4815e8f3a 100644
--- a/src/docs/user/flavortext/javascript_pitfalls.diviner
+++ b/src/docs/user/flavortext/javascript_pitfalls.diviner
@@ -1,88 +1,87 @@
@title Javascript Pitfalls
@group flavortext
This document discusses pitfalls and flaws in the Javascript language, and how
to avoid, work around, or at least understand them.
= Implicit Semicolons =
Javascript tries to insert semicolons if you forgot them. This is a pretty
horrible idea. Notably, it can mask syntax errors by transforming subexpressions
on their own lines into statements with no effect:
lang=js
string = "Here is a fairly long string that does not fit on one "
"line. Note that I forgot the string concatenation operators "
"so this will compile and execute with the wrong behavior. ";
Here's what ECMA262 says about this:
When, as the program is parsed ..., a token ... is encountered that is not
allowed by any production of the grammar, then a semicolon is automatically
inserted before the offending token if one or more of the following conditions
is true: ...
To protect yourself against this "feature", don't use it. Always explicitly
insert semicolons after each statement. You should also prefer to break lines in
places where insertion of a semicolon would not make the unparseable parseable,
usually after operators.
= ##with## is Bad News =
##with## is a pretty bad feature, for this reason among others:
with (object) {
property = 3; // Might be on object, might be on window: who knows.
}
Avoid ##with##.
= ##arguments## is not an Array =
You can convert ##arguments## to an array using JX.$A() or similar. Note that
you can pass ##arguments## to Function.prototype.apply() without converting it.
= Object, Array, and iteration are needlessly hard =
There is essentially only one reasonable, consistent way to use these primitives
but it is not obvious. Navigate these troubled waters with
@{article:Javascript Object and Array}.
= typeof null == "object" =
This statement is true in Javascript:
typeof null == 'object'
This is pretty much a bug in the language that can never be fixed now.
= Number, String, and Boolean objects =
Like Java, Javascript has primitive versions of number, string, and boolean,
and object versions. In Java, there's some argument for this distinction. In
Javascript, it's pretty much completely worthless and the behavior of these
objects is wrong. String and Boolean in particular are essentially unusable:
lang=js
"pancake" == "pancake"; // true
new String("pancake") == new String("pancake"); // false
var b = new Boolean(false);
b; // Shows 'false' in console.
!b; // ALSO shows 'false' in console.
!b == b; // So this is true!
!!b == !b // Negate both sides and it's false! FUCK!
if (b) {
// Better fucking believe this will get executed.
}
There is no advantage to using the object forms (the primitive forms behave like
objects and can have methods and properties, and inherit from Array.prototype,
Number.prototype, etc.) and their logical behavior is at best absurd and at
worst strictly wrong.
**Never use** ##new Number()##, ##new String()## or ##new Boolean()## unless
your Javascript is God Tier and you are absolutely sure you know what you are
doing.
-
diff --git a/src/docs/user/flavortext/things_you_should_do_now.diviner b/src/docs/user/flavortext/things_you_should_do_now.diviner
index 2ccc53b893..d446c741c2 100644
--- a/src/docs/user/flavortext/things_you_should_do_now.diviner
+++ b/src/docs/user/flavortext/things_you_should_do_now.diviner
@@ -1,140 +1,138 @@
@title Things You Should Do Now
@group flavortext
Describes things you should do now when building software, because the cost to
do them increases over time and eventually becomes prohibitive or impossible.
= Overview =
If you're building a hot new web startup, there are a lot of decisions to make
about what to focus on. Most things you'll build will take about the same amount
of time to build regardless of what order you build them in, but there are a few
technical things which become vastly more expensive to fix later.
If you don't do these things early in development, they'll become very hard or
impossible to do later. This is basically a list of things that would have saved
Facebook huge amounts of time and effort down the road if someone had spent
a tiny amount of time on them earlier in the development process.
See also @{article:Things You Should Do Soon} for things that scale less
drastically over time.
= Start IDs At a Gigantic Number =
If you're using integer IDs to identify data or objects, **don't** start your
IDs at 1. Start them at a huge number (e.g., 2^33) so that no object ID will
ever appear in any other role in your application (like a count, a natural
index, a byte size, a timestamp, etc). This takes about 5 seconds if you do it
before you launch and rules out a huge class of nasty bugs for all time. It
becomes incredibly difficult as soon as you have production data.
The kind of bug that this causes is accidental use of some other value as an ID:
COUNTEREXAMPLE
// Load the user's friends, returns a map of friend_id => true
$friend_ids = user_get_friends($user_id);
// Get the first 8 friends.
$first_few_friends = array_slice($friend_ids, 0, 8);
// Render those friends.
render_user_friends($user_id, array_keys($first_few_friends));
Because array_slice() in PHP discards array indices and renumbers them, this
doesn't render the user's first 8 friends but the users with IDs 0 through 7,
e.g. Mark Zuckerberg (ID 4) and Dustin Moskovitz (ID 6). If you have IDs in this
range, sooner or later something that isn't an ID will get treated like an ID
and the operation will be valid and cause unexpected behavior. This is
completely avoidable if you start your IDs at a gigantic number.
= Only Store Valid UTF-8 =
For the most part, you can ignore UTF-8 and unicode until later. However, there
is one aspect of unicode you should address now: store only valid UTF-8 strings.
Assuming you're storing data internally as UTF-8 (this is almost certainly the
right choice and definitely the right choice if you have no idea how unicode
works), you just need to sanitize all the data coming into your application and
make sure it's valid UTF-8.
If your application emits invalid UTF-8, other systems (like browsers) will
break in unexpected and interesting ways. You will eventually be forced to
ensure you emit only valid UTF-8 to avoid these problems. If you haven't
sanitized your data, you'll basically have two options:
- do a huge migration on literally all of your data to sanitize it; or
- forever sanitize all data on its way out on the read pathways.
As of 2011 Facebook is in the second group, and spends several milliseconds of
CPU time sanitizing every display string on its way to the browser, which
multiplies out to hundreds of servers worth of CPUs sitting in a datacenter
paying the price for the invalid UTF-8 in the databases.
You can likely learn enough about unicode to be confident in an implementation
which addresses this problem within a few hours. You don't need to learn
everything, just the basics. Your language probably already has a function which
does the sanitizing for you.
= Never Design a Blacklist-Based Security System =
When you have an alternative, don't design security systems which are default
permit, blacklist-based, or otherwise attempt to enumerate badness. When
Facebook launched Platform, it launched with a blacklist-based CSS filter, which
basically tried to enumerate all the "bad" parts of CSS and filter them out.
This was a poor design choice and lead to basically infinite security holes for
all time.
It is very difficult to enumerate badness in a complex system and badness is
often a moving target. Instead of trying to do this, design whitelist-based
security systems where you list allowed things and reject anything you don't
understand. Assume things are bad until you verify that they're OK.
It's tempting to design blacklist-based systems because they're easier to write
and accept more inputs. In the case of the CSS filter, the product goal was for
users to just be able to use CSS normally and feel like this system was no
different from systems they were familiar with. A whitelist-based system would
reject some valid, safe inputs and create product friction.
But this is a much better world than the alternative, where the blacklist-based
system fails to reject some dangerous inputs and creates //security holes//. It
//also// creates product friction because when you fix those holes you break
existing uses, and that backward-compatibility friction makes it very difficult
to move the system from a blacklist to a whitelist. So you're basically in
trouble no matter what you do, and have a bunch of security holes you need to
unbreak immediately, so you won't even have time to feel sorry for yourself.
Designing blacklist-based security is one of the worst now-vs-future tradeoffs
you can make. See also "The Six Dumbest Ideas in Computer Security":
http://www.ranum.com/security/computer_security/
= Fail Very Loudly when SQL Syntax Errors Occur in Production =
This doesn't apply if you aren't using SQL, but if you are: detect when a query
fails because of a syntax error (in MySQL, it is error 1064). If the failure
happened in production, fail in the loudest way possible. (I implemented this in
2008 at Facebook and had it just email me and a few other people directly. The
system was eventually refined.)
This basically creates a high-signal stream that tells you where you have SQL
injection holes in your application. It will have some false positives and could
theoretically have false negatives, but at Facebook it was pretty high signal
considering how important the signal is.
Of course, the real solution here is to not have SQL injection holes in your
application, ever. As far as I'm aware, this system correctly detected the one
SQL injection hole we had from mid-2008 until I left in 2011, which was in a
hackathon project on an underisolated semi-production tier and didn't use the
query escaping system the rest of the application does.
Hopefully, whatever language you're writing in has good query libraries that
can handle escaping for you. If so, use them. If you're using PHP and don't have
a solution in place yet, the Phabricator implementation of qsprintf() is similar
to Facebook's system and was successful there.
-
-
diff --git a/src/docs/user/userguide/arcanist_lint.diviner b/src/docs/user/userguide/arcanist_lint.diviner
index 67f5513e38..4be3c402df 100644
--- a/src/docs/user/userguide/arcanist_lint.diviner
+++ b/src/docs/user/userguide/arcanist_lint.diviner
@@ -1,198 +1,197 @@
@title Arcanist User Guide: Lint
@group userguide
Guide to lint, linters, and linter configuration.
This is a configuration guide that helps you set up advanced features. If you're
just getting started, you don't need to look at this yet. Instead, start with
the @{article:Arcanist User Guide}.
This guide explains how lint works when configured in an `arc` project. If
you haven't set up a project yet, do that first. For instructions, see
@{article:Arcanist User Guide: Configuring a New Project}.
= Overview =
"Lint" refers to a general class of programming tools which analyze source code
and raise warnings and errors about it. For example, a linter might raise
warnings about syntax errors, uses of undeclared variables, calls to deprecated
functions, spacing and formatting conventions, misuse of scope, implicit
fallthrough in switch statements, missing license headers, use of dangerous
language features, or a variety of other issues.
Integrating lint into your development pipeline has two major benefits:
- you can detect and prevent a large class of programming errors; and
- you can simplify code review by addressing many mechanical and formatting
problems automatically.
When arc is integrated with a lint toolkit, it enables the `arc lint` command
and runs lint on changes during `arc diff`. The user is prompted to fix errors
and warnings before sending their code for review, and lint issues which are
not fixed are visible during review.
There are many lint and static analysis tools available for a wide variety of
languages. Arcanist ships with bindings for many popular tools, and you can
write new bindings fairly easily if you have custom tools.
= Available Linters =
Arcanist ships with bindings for these linters:
- [[http://www.jshint.com/ | JSHint]], a Javascript linter based on JSHint.
See @{class@arcanist:ArcanistJSHintLinter}.
- [[http://pypi.python.org/pypi/pep8 | PEP8]], a Python linter.
See @{class@arcanist:ArcanistPEP8Linter}.
- [[http://pypi.python.org/pypi/pyflakes | Pyflakes]], another Python linter.
See @{class@arcanist:ArcanistPyFlakesLinter}.
- [[http://pypi.python.org/pypi/pylint | Pylint]], yet another Python linter.
See @{class@arcanist:ArcanistPyLintLinter}.
- [[http://pear.php.net/package/PHP_CodeSniffer | PHP CodeSniffer]], a
PHP linter. See @{class@arcanist:ArcanistPhpcsLinter}.
Arcanist also ships with generic bindings which can be configured to parse the
output of a broad range of lint programs:
- @{class@arcanist:ArcanistScriptAndRegexLinter}, which runs a script and
parses its output with a regular expression.
- @{class@arcanist:ArcanistConduitLinter}, which invokes a linter over
Conduit and can allow you to build client/server linters.
Additionally, Arcanist ships with some general purpose linters:
- @{class@arcanist:ArcanistTextLinter}, which enforces basic things like
trailing whitespace, DOS newlines, file encoding, line widths, terminal
newlines, and tab literals.
- @{class@arcanist:ArcanistSpellingLinter}, which can detect common spelling
mistakes.
- @{class@arcanist:ArcanistFilenameLinter}, which can enforce generally
sensible rules about not giving files nonsense names.
- @{class@arcanist:ArcanistLicenseLinter}, which can make sure license
headers are applied to all source files.
- @{class@arcanist:ArcanistNoLintLinter}, which can disable lint for files
marked unlintable.
- @{class@arcanist:ArcanistGeneratedLinter}, which can disable lint for
generated files.
Finally, Arcanist has special-purpose linters:
- @{class@arcanist:ArcanistXHPASTLinter}, the PHP linter used by Phabricator
itself. This linter is powerful, but somewhat rigid (it enforces phutil
rules and isn't very configurable for other rulesets).
- @{class@arcanist:ArcanistPhutilLibraryLinter}, which enforces phutil library
layout rules.
You can add support for new linters in three ways:
- write new bindings and contribute them to the upstream;
- write new bindings and install them alongside Arcanist; or
- use a generic binding like @{class@arcanist:ArcanistScriptAndRegexLinter}
and drive the integration through configuration.
= Configuring Lint =
Arcanist's lint integration involves two major components: linters and lint
engines.
Linters themselves are programs which detect problems in a source file. Usually
a linter is an external script, which Arcanist runs and passes a path to, like
`jshint` or `pep8.py`. The script emits some messages, and Arcanist parses
the output into structured errors. A piece of glue code (like
@{class@arcanist:ArcanistJSHintLinter} or
@{class@arcanist:ArcanistPEP8Linter}) handles calling the external script and
interpreting its output.
Lint engines coordinate linters, and decide which linters should run on which
files. For instance, you might want to run `jshint` on all your `.js` files,
and `pep8.py` on all your `.py` files. And you might not want to lint anything
in `externals/` or `third-party/`, and maybe there are other files which you
want to exclude or apply special rules for.
To configure arc for lint, you specify the name of a lint engine, and possibly
provide some additional configuration. To name a lint engine, set `lint.engine`
in your `.arcconfig` to the name of a class which extends
@{class@arcanist:ArcanistLintEngine}. For more information on `.arcconfig`, see
@{article:Arcanist User Guide: Configuring a New Project}.
You can also set a default lint engine by setting `lint.engine` in your global
user config with `arc set-config lint.engine`, or specify one explicitly with
`arc lint --engine <engine>`.
The available engines are:
- @{class@arcanist:ComprehensiveLintEngine}, which runs a wide array of
linters on many types of files. This is probably of limited use in any real
project because it is overbroad, but is a good starting point for getting
lint doing things.
- @{class@arcanist:ArcanistSingleLintEngine}, which runs a single linter on
every file unconditionally. This can be used with a glue linter like
@{class@arcanist:ArcanistScriptAndRegexLinter} to put engine logic in an
external script.
- A custom engine you write. For most projects, lint rules are sufficiently
specialized that this is the best option. For instructions on writing a
custom lint engine, see
@{article:Arcanist User Guide: Customizing Lint, Unit Tests and Workflows}
and @{class@arcanist:ExampleLintEngine}.
= Using Lint to Improve Code Review =
Code review is most valuable when it's about the big ideas in a change. It is
substantially less valuable when it devolves into nitpicking over style,
formatting, and naming conventions.
The best response to receiving a review request full of style problems is
probably to reject it immediately, point the author at your coding convention
documentation, and ask them to fix it before sending it for review. But even
this is a pretty negative experience for both parties, and less experienced
reviewers sometimes go through the whole review and point out every problem
individually.
Lint can greatly reduce the negativity of this whole experience (and the amount
of time wasted arguing about these things) by enforcing style and formatting
rules automatically. Arcanist supports linters that not only raise warnings
about these problems, but provide patches and fix the problems for the author --
before the code goes to review.
Good linter integration means that code is pretty much mechanically correct by
the time any reviewer sees it, provides clear rules about style which are
especially helpful to new authors, and has the overall effect of pushing
discussion away from stylistic nitpicks and toward useful examination of large
ideas.
It can also provide a straightforward solution to arguments about style:
- If a rule is important enough that it should be enforced, the proponent must
add it to lint so it is automatically detected or fixed in the future and
no one has to argue about it ever again.
- If it's not important enough for them to do the legwork to add it to lint,
they have to stop complaining about it.
This may or may not be an appropriate methodology to adopt at your organization,
but it generally puts the incentives in the right places.
= Philosophy of Lint =
Some general thoughts on how to develop lint effectively, based on building
lint tools at Facebook:
- Don't write regex-based linters to enforce language rules. Use a real parser
or AST-based tool. This is not a domain you can get right at any nontrivial
complexity with raw regexes. That is not a challenge. Just don't do this.
- False positives are pretty bad and should be avoided. You should aim to
implement only rules that have very few false positives, and provide ways to
mark false positives as OK. If running lint always raises 30 warnings about
irrelevant nonsense, it greatly devalues the tool.
- Move toward autocorrect rules. Most linters do not automatically correct
the problems they detect, but Arcanist supports this and it's quite
valuable to have the linter not only say "the convention is to put a space
after comma in a function call" but to fix it for you.
= Next Steps =
Continue by:
- integrating and customizing built-in linters and lint bindings with
@{article:Arcanist User Guide: Customizing Existing Linters}; or
- learning how to add new linters and lint engines with
@{article:Arcanist User Guide: Customizing Lint, Unit Tests and Workflows}.
-
diff --git a/src/docs/user/userguide/differential_test_plans.diviner b/src/docs/user/userguide/differential_test_plans.diviner
index 645578093d..81992097a2 100644
--- a/src/docs/user/userguide/differential_test_plans.diviner
+++ b/src/docs/user/userguide/differential_test_plans.diviner
@@ -1,67 +1,65 @@
@title Differential User Guide: Test Plans
@group userguide
This document describes things you should think about when developing a test
plan.
= Overview =
When you send a revision for review in Differential you must include a test plan
(this can be disabled or made optional in the config). A test plan is a
repeatable list of steps which document what you have done to verify the
behavior of a change. A good test plan convinces a reviewer that you have been
thorough in making sure your change works as intended and has enough detail to
allow someone unfamiliar with your change to verify its behavior.
This document has some common things to think about when developing or reviewing
a test plan. Some of these suggestions might not be applicable to the software
you are writing; they are adapted from Facebook's internal documentation.
= All Changes =
- **Error Handling:** Are errors detected and handled properly? How does your
change deal with error cases? Did you test them and make sure you got the
right error messages and the right behavior? It's important that you test
what happens when things go wrong, not just that the change works if
everything else also works.
- **Service Impact:** How does your change affect services like memcache,
thrift, or databases? Are you adding new cachekeys or queries? Will
this change add a lot of load to services?
- **Performance:** How does your change affect performance? **NOTE**: If
your change is a performance-motivated change, you should include
measurements, profiles or other data in your test plan proving that you have
improved performance.
- **Unit Tests:** Is your change adequately covered by unit tests? Could you
improve test coverage? If you're fixing a bug, did you add a test to prevent
it from happening again? Are the unit tests testing just the code in
question, or would a failure of a database or network service cause your
test to fail?
- **Concurrent Change Robustness:** If you're making a refactoring change, is
it robust against people introducing new calls between the time you started
the change and when you commit it? For example, if you change the parameter
order of some function from ##f(a, b)## to ##f(b, a)## and a new callsite is
introduced in the meantime, could it go unnoticed? How bad would that be?
(Because of this risk, you should almost never make parameter order
changes in weakly typed languages like PHP and Javascript.)
- **Revert Plan:** If your change needs to be reverted and you aren't around,
are any special steps or considerations that the reverter needs to know
about? If there are, make sure they're adequately described in the "Revert
Plan" field so someone without any knowledge of your patch (but with a
general knowledge of the system) can successfully revert your change.
- **Security:** Is your change robust against XSS, CSRF, and injection
attacks? Are you verifying the user has the right capabilities or
permissions? Are you consistently treating user data as untrustworthy? Are
you escaping data properly, and using dangerous functions only
when they are strictly necessary?
- **Architecture:** Is this the right change? Could there be a better way to
solve the problem? Have you talked to (or added as reviewers) domain experts
if you aren't one yourself? What are the limitations of this solution? What
tradeoffs did you make, and why?
= Frontend / User-Facing Changes =
- **Static Resources:** Will your change cause the application to serve more
JS or CSS? Can you use less JS/CSS, or reuse more?
- **Browsers:** Have you tested your change in multiple browsers?
-
-
diff --git a/src/docs/user/userguide/herald.diviner b/src/docs/user/userguide/herald.diviner
index 66272fb325..b2e11d65e4 100644
--- a/src/docs/user/userguide/herald.diviner
+++ b/src/docs/user/userguide/herald.diviner
@@ -1,99 +1,98 @@
@title Herald User Guide
@group userguide
Use Herald to get notified of changes you care about.
= Overview =
Herald allows you to write processing rules that take effect when objects (such
as Differential revisions and commits) are created or updated. For instance, you
might want to get notified every time someone sends out a revision that affects
some file you're interested in, even if they didn't add you as a reviewer.
Herald is less useful for small organizations (where everyone will generally
know most of what's going on) but the usefulness of the application increases
as an organization scales. Once there is too much activity to keep track of it
all, Herald allows you to filter it down so you're only notified of things you
are interested in.
= Global and Personal Rules =
You can create two kinds of Herald rules, //global// and //personal//:
- **Personal Rules** are rules you own, but they can only affect you. Only
you can edit or delete personal rules, but their actions are limited to
adding you to CC, subscribing you, etc.
- **Global Rules** are rules everyone owns, and they can affect anything.
Anyone can edit or delete a global rule, and they can take any action,
including affecting projects and mailing lists.
The general idea is to prevent individuals from controlling rules that affect
shared resources, so if a rule needs to be updated it's not a big deal if the
person who created it is on vacation.
= Rules, Conditions and Actions =
The best way to think of Herald is as a system similar to the mail rules you can
set up in most email clients, to organize mail based on "To", "Subject", etc.
Herald works very similarly, but operates on Phabricator objects (like revisions
and commits) instead of emails.
Every time an object is created or updated, Herald rules are run on it and
the actions for any matching rules are taken.
To create a new Herald rule, choose which type of event you want to act on
(e.g., changes to Differential Revisions, or Commits), and then set a list of
conditions. For example, you might add the condition ##Author is alincoln
(Abraham Lincoln)## to keep track of everything alincoln does. Finally, set
a list of actions to take when the conditions match, like adding yourself to the
CC list.
Now you'll automatically be added to CC any time alincoln creates a revision,
and can keep an eye on what he's up to.
= Available Actions =
Herald rules can take a number of actions. Note that some actions are only
available from Global rules, and others only from Personal rules. Additionally,
not every action is available for every object type (for instance, you can not
trigger an audit based on a Differential revision).
- **Add CC**: Add a user or mailing list to the CC list for the object. For
personal rules, you can only add yourself.
- **Remove CC**: Remove a user or mailing list from the CC list for the
object. For personal rules, you can only remove yourself.
- **Send an Email to**: Send one email, but don't subscribe to other updates.
For personal rules, you can only email yourself.
- **Trigger an Audit**: For commits, trigger an audit request for a project
or user. For personal rules, you can only trigger an audit request to
yourself.
- **Mark with flag**: Flag the object for later review. This action is only
available on personal rules. If an object already has a flag, this action
will not add another flag.
- **Do Nothing**: Don't do anything. This can be used to disable a rule
temporarily, or to create a rule for an "Another Herald rule" condition.
= Testing Rules =
When you've created a rule, use the "Test Console" to test it out. Enter a
revision or commit and Herald will do a dry run against that object, showing
you which rules //would// match had it actually been updated. Dry runs executed
via the test console don't take any actions.
= Advanced Herald =
A few features in Herald are particularly complicated:
- **matches regexp pair**: for Differential revisions, you can set a condition
like "Any changed file content matches regexp pair...". This allows you to
specify two regexes in JSON format. The first will be used to match the
filename of the changed file; the second will be used to match the content.
For example, if you want to match revisions which add or remove calls to
a "muffinize" function, //but only in JS files//, you can set the value
to ##["/\\.js$/", "/muffinize/"]## or similar.
- **Another Herald rule**: you can create Herald rules which depend on other
rules. This can be useful if you need to express a more complicated predicate
than "all" vs "any" allows, or have a common set of conditions which you want
to share between several rules. If a rule is only being used as a group of
conditions, you can set the action to "Do Nothing".
-
diff --git a/src/docs/user/userguide/mail_rules.diviner b/src/docs/user/userguide/mail_rules.diviner
index b5c8253bb0..2000d6d160 100644
--- a/src/docs/user/userguide/mail_rules.diviner
+++ b/src/docs/user/userguide/mail_rules.diviner
@@ -1,82 +1,81 @@
@title User Guide: Managing Phabricator Email
@group userguide
How to effectively manage Phabricator email notifications.
= Overview =
Phabricator uses email as a major notification channel, but the amount of email
it sends can seem overwhelming if you're working on an active team. This
document discusses some strategies for managing email.
By far the best approach to managing mail is to **write mail rules** to
categorize mail. Essentially all modern mail clients allow you to quickly
write sophisticated rules to route, categorize, or delete email.
= Reducing Email =
You can reduce the amount of email you receive by turning off some types of
email in ##Settings -> Email Preferences##. For example, you can turn off email
produced by your own actions (like when you comment on a revision), and some
types of less-important notifications about events.
= Mail Rules =
The best approach to managing mail is to write mail rules. Simply writing rules
to move mail from Differential, Maniphest and Herald to separate folders will
vastly simplify mail management.
Phabricator also sets a large number of headers (see below) which can allow you
to write more sophisticated mail rules.
= Mail Headers =
Phabricator sends a variety of mail headers that can be useful in crafting rules
to route and manage mail.
Headers in plural contain lists. A list containing two items, ##1## and
##15## will generally be formatted like this:
X-Header: <1>, <15>
The intent is to allow you to write a rule which matches against "<1>". If you
just match against "1", you'll incorrectly match "15", but matching "<1>" will
correctly match only "<1>".
Some other headers use a single value but can be presented multiple times.
It is to support e-mail clients which are not able to create rules using regular
expressions or wildcards (namely Outlook).
The headers Phabricator adds to mail are:
- ##X-Phabricator-Sent-This-Message##: this is attached to all mail
Phabricator sends. You can use it to differentiate between email from
Phabricator and replies/forwards of Phabricator mail from human beings.
- ##X-Phabricator-To##: this is attached to all mail Phabricator sends.
It shows the PHIDs of the original "To" line, before any mutation
by the mailer configuration.
- ##X-Phabricator-Cc##: this is attached to all mail Phabricator sends.
It shows the PHIDs of the original "Cc" line, before any mutation by the
mailer configuration.
- ##X-Differential-Author##: this is attached to Differential mail and shows
the revision's author. You can use it to filter mail about your revisions
(or other users' revisions).
- ##X-Differential-Reviewer##: this is attached to Differential mail and
shows the reviewers. You can use it to filter mail about revisions you
are reviewing, versus revisions you are explicitly CC'd on or CC'd as
a result of Herald rules.
- ##X-Differential-Reviewers##: list version of the previous.
- ##X-Differential-CC##: this is attached to Differential mail and shows
the CCs on the revision.
- ##X-Differential-CCs##: list version of the previous.
- ##X-Differential-Explicit-CC##: this is attached to Differential mail and
shows the explicit CCs on the revision (those that were added by humans,
not by Herald).
- ##X-Differential-Explicit-CCs##: list version of the previous.
- ##X-Phabricator-Mail-Tags##: this is attached to some mail and has
a list of descriptors about the mail. (This is fairly new and subject
to some change.)
- ##X-Herald-Rules##: this is attached to some mail and shows Herald rule
IDs which have triggered for the object. You can use this to sort or
categorize mail that has triggered specific rules.
-
diff --git a/src/docs/user/userguide/maniphest_custom.diviner b/src/docs/user/userguide/maniphest_custom.diviner
index 0193d52002..f285fe69d4 100644
--- a/src/docs/user/userguide/maniphest_custom.diviner
+++ b/src/docs/user/userguide/maniphest_custom.diviner
@@ -1,65 +1,64 @@
@title Maniphest User Guide: Adding Custom Fields
@group userguide
How to add custom fields to Maniphest.
= Overview =
Maniphest provides some support for adding new fields to tasks, like an
"cost" field, a "milestone" field, etc.
NOTE: Currently, these fields are somewhat limited. They primarily give you a
structured way to record data on tasks, but there isn't much support for
bringing them into other interfaces (e.g., querying by them, aggregating them,
drawing graphs, etc.). If you have a use case, let us know what you want to do
and maybe we can figure something out. This data is also exposed via the Conduit
API, so you might be able to write your own interface if you want to do
something very custom.
= Simple Field Customization =
If you don't need complicated display controls or sophisticated validation, you
can add simple fields. These allow you to attach things like strings, numbers,
and dropdown menus to the task template.
Customize Maniphest fields by setting `maniphest.custom-field-definitions` in
your configuration. For example, suppose you want to add "Estimated Hours" and
"Actual Hours" fields. To do this, set your configuration like this:
'maniphest.custom-fields' => array(
'mycompany:estimated-hours' => array(
'name' => 'Estimated Hours',
'type' => 'int',
'caption' => 'Estimated number of hours this will take.',
'required' => true,
),
'mycompany:actual-hours' => array(
'name' => 'Actual Hours',
'type' => 'int',
),
)
Each array key must be unique, and is used to organize the internal storage of
the field. These options are available:
- **name**: Display label for the field on the edit and detail interfaces.
- **type**: Field type. The supported field types are:
- **int**: An integer, rendered as a text field.
- **text**: A string, rendered as a text field.
- **bool**: A boolean value, rendered as a checkbox.
- **select**: Allows the user to select from several options, rendered
as a dropdown.
- **remarkup**: A text area which allows the user to enter markup.
- **users**: A typeahead which allows multiple users to be input.
- **date**: A date/time picker.
- **header**: Renders a visual divider which you can use to group fields.
- **caption**: A caption to display underneath the field (optional).
- **required**: True if the user should be required to provide a value.
- **options**: If type is set to **select**, provide options for the dropdown
as a dictionary.
- **default**: Default field value.
- **copy**: When a user creates a task, the UI gives them an option to
"Create Another Similar Task". Some fields from the original task are copied
into the new task, while others are not; by default, fields are not copied.
If you want this field to be copied, specify `true` for the `copy` property.
-
diff --git a/src/docs/user/userguide/phame.diviner b/src/docs/user/userguide/phame.diviner
index 1cf22b1b53..64e17d421f 100644
--- a/src/docs/user/userguide/phame.diviner
+++ b/src/docs/user/userguide/phame.diviner
@@ -1,56 +1,56 @@
@title Phame User Guide
@group userguide
Journal about your thoughts and feelings. Share with others. Profit.
= Overview =
Phame is a simple blogging platform. You can write drafts which only you can
see. Later, you can publish these drafts as posts which anyone who can access
-the Phabricator instance can see. You can also add posts to blogs to increase
+the Phabricator instance can see. You can also add posts to blogs to increase
your distribution.
-Overall, Phame is intended to help an individual spread their message. As
+Overall, Phame is intended to help an individual spread their message. As
such, pertinent design decisions skew towards favoring the individual
-rather than the collective.
+rather than the collective.
= Drafts =
-Drafts are completely private so draft away.
+Drafts are completely private so draft away.
-= Posts =
+= Posts =
Posts are accessible to anyone who has access to the Phabricator instance.
= Blogs =
Blogs are collections of posts. Each blog has associated metadata like
a name, description, and set of bloggers who can add posts to the blog.
Each blogger can also edit metadata about the blog and delete the blog
outright.
Soon, blogs will be useful for powering external websites, like
blog.yourcompany.com
-by making pertinent configuration changes with your DNS authority and
+by making pertinent configuration changes with your DNS authority and
Phabricator instance.
-NOTE: removing a blogger from a given blog does not remove their posts that
+NOTE: removing a blogger from a given blog does not remove their posts that
are already associated with the blog. Rather, it removes their ability to edit
metadata about and add posts to the blog.
= Comment Widgets =
Phame supports comment widgets from Facebook and Disqus. The adminstrator
of the Phabricator instance must properly configure Phabricator to enable
this functionality.
A given comment widget is tied 1:1 with a given post. This means the same
instance of a given comment widget will appear for a given post regardless
of whether that post is being viewed in the context of a blog.
-= Next Steps =
+= Next Steps =
- Phame is extremely new and very basic for now. Give us feedback on
what you'd like to see improve! See @{article:Give Feedback! Get Support!}.
diff --git a/src/infrastructure/__tests__/PhabricatorInfrastructureTestCase.php b/src/infrastructure/__tests__/PhabricatorInfrastructureTestCase.php
index 47cdbf14fe..68822568ae 100644
--- a/src/infrastructure/__tests__/PhabricatorInfrastructureTestCase.php
+++ b/src/infrastructure/__tests__/PhabricatorInfrastructureTestCase.php
@@ -1,104 +1,103 @@
<?php
final class PhabricatorInfrastructureTestCase
extends PhabricatorTestCase {
protected function getPhabricatorTestCaseConfiguration() {
return array(
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
);
}
/**
* This is more of an acceptance test case instead of a unittest. It verifies
* that all symbols can be loaded correctly. It can catch problems like
* missing methods in descendants of abstract base classes.
*/
public function testEverythingImplemented() {
id(new PhutilSymbolLoader())->selectAndLoadSymbols();
}
public function testApplicationsInstalled() {
$all = PhabricatorApplication::getAllApplications();
$installed = PhabricatorApplication::getAllInstalledApplications();
$this->assertEqual(
count($all),
count($installed),
'In test cases, all applications should default to installed.');
}
public function testMySQLAgreesWithUsAboutBMP() {
// Build a string with every BMP character in it, then insert it into MySQL
// and read it back. We expect to get the same string out that we put in,
// demonstrating that strings which pass our BMP checks are also valid in
// MySQL and no silent data truncation will occur.
$buf = '';
for ($ii = 0x01; $ii <= 0x7F; $ii++) {
$buf .= chr($ii);
}
for ($ii = 0xC2; $ii <= 0xDF; $ii++) {
for ($jj = 0x80; $jj <= 0xBF; $jj++) {
$buf .= chr($ii).chr($jj);
}
}
// NOTE: This is \xE0\xA0\xZZ.
for ($ii = 0xE0; $ii <= 0xE0; $ii++) {
for ($jj = 0xA0; $jj <= 0xBF; $jj++) {
for ($kk = 0x80; $kk <= 0xBF; $kk++) {
$buf .= chr($ii).chr($jj).chr($kk);
}
}
}
// NOTE: This is \xE1\xZZ\xZZ through \xEF\xZZ\xZZ.
for ($ii = 0xE1; $ii <= 0xEF; $ii++) {
for ($jj = 0x80; $jj <= 0xBF; $jj++) {
for ($kk = 0x80; $kk <= 0xBF; $kk++) {
$buf .= chr($ii).chr($jj).chr($kk);
}
}
}
$this->assertEqual(194431, strlen($buf));
$this->assertEqual(true, phutil_is_utf8_with_only_bmp_characters($buf));
$write = id(new HarbormasterScratchTable())
->setData('all.utf8.bmp')
->setBigData($buf)
->save();
$read = id(new HarbormasterScratchTable())->load($write->getID());
$this->assertEqual($buf, $read->getBigData());
}
public function testRejectMySQLBMPQueries() {
$table = new HarbormasterScratchTable();
$conn_r = $table->establishConnection('w');
$snowman = "\xE2\x98\x83";
$gclef = "\xF0\x9D\x84\x9E";
qsprintf($conn_r, 'SELECT %B', $snowman);
qsprintf($conn_r, 'SELECT %s', $snowman);
qsprintf($conn_r, 'SELECT %B', $gclef);
$caught = null;
try {
qsprintf($conn_r, 'SELECT %s', $gclef);
} catch (AphrontQueryCharacterSetException $ex) {
$caught = $ex;
}
$this->assertEqual(
true,
($caught instanceof AphrontQueryCharacterSetException));
}
}
-
diff --git a/src/infrastructure/celerity/CeleritySpriteGenerator.php b/src/infrastructure/celerity/CeleritySpriteGenerator.php
index 95102cf5ce..42e33c9ca6 100644
--- a/src/infrastructure/celerity/CeleritySpriteGenerator.php
+++ b/src/infrastructure/celerity/CeleritySpriteGenerator.php
@@ -1,867 +1,865 @@
<?php
final class CeleritySpriteGenerator {
public function buildIconSheet() {
$icons = $this->getDirectoryList('icons_1x');
$colors = array(
'',
'grey',
'white',
);
$scales = array(
'1x' => 1,
'2x' => 2,
);
$template = id(new PhutilSprite())
->setSourceSize(14, 14);
$sprites = array();
foreach ($colors as $color) {
foreach ($icons as $icon) {
$prefix = 'icons_';
if (strlen($color)) {
$prefix .= $color.'_';
}
$suffix = '';
if (strlen($color)) {
$suffix = '-'.$color;
}
$sprite = id(clone $template)
->setName('icons-'.$icon.$suffix);
$tcss = array();
$tcss[] = '.icons-'.$icon.$suffix;
if ($color == 'white') {
$tcss[] = '.device-desktop .phabricator-action-view:hover '.
'.icons-'.$icon;
$tcss[] = '.device-desktop .phui-list-sidenav '.
'.phui-list-item-href:hover .icons-'.$icon;
}
$sprite->setTargetCSS(implode(', ', $tcss));
foreach ($scales as $scale_key => $scale) {
$path = $this->getPath($prefix.$scale_key.'/'.$icon.'.png');
$sprite->setSourceFile($path, $scale);
}
$sprites[] = $sprite;
}
}
$remarkup_icons = $this->getDirectoryList('remarkup_1x');
foreach ($remarkup_icons as $icon) {
$prefix = 'remarkup_';
// Strip 'text_' from these file names.
$class_name = substr($icon, 5);
if ($class_name == 'fullscreen_off') {
$tcss = '.remarkup-control-fullscreen-mode .remarkup-assist-fullscreen';
} else {
$tcss = '.remarkup-assist-'.$class_name;
}
$sprite = id(clone $template)
->setName('remarkup-assist-'.$icon)
->setTargetCSS($tcss);
foreach ($scales as $scale_key => $scale) {
$path = $this->getPath($prefix.$scale_key.'/'.$icon.'.png');
$sprite->setSourceFile($path, $scale);
}
$sprites[] = $sprite;
}
$sheet = $this->buildSheet('icons', true);
$sheet->setScales($scales);
foreach ($sprites as $sprite) {
$sheet->addSprite($sprite);
}
return $sheet;
}
public function buildActionsSheet() {
$icons = $this->getDirectoryList('actions_white_1x');
$colors = array(
'dark',
'grey',
'white',
);
$scales = array(
'1x' => 1,
'2x' => 2,
);
$template = id(new PhutilSprite())
->setSourceSize(24, 24);
$sprites = array();
foreach ($colors as $color) {
foreach ($icons as $icon) {
$prefix = 'actions_';
if (strlen($color)) {
$prefix .= $color.'_';
}
$suffix = '';
if (strlen($color)) {
$suffix = '-'.$color;
}
$sprite = id(clone $template)
->setName('actions-'.$icon.$suffix);
$tcss = array();
$tcss[] = '.actions-'.$icon.$suffix;
if ($color == 'dark') {
$tcss[] = '.device-desktop '.
'.actions-'.$icon.'-grey.phui-icon-view:hover';
}
$sprite->setTargetCSS(implode(', ', $tcss));
foreach ($scales as $scale_key => $scale) {
$path = $this->getPath($prefix.$scale_key.'/'.$icon.'.png');
$sprite->setSourceFile($path, $scale);
}
$sprites[] = $sprite;
}
}
$sheet = $this->buildSheet('actions', true);
$sheet->setScales($scales);
foreach ($sprites as $sprite) {
$sheet->addSprite($sprite);
}
return $sheet;
}
public function buildMiniconsSheet() {
$icons = $this->getDirectoryList('minicons_white_1x');
$colors = array(
'white',
'dark',
);
$scales = array(
'1x' => 1,
'2x' => 2,
);
$template = id(new PhutilSprite())
->setSourceSize(16, 16);
$sprites = array();
foreach ($colors as $color) {
foreach ($icons as $icon) {
$prefix = 'minicons_';
if (strlen($color)) {
$prefix .= $color.'_';
}
$suffix = '';
if (strlen($color)) {
$suffix = '-'.$color;
}
$sprite = id(clone $template)
->setName('minicons-'.$icon.$suffix);
$sprite->setTargetCSS('.minicons-'.$icon.$suffix);
foreach ($scales as $scale_key => $scale) {
$path = $this->getPath($prefix.$scale_key.'/'.$icon.'.png');
$sprite->setSourceFile($path, $scale);
}
$sprites[] = $sprite;
}
}
$sheet = $this->buildSheet('minicons', true);
$sheet->setScales($scales);
foreach ($sprites as $sprite) {
$sheet->addSprite($sprite);
}
return $sheet;
}
public function buildMenuSheet() {
$sprites = array();
$sources = array(
'seen_read_all' => array(
'x' => 18,
'y' => 18,
'css' =>
'.alert-notifications .phabricator-main-menu-alert-icon',
),
'seen_have_unread' => array(
'x' => 18,
'y' => 18,
'css' =>
'.alert-notifications:hover .phabricator-main-menu-alert-icon',
),
'unseen_any' => array(
'x' => 18,
'y' => 18,
'css' =>
'.alert-notifications.alert-unread .phabricator-main-menu-alert-icon',
),
'arrow-right' => array(
'x' => 9,
'y' => 31,
'css' => '.phabricator-crumb-divider',
),
'search' => array(
'x' => 24,
'y' => 24,
'css' => '.menu-icon-search',
),
'search_blue' => array(
'x' => 24,
'y' => 24,
'css' => '.menu-icon-search-blue',
),
'new' => array(
'x' => 24,
'y' => 24,
'css' => '.menu-icon-new',
),
'new_blue' => array(
'x' => 24,
'y' => 24,
'css' => '.menu-icon-new-blue',
),
'app' => array(
'x' => 24,
'y' => 24,
'css' => '.menu-icon-app',
),
'app_blue' => array(
'x' => 24,
'y' => 24,
'css' => '.menu-icon-app-blue',
),
'logo' => array(
'x' => 149,
'y' => 26,
'css' => '.phabricator-main-menu-logo-image',
),
'conf-off' => array(
'x' => 18,
'y' => 18,
'css' =>
'.alert-notifications .phabricator-main-menu-message-icon',
),
'conf-hover' => array(
'x' => 18,
'y' => 18,
'css' =>
'.alert-notifications:hover .phabricator-main-menu-message-icon',
),
'conf-unseen' => array(
'x' => 18,
'y' => 18,
'css' =>
'.alert-notifications.message-unread '.
'.phabricator-main-menu-message-icon',
),
);
$scales = array(
'1x' => 1,
'2x' => 2,
);
$template = new PhutilSprite();
foreach ($sources as $name => $spec) {
$sprite = id(clone $template)
->setName($name)
->setSourceSize($spec['x'], $spec['y'])
->setTargetCSS($spec['css']);
foreach ($scales as $scale_name => $scale) {
$path = 'menu_'.$scale_name.'/'.$name.'.png';
$path = $this->getPath($path);
$sprite->setSourceFile($path, $scale);
}
$sprites[] = $sprite;
}
$sheet = $this->buildSheet('menu', true);
$sheet->setScales($scales);
foreach ($sprites as $sprite) {
$sheet->addSprite($sprite);
}
return $sheet;
}
public function buildTokenSheet() {
$icons = $this->getDirectoryList('tokens_1x');
$scales = array(
'1x' => 1,
'2x' => 2,
);
$template = id(new PhutilSprite())
->setSourceSize(16, 16);
$sprites = array();
$prefix = 'tokens_';
foreach ($icons as $icon) {
$sprite = id(clone $template)
->setName('tokens-'.$icon)
->setTargetCSS('.tokens-'.$icon);
foreach ($scales as $scale_key => $scale) {
$path = $this->getPath($prefix.$scale_key.'/'.$icon.'.png');
$sprite->setSourceFile($path, $scale);
}
$sprites[] = $sprite;
}
$sheet = $this->buildSheet('tokens', true);
$sheet->setScales($scales);
foreach ($sprites as $sprite) {
$sheet->addSprite($sprite);
}
return $sheet;
}
public function buildButtonBarSheet() {
$icons = $this->getDirectoryList('button_bar_1x');
$scales = array(
'1x' => 1,
'2x' => 2,
);
$template = id(new PhutilSprite())
->setSourceSize(14, 14);
$sprites = array();
$prefix = 'button_bar_';
foreach ($icons as $icon) {
$sprite = id(clone $template)
->setName('buttonbar-'.$icon)
->setTargetCSS('.buttonbar-'.$icon);
foreach ($scales as $scale_key => $scale) {
$path = $this->getPath($prefix.$scale_key.'/'.$icon.'.png');
$sprite->setSourceFile($path, $scale);
}
$sprites[] = $sprite;
}
$sheet = $this->buildSheet('buttonbar', true);
$sheet->setScales($scales);
foreach ($sprites as $sprite) {
$sheet->addSprite($sprite);
}
return $sheet;
}
public function buildProjectsSheet() {
$icons = $this->getDirectoryList('projects_1x');
$scales = array(
'1x' => 1,
'2x' => 2,
);
$template = id(new PhutilSprite())
->setSourceSize(50, 50);
$sprites = array();
$prefix = 'projects-';
foreach ($icons as $icon) {
$sprite = id(clone $template)
->setName($prefix.$icon)
->setTargetCSS('.'.$prefix.$icon);
foreach ($scales as $scale_key => $scale) {
$path = $this->getPath('projects_'.$scale_key.'/'.$icon.'.png');
$sprite->setSourceFile($path, $scale);
}
$sprites[] = $sprite;
}
$sheet = $this->buildSheet('projects', true);
$sheet->setScales($scales);
foreach ($sprites as $sprite) {
$sheet->addSprite($sprite);
}
return $sheet;
}
public function buildPaymentsSheet() {
$icons = $this->getDirectoryList('payments_2x');
$scales = array(
'2x' => 1,
);
$template = id(new PhutilSprite())
->setSourceSize(60, 32);
$sprites = array();
$prefix = 'payments_';
foreach ($icons as $icon) {
$sprite = id(clone $template)
->setName('payments-'.$icon)
->setTargetCSS('.payments-'.$icon);
foreach ($scales as $scale_key => $scale) {
$path = $this->getPath($prefix.$scale_key.'/'.$icon.'.png');
$sprite->setSourceFile($path, $scale);
}
$sprites[] = $sprite;
}
$sheet = $this->buildSheet('payments', true);
$sheet->setScales($scales);
foreach ($sprites as $sprite) {
$sheet->addSprite($sprite);
}
return $sheet;
}
public function buildConpherenceSheet() {
$name = 'conpherence';
$icons = $this->getDirectoryList($name.'_1x');
$scales = array(
'1x' => 1,
'2x' => 2,
);
$template = id(new PhutilSprite())
->setSourceSize(32, 32);
$sprites = array();
foreach ($icons as $icon) {
$color = preg_match('/_on/', $icon) ? 'on' : 'off';
$prefix = $name.'_';
$sprite = id(clone $template)
->setName($prefix.$icon);
$tcss = array();
$tcss[] = '.'.$prefix.$icon;
if ($color == 'on') {
$class = str_replace('_on', '_off', $prefix.$icon);
$tcss[] = '.device-desktop .'.$class.':hover ';
}
$sprite->setTargetCSS(implode(', ', $tcss));
foreach ($scales as $scale_key => $scale) {
$path = $this->getPath($prefix.$scale_key.'/'.$icon.'.png');
$sprite->setSourceFile($path, $scale);
}
$sprites[] = $sprite;
}
$sheet = $this->buildSheet($name, true);
$sheet->setScales($scales);
foreach ($sprites as $sprite) {
$sheet->addSprite($sprite);
}
return $sheet;
}
public function buildDocsSheet() {
$icons = $this->getDirectoryList('docs_1x');
$scales = array(
'1x' => 1,
'2x' => 2,
);
$template = id(new PhutilSprite())
->setSourceSize(32, 32);
$sprites = array();
$prefix = 'docs_';
foreach ($icons as $icon) {
$sprite = id(clone $template)
->setName($prefix.$icon)
->setTargetCSS('.'.$prefix.$icon);
foreach ($scales as $scale_key => $scale) {
$path = $this->getPath($prefix.$scale_key.'/'.$icon.'.png');
$sprite->setSourceFile($path, $scale);
}
$sprites[] = $sprite;
}
$sheet = $this->buildSheet('docs', true);
$sheet->setScales($scales);
foreach ($sprites as $sprite) {
$sheet->addSprite($sprite);
}
return $sheet;
}
public function buildLoginSheet() {
$icons = $this->getDirectoryList('login_1x');
$scales = array(
'1x' => 1,
'2x' => 2,
);
$template = id(new PhutilSprite())
->setSourceSize(34, 34);
$sprites = array();
$prefix = 'login_';
foreach ($icons as $icon) {
$sprite = id(clone $template)
->setName('login-'.$icon)
->setTargetCSS('.login-'.$icon);
foreach ($scales as $scale_key => $scale) {
$path = $this->getPath($prefix.$scale_key.'/'.$icon.'.png');
$sprite->setSourceFile($path, $scale);
}
$sprites[] = $sprite;
}
$sheet = $this->buildSheet('login', true);
$sheet->setScales($scales);
foreach ($sprites as $sprite) {
$sheet->addSprite($sprite);
}
return $sheet;
}
public function buildStatusSheet() {
$icons = $this->getDirectoryList('status_1x');
$scales = array(
'1x' => 1,
'2x' => 2,
);
$template = id(new PhutilSprite())
->setSourceSize(14, 14);
$sprites = array();
$prefix = 'status_';
$extra_css = array(
'policy-custom-white' =>
', .dropdown-menu-item:hover .status-policy-custom',
'policy-all-white' =>
', .dropdown-menu-item:hover .status-policy-all',
'policy-unknown-white' =>
', .dropdown-menu-item:hover .status-policy-unknown',
'policy-admin-white' =>
', .dropdown-menu-item:hover .status-policy-admin',
'policy-public-white' =>
', .dropdown-menu-item:hover .status-policy-public',
'policy-project-white' =>
', .dropdown-menu-item:hover .status-policy-project',
'policy-noone-white' =>
', .dropdown-menu-item:hover .status-policy-noone',
);
foreach ($icons as $icon) {
$sprite = id(clone $template)
->setName('status-'.$icon)
->setTargetCSS('.status-'.$icon.idx($extra_css, $icon));
foreach ($scales as $scale_key => $scale) {
$path = $this->getPath($prefix.$scale_key.'/'.$icon.'.png');
$sprite->setSourceFile($path, $scale);
}
$sprites[] = $sprite;
}
$sheet = $this->buildSheet('status', true);
$sheet->setScales($scales);
foreach ($sprites as $sprite) {
$sheet->addSprite($sprite);
}
return $sheet;
}
public function buildGradientSheet() {
$gradients = $this->getDirectoryList('gradients');
$template = new PhutilSprite();
$unusual_heights = array(
'dark-menu-label' => 25,
'breadcrumbs' => 31,
'menu-label' => 24,
'red-header' => 70,
'blue-header' => 70,
'green-header' => 70,
'yellow-header' => 70,
'grey-header' => 70,
'dark-grey-header' => 70,
'lightblue-header' => 240,
);
$extra_css = array(
'dark-menu-label' =>
', .phabricator-dark-menu .phui-list-item-type-label',
'menu-label' =>
', .phabricator-side-menu .phui-list-item-type-label',
);
$sprites = array();
foreach ($gradients as $gradient) {
$path = $this->getPath('gradients/'.$gradient.'.png');
$sprite = id(clone $template)
->setName('gradient-'.$gradient)
->setSourceFile($path)
->setTargetCSS('.gradient-'.$gradient.idx($extra_css, $gradient));
$sprite->setSourceSize(4, idx($unusual_heights, $gradient, 26));
$sprites[] = $sprite;
}
$sheet = $this->buildSheet(
'gradient',
false,
PhutilSpriteSheet::TYPE_REPEAT_X,
', .phabricator-dark-menu .phui-list-item-type-label, '.
'.phabricator-side-menu .phui-list-item-type-label');
foreach ($sprites as $sprite) {
$sheet->addSprite($sprite);
}
return $sheet;
}
public function buildMainHeaderSheet() {
$gradients = $this->getDirectoryList('main_header');
$template = new PhutilSprite();
$sprites = array();
foreach ($gradients as $gradient) {
$path = $this->getPath('main_header/'.$gradient.'.png');
$sprite = id(clone $template)
->setName('main-header-'.$gradient)
->setSourceFile($path)
->setTargetCSS('.main-header-'.$gradient);
$sprite->setSourceSize(6, 44);
$sprites[] = $sprite;
}
$sheet = $this->buildSheet('main-header',
false,
PhutilSpriteSheet::TYPE_REPEAT_X);
foreach ($sprites as $sprite) {
$sheet->addSprite($sprite);
}
return $sheet;
}
public function buildAppsSheet() {
return $this->buildAppsSheetVariant(1);
}
public function buildAppsLargeSheet() {
return $this->buildAppsSheetVariant(2);
}
public function buildAppsXLargeSheet() {
return $this->buildAppsSheetVariant(3);
}
private function buildAppsSheetVariant($variant) {
if ($variant == 1) {
$scales = array(
'1x' => 1,
'2x' => 2,
);
$variant_name = 'apps';
$variant_short = '';
$size_x = 14;
$size_y = 14;
$colors = array(
'dark' => 'dark',
'white' => 'white',
);
} else if ($variant == 2) {
$scales = array(
'2x' => 1,
'4x' => 2,
);
$variant_name = 'apps-large';
$variant_short = '-large';
$size_x = 28;
$size_y = 28;
$colors = array(
'light' => 'lb',
'dark' => 'dark',
'blue' => 'blue',
'white' => 'white',
);
} else {
$scales = array(
'4x' => 1,
);
$variant_name = 'apps-xlarge';
$variant_short = '-xlarge';
$size_x = 56;
$size_y = 56;
$colors = array(
'dark' => 'dark',
/*
TODO: These are available but not currently used.
'blue' => 'blue',
'light' => 'lb',
*/
);
}
$apps = $this->getDirectoryList('apps_dark_1x');
$template = id(new PhutilSprite())
->setSourceSize($size_x, $size_y);
$sprites = array();
foreach ($apps as $app) {
foreach ($colors as $color => $color_path) {
$css = '.apps-'.$app.'-'.$color.$variant_short;
if ($color == 'blue' && $variant_name == 'apps-large') {
$css .= ', .phabricator-crumb-view:hover .apps-'.$app.'-dark-large';
}
if ($color == 'white' && $variant == 1) {
$css .= ', .phui-list-item-href:hover .apps-'.$app.'-dark';
}
$sprite = id(clone $template)
->setName('apps-'.$app.'-'.$color.$variant_short)
->setTargetCSS($css);
foreach ($scales as $scale_name => $scale) {
$path = $this->getPath(
'apps_'.$color_path.'_'.$scale_name.'/'.$app.'.png');
$sprite->setSourceFile($path, $scale);
}
$sprites[] = $sprite;
}
}
$sheet = $this->buildSheet($variant_name, count($scales) > 1);
$sheet->setScales($scales);
foreach ($sprites as $sprite) {
$sheet->addSprite($sprite);
}
return $sheet;
}
private function getPath($to_path = null) {
$root = dirname(phutil_get_library_root('phabricator'));
return $root.'/resources/sprite/'.$to_path;
}
private function getDirectoryList($dir) {
$path = $this->getPath($dir);
$result = array();
$images = Filesystem::listDirectory($path, $include_hidden = false);
foreach ($images as $image) {
if (!preg_match('/\.png$/', $image)) {
throw new Exception(
"Expected file '{$image}' in '{$path}' to be a sprite source ".
"ending in '.png'.");
}
$result[] = substr($image, 0, -4);
}
return $result;
}
private function buildSheet(
$name,
$has_retina,
$type = null,
$extra_css = '') {
$sheet = new PhutilSpriteSheet();
$at = '@';
switch ($type) {
case PhutilSpriteSheet::TYPE_STANDARD:
default:
$type = PhutilSpriteSheet::TYPE_STANDARD;
$repeat_rule = 'no-repeat';
break;
case PhutilSpriteSheet::TYPE_REPEAT_X:
$repeat_rule = 'repeat-x';
break;
case PhutilSpriteSheet::TYPE_REPEAT_Y:
$repeat_rule = 'repeat-y';
break;
}
$retina_rules = null;
if ($has_retina) {
$retina_rules = <<<EOCSS
@media
only screen and (min-device-pixel-ratio: 1.5),
only screen and (-webkit-min-device-pixel-ratio: 1.5) {
.sprite-{$name}{$extra_css} {
background-image: url(/rsrc/image/sprite-{$name}-X2.png);
background-size: {X}px {Y}px;
}
}
EOCSS;
}
$sheet->setSheetType($type);
$sheet->setCSSHeader(<<<EOCSS
/**
* @provides sprite-{$name}-css
* {$at}generated
*/
.sprite-{$name}{$extra_css} {
background-image: url(/rsrc/image/sprite-{$name}.png);
background-repeat: {$repeat_rule};
}
{$retina_rules}
EOCSS
);
return $sheet;
}
}
-
-
diff --git a/src/infrastructure/daemon/bot/adapter/PhabricatorBotBaseStreamingProtocolAdapter.php b/src/infrastructure/daemon/bot/adapter/PhabricatorBotBaseStreamingProtocolAdapter.php
index 2e81dee3ed..1456085eba 100644
--- a/src/infrastructure/daemon/bot/adapter/PhabricatorBotBaseStreamingProtocolAdapter.php
+++ b/src/infrastructure/daemon/bot/adapter/PhabricatorBotBaseStreamingProtocolAdapter.php
@@ -1,163 +1,162 @@
<?php
abstract class PhabricatorBotBaseStreamingProtocolAdapter
extends PhabricatorBaseProtocolAdapter {
private $readBuffers;
private $authtoken;
private $server;
private $readHandles;
private $multiHandle;
private $active;
private $inRooms = array();
public function getServiceName() {
$uri = new PhutilURI($this->server);
return $uri->getDomain();
}
public function connect() {
$this->server = $this->getConfig('server');
$this->authtoken = $this->getConfig('authtoken');
$rooms = $this->getConfig('join');
// First, join the room
if (!$rooms) {
throw new Exception("Not configured to join any rooms!");
}
$this->readBuffers = array();
// Set up our long poll in a curl multi request so we can
// continue running while it executes in the background
$this->multiHandle = curl_multi_init();
$this->readHandles = array();
foreach ($rooms as $room_id) {
$this->joinRoom($room_id);
// Set up the curl stream for reading
$url = $this->buildStreamingUrl($room_id);
$this->readHandle[$url] = curl_init();
curl_setopt($this->readHandle[$url], CURLOPT_URL, $url);
curl_setopt($this->readHandle[$url], CURLOPT_RETURNTRANSFER, true);
curl_setopt($this->readHandle[$url], CURLOPT_FOLLOWLOCATION, 1);
curl_setopt(
$this->readHandle[$url],
CURLOPT_USERPWD,
$this->authtoken.':x');
curl_setopt(
$this->readHandle[$url],
CURLOPT_HTTPHEADER,
array("Content-type: application/json"));
curl_setopt(
$this->readHandle[$url],
CURLOPT_WRITEFUNCTION,
array($this, 'read'));
curl_setopt($this->readHandle[$url], CURLOPT_BUFFERSIZE, 128);
curl_setopt($this->readHandle[$url], CURLOPT_TIMEOUT, 0);
curl_multi_add_handle($this->multiHandle, $this->readHandle[$url]);
// Initialize read buffer
$this->readBuffers[$url] = '';
}
$this->active = null;
$this->blockingMultiExec();
}
protected function joinRoom($room_id) {
// Optional hook, by default, do nothing
}
// This is our callback for the background curl multi-request.
// Puts the data read in on the readBuffer for processing.
private function read($ch, $data) {
$info = curl_getinfo($ch);
$length = strlen($data);
$this->readBuffers[$info['url']] .= $data;
return $length;
}
private function blockingMultiExec() {
do {
$status = curl_multi_exec($this->multiHandle, $this->active);
} while ($status == CURLM_CALL_MULTI_PERFORM);
// Check for errors
if ($status != CURLM_OK) {
throw new Exception(
"Phabricator Bot had a problem reading from stream.");
}
}
public function getNextMessages($poll_frequency) {
$messages = array();
if (!$this->active) {
throw new Exception("Phabricator Bot stopped reading from stream.");
}
// Prod our http request
curl_multi_select($this->multiHandle, $poll_frequency);
$this->blockingMultiExec();
// Process anything waiting on the read buffer
while ($m = $this->processReadBuffer()) {
$messages[] = $m;
}
return $messages;
}
private function processReadBuffer() {
foreach ($this->readBuffers as $url => &$buffer) {
$until = strpos($buffer, "}\r");
if ($until == false) {
continue;
}
$message = substr($buffer, 0, $until + 1);
$buffer = substr($buffer, $until + 2);
$m_obj = json_decode($message, true);
if ($message = $this->processMessage($m_obj)) {
return $message;
}
}
// If we're here, there's nothing to process
return false;
}
protected function performPost($endpoint, $data = null) {
$uri = new PhutilURI($this->server);
$uri->setPath($endpoint);
$payload = json_encode($data);
list($output) = id(new HTTPSFuture($uri))
->setMethod('POST')
->addHeader('Content-Type', 'application/json')
->addHeader('Authorization', $this->getAuthorizationHeader())
->setData($payload)
->resolvex();
$output = trim($output);
if (strlen($output)) {
return json_decode($output, true);
}
return true;
}
protected function getAuthorizationHeader() {
return 'Basic '.base64_encode($this->authtoken.':x');
}
abstract protected function buildStreamingUrl($channel);
abstract protected function processMessage($raw_object);
}
-
diff --git a/src/infrastructure/events/PhabricatorEvent.php b/src/infrastructure/events/PhabricatorEvent.php
index d92d7b6894..03116cdd9e 100644
--- a/src/infrastructure/events/PhabricatorEvent.php
+++ b/src/infrastructure/events/PhabricatorEvent.php
@@ -1,49 +1,44 @@
<?php
/**
* @group events
*/
final class PhabricatorEvent extends PhutilEvent {
private $user;
private $aphrontRequest;
private $conduitRequest;
public function __construct($type, array $data = array()) {
parent::__construct($type, $data);
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function getUser() {
return $this->user;
}
public function setAphrontRequest(AphrontRequest $aphront_request) {
$this->aphrontRequest = $aphront_request;
return $this;
}
public function getAphrontRequest() {
return $this->aphrontRequest;
}
public function setConduitRequest(ConduitAPIRequest $conduit_request) {
$this->conduitRequest = $conduit_request;
return $this;
}
public function getConduitRequest() {
return $this->conduitRequest;
}
}
-
-
-
-
-
diff --git a/src/infrastructure/events/PhabricatorEventListener.php b/src/infrastructure/events/PhabricatorEventListener.php
index 5a0c8c1986..211cc1f544 100644
--- a/src/infrastructure/events/PhabricatorEventListener.php
+++ b/src/infrastructure/events/PhabricatorEventListener.php
@@ -1,56 +1,51 @@
<?php
abstract class PhabricatorEventListener extends PhutilEventListener {
private $application;
public function setApplication(PhabricatorApplication $application) {
$this->application = $application;
return $this;
}
public function getApplication() {
return $this->application;
}
public function hasApplicationCapability(
PhabricatorUser $viewer,
$capability) {
return PhabricatorPolicyFilter::hasCapability(
$viewer,
$this->getApplication(),
$capability);
}
public function canUseApplication(PhabricatorUser $viewer) {
return $this->hasApplicationCapability(
$viewer,
PhabricatorPolicyCapability::CAN_VIEW);
}
protected function addActionMenuItems(PhutilEvent $event, $items) {
if ($event->getType() !== PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS) {
throw new Exception("Not an action menu event!");
}
if (!$items) {
return;
}
if (!is_array($items)) {
$items = array($items);
}
$event_actions = $event->getValue('actions');
foreach ($items as $item) {
$event_actions[] = $item;
}
$event->setValue('actions', $event_actions);
}
}
-
-
-
-
-
diff --git a/src/infrastructure/events/PhabricatorExampleEventListener.php b/src/infrastructure/events/PhabricatorExampleEventListener.php
index 03c83d7146..4cbbb6b0bb 100644
--- a/src/infrastructure/events/PhabricatorExampleEventListener.php
+++ b/src/infrastructure/events/PhabricatorExampleEventListener.php
@@ -1,36 +1,31 @@
<?php
/**
* Example event listener. For details about installing Phabricator event hooks,
* refer to @{article:Events User Guide: Installing Event Listeners}.
*
* @group events
*/
final class PhabricatorExampleEventListener extends PhabricatorEventListener {
public function register() {
// When your listener is installed, its register() method will be called.
// You should listen() to any events you are interested in here.
$this->listen(PhabricatorEventType::TYPE_TEST_DIDRUNTEST);
}
public function handleEvent(PhutilEvent $event) {
// When an event you have called listen() for in your register() method
// occurs, this method will be invoked. You should respond to the event.
// In this case, we just echo a message out so the event test script will
// do something visible.
$console = PhutilConsole::getConsole();
$console->writeOut(
"PhabricatorExampleEventListener got test event at %d\n",
$event->getValue('time'));
}
}
-
-
-
-
-
diff --git a/src/view/control/AphrontTableView.php b/src/view/control/AphrontTableView.php
index 17fca10d5a..e698421c88 100644
--- a/src/view/control/AphrontTableView.php
+++ b/src/view/control/AphrontTableView.php
@@ -1,323 +1,322 @@
<?php
final class AphrontTableView extends AphrontView {
protected $data;
protected $headers;
protected $shortHeaders;
protected $rowClasses = array();
protected $columnClasses = array();
protected $cellClasses = array();
protected $zebraStripes = true;
protected $noDataString;
protected $className;
protected $columnVisibility = array();
private $deviceVisibility = array();
protected $sortURI;
protected $sortParam;
protected $sortSelected;
protected $sortReverse;
protected $sortValues;
private $deviceReadyTable;
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 setCellClasses(array $cell_classes) {
$this->cellClasses = $cell_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 setDeviceVisibility(array $device_visibility) {
$this->deviceVisibility = $device_visibility;
return $this;
}
public function setDeviceReadyTable($ready) {
$this->deviceReadyTable = $ready;
return $this;
}
public function setShortHeaders(array $short_headers) {
$this->shortHeaders = $short_headers;
return $this;
}
/**
* Parse a sorting parameter:
*
* list($sort, $reverse) = AphrontTableView::parseSortParam($sort_param);
*
* @param string Sort request parameter.
* @return pair Sort value, sort direction.
*/
public static function parseSort($sort) {
return array(ltrim($sort, '-'), preg_match('/^-/', $sort));
}
public function makeSortable(
PhutilURI $base_uri,
$param,
$selected,
$reverse,
array $sort_values) {
$this->sortURI = $base_uri;
$this->sortParam = $param;
$this->sortSelected = $selected;
$this->sortReverse = $reverse;
$this->sortValues = array_values($sort_values);
return $this;
}
public function render() {
require_celerity_resource('aphront-table-view-css');
$table = array();
$col_classes = array();
foreach ($this->columnClasses as $key => $class) {
if (strlen($class)) {
$col_classes[] = $class;
} else {
$col_classes[] = null;
}
}
$visibility = array_values($this->columnVisibility);
$device_visibility = array_values($this->deviceVisibility);
$headers = $this->headers;
$short_headers = $this->shortHeaders;
$sort_values = $this->sortValues;
if ($headers) {
while (count($headers) > count($visibility)) {
$visibility[] = true;
}
while (count($headers) > count($device_visibility)) {
$device_visibility[] = true;
}
while (count($headers) > count($short_headers)) {
$short_headers[] = null;
}
while (count($headers) > count($sort_values)) {
$sort_values[] = null;
}
$tr = array();
foreach ($headers as $col_num => $header) {
if (!$visibility[$col_num]) {
continue;
}
$classes = array();
if (!empty($col_classes[$col_num])) {
$classes[] = $col_classes[$col_num];
}
if (empty($device_visiblity[$col_num])) {
$classes[] = 'aphront-table-nodevice';
}
if ($sort_values[$col_num] !== null) {
$classes[] = 'aphront-table-view-sortable';
$sort_value = $sort_values[$col_num];
$sort_glyph_class = 'aphront-table-down-sort';
if ($sort_value == $this->sortSelected) {
if ($this->sortReverse) {
$sort_glyph_class = 'aphront-table-up-sort';
} else if (!$this->sortReverse) {
$sort_value = '-'.$sort_value;
}
$classes[] = 'aphront-table-view-sortable-selected';
}
$sort_glyph = phutil_tag(
'span',
array(
'class' => $sort_glyph_class,
),
'');
$header = phutil_tag(
'a',
array(
'href' => $this->sortURI->alter($this->sortParam, $sort_value),
'class' => 'aphront-table-view-sort-link',
),
array(
$header,
' ',
$sort_glyph,
));
}
if ($classes) {
$class = implode(' ', $classes);
} else {
$class = null;
}
if ($short_headers[$col_num] !== null) {
$header_nodevice = phutil_tag(
'span',
array(
'class' => 'aphront-table-view-nodevice',
),
$header);
$header_device = phutil_tag(
'span',
array(
'class' => 'aphront-table-view-device',
),
$short_headers[$col_num]);
$header = hsprintf('%s %s', $header_nodevice, $header_device);
}
$tr[] = phutil_tag('th', array('class' => $class), $header);
}
$table[] = phutil_tag('tr', array(), $tr);
}
foreach ($col_classes as $key => $value) {
if (($sort_values[$key] !== null) &&
($sort_values[$key] == $this->sortSelected)) {
$value = trim($value.' sorted-column');
}
if ($value !== null) {
$col_classes[$key] = $value;
}
}
$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;
}
$tr = array();
// 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 (!empty($this->cellClasses[$row_num][$col_num])) {
$class = trim($class.' '.$this->cellClasses[$row_num][$col_num]);
}
$tr[] = phutil_tag('td', array('class' => $class), $value);
++$col_num;
}
$class = idx($this->rowClasses, $row_num);
if ($this->zebraStripes && ($row_num % 2)) {
if ($class !== null) {
$class = 'alt alt-'.$class;
} else {
$class = 'alt';
}
}
$table[] = phutil_tag('tr', array('class' => $class), $tr);
++$row_num;
}
} else {
$colspan = max(count(array_filter($visibility)), 1);
$table[] = phutil_tag(
'tr',
array('class' => 'no-data'),
phutil_tag(
'td',
array('colspan' => $colspan),
coalesce($this->noDataString, pht('No data available.'))));
}
$table_class = 'aphront-table-view';
if ($this->className !== null) {
$table_class .= ' '.$this->className;
}
if ($this->deviceReadyTable) {
$table_class .= ' aphront-table-view-device-ready';
}
$html = phutil_tag('table', array('class' => $table_class), $table);
return phutil_tag_div('aphront-table-wrap', $html);
}
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_tag(
'div',
array(
'class' => 'single-display-line-bounds',
),
array(
phutil_tag(
'span',
array(
'class' => 'single-display-line-content',
),
$line),
"\xC2\xA0",
));
}
}
-
diff --git a/src/view/layout/PhabricatorFileLinkListView.php b/src/view/layout/PhabricatorFileLinkListView.php
index 0eaf519d33..5824b6a8c2 100644
--- a/src/view/layout/PhabricatorFileLinkListView.php
+++ b/src/view/layout/PhabricatorFileLinkListView.php
@@ -1,37 +1,36 @@
<?php
final class PhabricatorFileLinkListView extends AphrontView {
private $files;
public function setFiles(array $files) {
assert_instances_of($files, 'PhabricatorFile');
$this->files = $files;
return $this;
}
private function getFiles() {
return $this->files;
}
public function render() {
$files = $this->getFiles();
if (!$files) {
return '';
}
require_celerity_resource('phabricator-remarkup-css');
$file_links = array();
foreach ($this->getFiles() as $file) {
$view = id(new PhabricatorFileLinkView())
->setFilePHID($file->getPHID())
->setFileName($file->getName())
->setFileDownloadURI($file->getDownloadURI())
->setFileViewURI($file->getBestURI())
->setFileViewable($file->isViewableImage());
$file_links[] = $view->render();
}
return phutil_implode_html(phutil_tag('br'), $file_links);
}
}
-
diff --git a/src/view/viewutils.php b/src/view/viewutils.php
index 44916e947b..b7fc259dfe 100644
--- a/src/view/viewutils.php
+++ b/src/view/viewutils.php
@@ -1,280 +1,279 @@
<?php
function phabricator_date($epoch, PhabricatorUser $user) {
return phabricator_format_local_time(
$epoch,
$user,
_phabricator_date_format($epoch));
}
function phabricator_on_relative_date($epoch, $user) {
return phabricator_relative_date($epoch, $user, true);
}
function phabricator_relative_date($epoch, $user, $on = false) {
static $today;
static $yesterday;
if (!$today || !$yesterday) {
$now = time();
$today = phabricator_date($now, $user);
$yesterday = phabricator_date($now - 86400, $user);
}
$date = phabricator_date($epoch, $user);
if ($date === $today) {
return 'today';
}
if ($date === $yesterday) {
return 'yesterday';
}
return (($on ? 'on ' : '').$date);
}
function phabricator_time($epoch, $user) {
return phabricator_format_local_time(
$epoch,
$user,
_phabricator_time_format($user));
}
function phabricator_datetime($epoch, $user) {
return phabricator_format_local_time(
$epoch,
$user,
pht('%s, %s',
_phabricator_date_format($epoch),
_phabricator_time_format($user)));
}
function _phabricator_date_format($epoch) {
$now = time();
$shift = 30 * 24 * 60 * 60;
if ($epoch < $now + $shift && $epoch > $now - $shift) {
$format = pht('D, M j');
} else {
$format = pht('M j Y');
}
return $format;
}
function _phabricator_time_format($user) {
$prefs = $user->loadPreferences();
$pref = $prefs->getPreference(
PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT);
if (strlen($pref)) {
return $pref;
}
return pht('g:i A');
}
/**
* This function does not usually need to be called directly. Instead, call
* @{function:phabricator_date}, @{function:phabricator_time}, or
* @{function:phabricator_datetime}.
*
* @param int Unix epoch timestamp.
* @param PhabricatorUser User viewing the timestamp.
* @param string Date format, as per DateTime class.
* @return string Formatted, local date/time.
*/
function phabricator_format_local_time($epoch, $user, $format) {
if (!$epoch) {
// If we're missing date information for something, the DateTime class will
// throw an exception when we try to construct an object. Since this is a
// display function, just return an empty string.
return '';
}
$user_zone = $user->getTimezoneIdentifier();
static $zones = array();
if (empty($zones[$user_zone])) {
$zones[$user_zone] = new DateTimeZone($user_zone);
}
$zone = $zones[$user_zone];
// NOTE: Although DateTime takes a second DateTimeZone parameter to its
// constructor, it ignores it if the date string includes timezone
// information. Further, it treats epoch timestamps ("@946684800") as having
// a UTC timezone. Set the timezone explicitly after constructing the object.
try {
$date = new DateTime('@'.$epoch);
} catch (Exception $ex) {
// NOTE: DateTime throws an empty exception if the format is invalid,
// just replace it with a useful one.
throw new Exception(
pht("Construction of a DateTime() with epoch '%s' ".
"raised an exception.", $epoch));
}
$date->setTimeZone($zone);
return PhutilTranslator::getInstance()->translateDate($format, $date);
}
function phabricator_format_relative_time($duration) {
return phabricator_format_units_generic(
$duration,
array(60, 60, 24, 7),
array('s', 'm', 'h', 'd', 'w'),
$precision = 0);
}
/**
* Format a relative time (duration) into weeks, days, hours, minutes,
* seconds, but unlike phabricator_format_relative_time, does so for more than
* just the largest unit.
*
* @param int Duration in seconds.
* @param int Levels to render - will render the three highest levels, ie:
* 5 h, 37 m, 1 s
* @return string Human-readable description.
*/
function phabricator_format_relative_time_detailed($duration, $levels = 2) {
if ($duration == 0) {
return 'now';
}
$levels = max(1, min($levels, 5));
$remainder = 0;
$is_negative = false;
if ($duration < 0) {
$is_negative = true;
$duration = abs($duration);
}
$this_level = 1;
$detailed_relative_time = phabricator_format_units_generic(
$duration,
array(60, 60, 24, 7),
array('s', 'm', 'h', 'd', 'w'),
$precision = 0,
$remainder);
$duration = $remainder;
while ($remainder > 0 && $this_level < $levels) {
$detailed_relative_time .= ', '.phabricator_format_units_generic(
$duration,
array(60, 60, 24, 7),
array('s', 'm', 'h', 'd', 'w'),
$precision = 0,
$remainder);
$duration = $remainder;
$this_level++;
};
if ($is_negative) {
$detailed_relative_time .= ' ago';
}
return $detailed_relative_time;
}
/**
* Format a byte count for human consumption, e.g. "10MB" instead of
* "10000000".
*
* @param int Number of bytes.
* @return string Human-readable description.
*/
function phabricator_format_bytes($bytes) {
return phabricator_format_units_generic(
$bytes,
// NOTE: Using the SI version of these units rather than the 1024 version.
array(1000, 1000, 1000, 1000, 1000),
array('B', 'KB', 'MB', 'GB', 'TB', 'PB'),
$precision = 0);
}
/**
* Parse a human-readable byte description (like "6MB") into an integer.
*
* @param string Human-readable description.
* @return int Number of represented bytes.
*/
function phabricator_parse_bytes($input) {
$bytes = trim($input);
if (!strlen($bytes)) {
return null;
}
// NOTE: Assumes US-centric numeral notation.
$bytes = preg_replace('/[ ,]/', '', $bytes);
$matches = null;
if (!preg_match('/^(?:\d+(?:[.]\d+)?)([kmgtp]?)b?$/i', $bytes, $matches)) {
throw new Exception("Unable to parse byte size '{$input}'!");
}
$scale = array(
'k' => 1000,
'm' => 1000 * 1000,
'g' => 1000 * 1000 * 1000,
't' => 1000 * 1000 * 1000 * 1000,
'p' => 1000 * 1000 * 1000 * 1000 * 1000,
);
$bytes = (float)$bytes;
if ($matches[1]) {
$bytes *= $scale[strtolower($matches[1])];
}
return (int)$bytes;
}
function phabricator_format_units_generic(
$n,
array $scales,
array $labels,
$precision = 0,
&$remainder = null) {
$is_negative = false;
if ($n < 0) {
$is_negative = true;
$n = abs($n);
}
$remainder = 0;
$accum = 1;
$scale = array_shift($scales);
$label = array_shift($labels);
while ($n >= $scale && count($labels)) {
$remainder += ($n % $scale) * $accum;
$n /= $scale;
$accum *= $scale;
$label = array_shift($labels);
if (!count($scales)) {
break;
}
$scale = array_shift($scales);
}
if ($is_negative) {
$n = -$n;
$remainder = -$remainder;
}
if ($precision) {
$num_string = number_format($n, $precision);
} else {
$num_string = (int)floor($n);
}
if ($label) {
$num_string .= ' '.$label;
}
return $num_string;
}
-
diff --git a/support/aphlict/client/aphlict_test_client.php b/support/aphlict/client/aphlict_test_client.php
index b6ca0480d6..52974a0731 100755
--- a/support/aphlict/client/aphlict_test_client.php
+++ b/support/aphlict/client/aphlict_test_client.php
@@ -1,56 +1,55 @@
#!/usr/bin/env php
<?php
$root = dirname(dirname(dirname(dirname(__FILE__))));
require_once $root.'/scripts/__init_script__.php';
$args = new PhutilArgumentParser($argv);
$args->setTagline('test client for Aphlict server');
$args->setSynopsis(<<<EOHELP
**aphlict_test_client.php** [__options__]
Connect to the Aphlict server configured in the Phabricator config.
EOHELP
);
$args->parseStandardArguments();
$args->parse(
array(
array(
'name' => 'server',
'param' => 'uri',
'default' => PhabricatorEnv::getEnvConfig('notification.client-uri'),
'help' => 'Connect to __uri__ instead of the default server.',
),
));
$console = PhutilConsole::getConsole();
$errno = null;
$errstr = null;
$uri = $args->getArg('server');
$uri = new PhutilURI($uri);
$uri->setProtocol('tcp');
$console->writeErr("Connecting...\n");
$socket = stream_socket_client(
$uri,
$errno,
$errstr);
if (!$socket) {
$console->writeErr(
"Unable to connect to Aphlict (at '$uri'). Error #{$errno}: {$errstr}");
exit(1);
} else {
$console->writeErr("Connected.\n");
}
$io_channel = new PhutilSocketChannel($socket);
$proto_channel = new PhutilJSONProtocolChannel($io_channel);
$json = new PhutilJSON();
while (true) {
$message = $proto_channel->waitForMessage();
$console->writeOut($json->encodeFormatted($message));
}
-
diff --git a/support/empty/README b/support/empty/README
index 1d7f895991..019c55dcec 100644
--- a/support/empty/README
+++ b/support/empty/README
@@ -1,6 +1,5 @@
This is an empty, readable directory. If you need an empty, readable directory
for some reason, you can use this one.
Of course, it's not quite empty because it has this file in it. So it's a
mostly-empty, readable directory.
-
diff --git a/webroot/index.php b/webroot/index.php
index f880a77b90..7072727e7f 100644
--- a/webroot/index.php
+++ b/webroot/index.php
@@ -1,151 +1,150 @@
<?php
require_once dirname(dirname(__FILE__)).'/support/PhabricatorStartup.php';
PhabricatorStartup::didStartup();
$show_unexpected_traces = false;
try {
PhabricatorStartup::loadCoreLibraries();
PhabricatorEnv::initializeWebEnvironment();
$show_unexpected_traces = PhabricatorEnv::getEnvConfig(
'phabricator.developer-mode');
// This is the earliest we can get away with this, we need env config first.
PhabricatorAccessLog::init();
$access_log = PhabricatorAccessLog::getLog();
PhabricatorStartup::setGlobal('log.access', $access_log);
$access_log->setData(
array(
'R' => AphrontRequest::getHTTPHeader('Referer', '-'),
'r' => idx($_SERVER, 'REMOTE_ADDR', '-'),
'M' => idx($_SERVER, 'REQUEST_METHOD', '-'),
));
DarkConsoleXHProfPluginAPI::hookProfiler();
DarkConsoleErrorLogPluginAPI::registerErrorHandler();
$sink = new AphrontPHPHTTPSink();
$response = PhabricatorSetupCheck::willProcessRequest();
if ($response) {
PhabricatorStartup::endOutputCapture();
$sink->writeResponse($response);
return;
}
$host = AphrontRequest::getHTTPHeader('Host');
$path = $_REQUEST['__path__'];
switch ($host) {
default:
$config_key = 'aphront.default-application-configuration-class';
$application = PhabricatorEnv::newObjectFromConfig($config_key);
break;
}
$application->setHost($host);
$application->setPath($path);
$application->willBuildRequest();
$request = $application->buildRequest();
// Until an administrator sets "phabricator.base-uri", assume it is the same
// as the request URI. This will work fine in most cases, it just breaks down
// when daemons need to do things.
$request_protocol = ($request->isHTTPS() ? 'https' : 'http');
$request_base_uri = "{$request_protocol}://{$host}/";
PhabricatorEnv::setRequestBaseURI($request_base_uri);
$write_guard = new AphrontWriteGuard(array($request, 'validateCSRF'));
$application->setRequest($request);
list($controller, $uri_data) = $application->buildController();
$access_log->setData(
array(
'U' => (string)$request->getRequestURI()->getPath(),
'C' => get_class($controller),
));
// If execution throws an exception and then trying to render that exception
// throws another exception, we want to show the original exception, as it is
// likely the root cause of the rendering exception.
$original_exception = null;
try {
$response = $controller->willBeginExecution();
if ($request->getUser() && $request->getUser()->getPHID()) {
$access_log->setData(
array(
'u' => $request->getUser()->getUserName(),
'P' => $request->getUser()->getPHID(),
));
}
if (!$response) {
$controller->willProcessRequest($uri_data);
$response = $controller->processRequest();
}
} catch (Exception $ex) {
$original_exception = $ex;
$response = $application->handleException($ex);
}
try {
$response = $controller->didProcessRequest($response);
$response = $application->willSendResponse($response, $controller);
$response->setRequest($request);
$unexpected_output = PhabricatorStartup::endOutputCapture();
if ($unexpected_output) {
$unexpected_output = "Unexpected output:\n\n{$unexpected_output}";
phlog($unexpected_output);
if ($response instanceof AphrontWebpageResponse) {
echo phutil_tag(
'div',
array('style' =>
'background: #eeddff;'.
'white-space: pre-wrap;'.
'z-index: 200000;'.
'position: relative;'.
'padding: 8px;'.
'font-family: monospace'),
$unexpected_output);
}
}
$sink->writeResponse($response);
} catch (Exception $ex) {
$write_guard->dispose();
$access_log->write();
if ($original_exception) {
$ex = new PhutilAggregateException(
"Multiple exceptions during processing and rendering.",
array(
$original_exception,
$ex,
));
}
PhabricatorStartup::didEncounterFatalException(
'Rendering Exception',
$ex,
$show_unexpected_traces);
}
$write_guard->dispose();
$access_log->setData(
array(
'c' => $response->getHTTPResponseCode(),
'T' => PhabricatorStartup::getMicrosecondsSinceStart(),
));
DarkConsoleXHProfPluginAPI::saveProfilerSample($access_log);
} catch (Exception $ex) {
PhabricatorStartup::didEncounterFatalException(
'Core Exception',
$ex,
$show_unexpected_traces);
}
-
diff --git a/webroot/rsrc/css/aphront/lightbox-attachment.css b/webroot/rsrc/css/aphront/lightbox-attachment.css
index 6058b95fb9..a11512ce4e 100644
--- a/webroot/rsrc/css/aphront/lightbox-attachment.css
+++ b/webroot/rsrc/css/aphront/lightbox-attachment.css
@@ -1,107 +1,106 @@
/**
* @provides lightbox-attachment-css
*/
.lightbox-attached {
overflow: hidden;
}
.lightbox-attachment {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
overflow-y: auto;
}
.lightbox-attachment img {
margin: 3% auto 0;
max-height: 90%;
max-width: 90%;
}
.lightbox-attachment .loading {
position: absolute;
top: -9999px;
}
.lightbox-attachment .attachment-name {
width: 100%;
color: #F2F2F2;
line-height: 30px;
text-align: center;
}
.lightbox-attachment .lightbox-status {
background: #010101;
color: #F2F2F2;
line-height: 30px;
position: fixed;
bottom: 0px;
width: 100%;
}
.lightbox-attachment .lightbox-status .lightbox-status-txt {
padding: 0px 0px 0px 20px;
}
.lightbox-attachment .lightbox-status .lightbox-download {
padding: 0px 20px 0px 0px;
float: right;
}
.lightbox-attachment .lightbox-status .lightbox-download
.lightbox-download-form {
display: inline;
}
.lightbox-attachment .lightbox-status .lightbox-download
.lightbox-download-form button {
border: 0;
background: #010101;
}
.lightbox-attachment .lightbox-status .lightbox-download
.lightbox-download-form button:hover {
background: #333;
}
.lightbox-attachment .lightbox-close {
top: 22px;
right: 20px;
position: fixed;
display: block;
height: 26px;
width: 26px;
background: url('/rsrc/image/icon/lightbox/close-2.png');
}
.lightbox-attachment .lightbox-close:hover {
background: url('/rsrc/image/icon/lightbox/close-hover-2.png');
}
.lightbox-attachment .lightbox-left {
top: 46%;
left: 20px;
position: fixed;
display: block;
height: 38px;
width: 21px;
background: url('/rsrc/image/icon/lightbox/left-arrow-2.png');
}
.lightbox-attachment .lightbox-left:hover {
background: url('/rsrc/image/icon/lightbox/left-arrow-hover-2.png');
}
.lightbox-attachment .lightbox-right {
top: 46%;
right: 20px;
position: fixed;
display: block;
height: 38px;
width: 21px;
background: url('/rsrc/image/icon/lightbox/right-arrow-2.png');
}
.lightbox-attachment .lightbox-right:hover {
background: url('/rsrc/image/icon/lightbox/right-arrow-hover-2.png');
}
-
diff --git a/webroot/rsrc/css/aphront/phabricator-nav-view.css b/webroot/rsrc/css/aphront/phabricator-nav-view.css
index efb8b91f61..b6682c6f44 100644
--- a/webroot/rsrc/css/aphront/phabricator-nav-view.css
+++ b/webroot/rsrc/css/aphront/phabricator-nav-view.css
@@ -1,103 +1,102 @@
/**
* @provides phabricator-nav-view-css
*/
.jx-drag-col {
cursor: col-resize;
}
.phabricator-nav {
/* Force top margins in page content not to collapse with the top margin of
the navigation container by giving it padding. Then put it in the right
position by undoing the padding with a margin. */
padding-top: 1px;
margin-top: -1px;
}
.phabricator-nav-column-background {
position: fixed;
top: 0;
left: 0;
/* On the iPhone, scrolling down causes the revealed area to fill with white,
then draw with the texture after the action completes. Just make the
element extend off the bottom of the screen to prevent this. */
bottom: -480px;
width: 205px;
background: #303539;
box-shadow: inset -3px 0 3px rgba(0, 0, 0, 0.5);
}
.phabricator-nav-column-background,
.phabricator-nav-local,
.phabricator-nav-drag {
display: none;
}
.device-desktop .has-local-nav .phabricator-nav-column-background,
.device-desktop .has-local-nav .phabricator-nav-local,
.device-desktop .has-local-nav .phabricator-nav-drag {
display: block;
}
.device .phabricator-side-menu-home .phabricator-nav-column-background,
.device .phabricator-side-menu-home .phabricator-nav-local {
display: block;
}
.phabricator-nav-local {
width: 205px;
position: absolute;
left: 0;
white-space: nowrap;
overflow-x: hidden;
overflow-y: auto;
}
.phabricator-nav-drag {
position: fixed;
top: 0;
bottom: 0;
left: 205px;
width: 7px;
cursor: col-resize;
background: #f5f5f5;
border-style: solid;
border-width: 0 1px 0 1px;
border-color: #fff #999c9e #fff #999c9e;
box-shadow: inset -1px 0px 1px rgba(0, 0, 0, 0.15);
background-image: url(/rsrc/image/divot.png);
background-position: center;
background-repeat: no-repeat;
}
.device-desktop .phabricator-standard-page-body .has-drag-nav .phabricator-nav-content {
margin-left: 212px;
}
.device-desktop .has-local-nav .phabricator-nav-content {
margin-left: 205px;
}
.phabricator-side-menu-home .phabricator-nav-column-background,
.phabricator-side-menu-home .phabricator-nav-local {
width: 240px;
}
.device-desktop .phabricator-side-menu-home .phabricator-nav-content,
.device-tablet .phabricator-side-menu-home .phabricator-nav-content {
margin-left: 240px;
}
.device-phone .phabricator-side-menu-home .phabricator-nav-content {
display: none;
}
.device-phone .phabricator-side-menu-home .phabricator-nav-column-background,
.device-phone .phabricator-side-menu-home .phabricator-nav-local {
width: 100%;
}
-
diff --git a/webroot/rsrc/css/application/conpherence/notification.css b/webroot/rsrc/css/application/conpherence/notification.css
index 565504cbc2..ec55ad0b3a 100644
--- a/webroot/rsrc/css/application/conpherence/notification.css
+++ b/webroot/rsrc/css/application/conpherence/notification.css
@@ -1,80 +1,79 @@
/**
* @provides conpherence-notification-css
*/
/* kill styles on phabricator-notification */
.conpherence-notification {
padding: 0;
}
.phabricator-notification .conpherence-menu-item-view {
display: block;
height: 55px;
overflow: hidden;
position: relative;
text-decoration: none;
border-bottom: none;
border-right: 0;
border-left: 0;
}
.phabricator-notification .conpherence-menu-item-view
.conpherence-menu-item-image {
top: 6px;
left: 6px;
display: block;
position: absolute;
width: 35px;
height: 35px;
background-size: 35px;
border: 4px solid #e7e7e7;
border-radius: 3px;
}
.phabricator-notification .conpherence-menu-item-view
.conpherence-menu-item-title {
display: block;
margin-top: 12px;
margin-left: 58px;
text-align: left;
font-weight: bold;
font-size: 13px;
color: #333;
overflow: hidden;
width: 220px;
text-overflow: ellipsis;
}
.phabricator-notification .conpherence-menu-item-view
.conpherence-menu-item-subtitle {
display: block;
color: #a1a5a9;
font-size: 11px;
margin-top: 2px;
margin-left: 58px;
font-style: italic;
}
.phabricator-notification .conpherence-menu-item-view
.conpherence-menu-item-unread-count {
position: absolute;
left: 35px;
top: 3px;
background: {$red};
border-radius: 10px;
color: #FFF;
font-weight: bold;
padding: 1px 5px 2px;
border: 1px solid #333;
font-size: 11px;
}
.phabricator-notification .conpherence-menu-item-view
.conpherence-menu-item-date {
position: absolute;
top: 15px;
right: 16px;
color: #a1a5a9;
font-size: 11px;
}
-
diff --git a/webroot/rsrc/css/application/differential/core.css b/webroot/rsrc/css/application/differential/core.css
index 2b4f6b4d9c..7ccd633f3f 100644
--- a/webroot/rsrc/css/application/differential/core.css
+++ b/webroot/rsrc/css/application/differential/core.css
@@ -1,26 +1,25 @@
/**
* @provides differential-core-view-css
*/
.differential-primary-pane {
margin-bottom: 32px;
}
.differential-panel {
padding: 16px;
}
.differential-panel h1 {
border-bottom: 1px solid #aaaa99;
padding-bottom: 8px;
margin-bottom: 8px;
}
.differential-unselectable tr td:nth-of-type(1) {
-moz-user-select: -moz-none;
-khtml-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
-
diff --git a/webroot/rsrc/css/application/maniphest/batch-editor.css b/webroot/rsrc/css/application/maniphest/batch-editor.css
index 10e9a61d62..f761cf1aa0 100644
--- a/webroot/rsrc/css/application/maniphest/batch-editor.css
+++ b/webroot/rsrc/css/application/maniphest/batch-editor.css
@@ -1,19 +1,18 @@
/**
* @provides maniphest-batch-editor
*/
.maniphest-batch-actions-table {
width: 100%;
margin: 1em 0;
}
.maniphest-batch-actions-table td {
padding: 4px 8px;
vertical-align: middle;
}
.batch-editor-input {
width: 100%;
text-align: left;
}
-
diff --git a/webroot/rsrc/css/application/people/people-profile.css b/webroot/rsrc/css/application/people/people-profile.css
index 655bcb0c99..bc0e61fbf9 100644
--- a/webroot/rsrc/css/application/people/people-profile.css
+++ b/webroot/rsrc/css/application/people/people-profile.css
@@ -1,79 +1,78 @@
/**
* @provides people-profile-css
*/
form.profile-image-form {
display: inline-block;
margin: 0 8px 8px 0;
}
button.profile-image-button {
padding: 4px;
margin: 0;
}
.compose-dialog button.profile-image-button-selected {
background-image: none;
background-color: {$lightblue};
border-color: {$blueborder};
}
.compose-header {
color: {$bluetext};
border-bottom: 1px solid {$lightblueborder};
padding: 4px 0;
margin: 0 0 8px;
}
form.compose-dialog {
width: 80%;
}
.compose-dialog .phui-icon-view {
display: block;
position: relative;
width: 50px;
height: 50px;
background-color: {$darkgreytext};
}
.compose-dialog .compose-background-red {
background-color: {$red};
}
.compose-dialog .compose-background-orange {
background-color: {$orange};
}
.compose-dialog .compose-background-yellow {
background-color: {$yellow};
}
.compose-dialog .compose-background-green {
background-color: {$green};
}
.compose-dialog .compose-background-blue {
background-color: {$blue};
}
.compose-dialog .compose-background-sky {
background-color: {$sky};
}
.compose-dialog .compose-background-indigo {
background-color: {$indigo};
}
.compose-dialog .compose-background-violet {
background-color: {$violet};
}
.compose-dialog .compose-background-charcoal {
background-color: {$charcoal};
}
.compose-dialog .compose-background-backdrop {
background-color: {$backdrop};
}
-
diff --git a/webroot/rsrc/css/layout/phabricator-action-header-view.css b/webroot/rsrc/css/layout/phabricator-action-header-view.css
index fbc573b919..11127e18b6 100644
--- a/webroot/rsrc/css/layout/phabricator-action-header-view.css
+++ b/webroot/rsrc/css/layout/phabricator-action-header-view.css
@@ -1,65 +1,64 @@
/**
* @provides phabricator-action-header-view-css
*/
.phabricator-action-header {
padding: 0 5px 0 8px;
overflow: hidden;
}
.phabricator-action-header-title {
color: {$darkgreytext};
float: left;
font-size: 14px;
font-weight: bold;
line-height: 15px;
padding: 8px 0;
text-shadow: 0 1px 1px #fff;
white-space: nowrap;
}
.phabricator-action-header-icon-list {
float: right;
padding-top: 4px;
}
.phabricator-action-header-icon-item {
float: right;
padding-left: 2px;
}
.phabricator-action-header-icon-item .phui-icon-view {
display: inline-block;
}
.phabricator-action-header-icon-item .phui-tag-view {
margin: 4px 2px 0;
}
.phabricator-action-header-link {
color: {$darkgreytext};
}
.gradient-green-header .phabricator-action-header-title,
.gradient-red-header .phabricator-action-header-title,
.gradient-blue-header .phabricator-action-header-title,
.gradient-yellow-header .phabricator-action-header-title,
.gradient-green-header .phabricator-action-header-link,
.gradient-red-header .phabricator-action-header-link,
.gradient-blue-header .phabricator-action-header-link,
.gradient-yellow-header .phabricator-action-header-link {
color: #fff;
text-shadow: 0 -1px 1px rgba(0,0,0,.7);
}
.phabricator-action-header-icon-list .phui-tag-view {
font-weight: normal;
}
.phabricator-action-header-title span {
float: left;
height: 16px;
width: 16px;
margin-right: 4px;
}
-
diff --git a/webroot/rsrc/css/phui/phui-icon.css b/webroot/rsrc/css/phui/phui-icon.css
index ccc6715696..9ab8036bc9 100644
--- a/webroot/rsrc/css/phui/phui-icon.css
+++ b/webroot/rsrc/css/phui/phui-icon.css
@@ -1,54 +1,52 @@
/**
* @provides phui-icon-view-css
*/
.phui-icon-example .phui-icon-view {
display: inline-block;
vertical-align: top;
}
.phui-icon-view.sprite-minicons {
height: 16px;
width: 16px;
}
.phui-icon-view.sprite-actions {
height: 24px;
width: 24px;
}
.phui-icon-view.sprite-apps,
.phui-icon-view.sprite-icons,
.phui-icon-view.sprite-status,
.phui-icon-view.sprite-buttonbar {
height: 14px;
width: 14px;
}
.phui-icon-view.sprite-tokens {
height: 16px;
width: 16px;
}
.phui-icon-view.sprite-payments {
height: 32px;
width: 60px;
}
.phui-icon-view.sprite-login {
height: 34px;
width: 34px;
}
.phui-icon-view.phuihead-medium {
height: 50px;
width: 50px;
}
.phui-icon-view.phuihead-small {
height: 35px;
width: 35px;
background-size: 35px;
}
-
-
diff --git a/webroot/rsrc/externals/javelin/LICENSE b/webroot/rsrc/externals/javelin/LICENSE
index 48fb9f83b0..7d06b3778a 100644
--- a/webroot/rsrc/externals/javelin/LICENSE
+++ b/webroot/rsrc/externals/javelin/LICENSE
@@ -1,25 +1,24 @@
Copyright (c) 2009, Evan Priestley and Facebook, inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Facebook, inc. nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
diff --git a/webroot/rsrc/externals/javelin/core/__tests__/event-stop-and-kill.js b/webroot/rsrc/externals/javelin/core/__tests__/event-stop-and-kill.js
index bc45d43fe0..09a2d3a764 100644
--- a/webroot/rsrc/externals/javelin/core/__tests__/event-stop-and-kill.js
+++ b/webroot/rsrc/externals/javelin/core/__tests__/event-stop-and-kill.js
@@ -1,39 +1,35 @@
/**
* @requires javelin-event
*/
describe('Event Stop/Kill', function() {
var target;
beforeEach(function() {
target = new JX.Event();
});
it('should stop an event', function() {
expect(target.getStopped()).toBe(false);
target.prevent();
expect(target.getStopped()).toBe(false);
target.stop();
expect(target.getStopped()).toBe(true);
});
it('should prevent the default action of an event', function() {
expect(target.getPrevented()).toBe(false);
target.stop();
expect(target.getPrevented()).toBe(false);
target.prevent();
expect(target.getPrevented()).toBe(true);
});
it('should kill (stop and prevent) an event', function() {
expect(target.getPrevented()).toBe(false);
expect(target.getStopped()).toBe(false);
target.kill();
expect(target.getPrevented()).toBe(true);
expect(target.getStopped()).toBe(true);
});
});
-
-
-
-
diff --git a/webroot/rsrc/externals/javelin/core/__tests__/install.js b/webroot/rsrc/externals/javelin/core/__tests__/install.js
index c6b78ce9e0..c13a281bd8 100644
--- a/webroot/rsrc/externals/javelin/core/__tests__/install.js
+++ b/webroot/rsrc/externals/javelin/core/__tests__/install.js
@@ -1,152 +1,151 @@
/**
* @requires javelin-install
*/
describe('Javelin Install', function() {
it('should extend from an object', function() {
JX.install('Animal', {
properties: {
name: 'bob'
}
});
JX.install('Dog', {
extend: 'Animal',
members: {
bark: function() {
return 'bow wow';
}
}
});
var bob = new JX.Dog();
expect(bob.getName()).toEqual('bob');
expect(bob.bark()).toEqual('bow wow');
});
it('should create a class', function() {
var Animal = JX.createClass({
name: 'Animal',
properties: {
name: 'bob'
}
});
var Dog = JX.createClass({
name: 'Dog',
extend: Animal,
members: {
bark: function() {
return 'bow wow';
}
}
});
var bob = new Dog();
expect(bob.getName()).toEqual('bob');
expect(bob.bark()).toEqual('bow wow');
});
it('should call base constructor when construct is not provided', function() {
var Base = JX.createClass({
name: 'Base',
construct: function() {
this.baseCalled = true;
}
});
var Sub = JX.createClass({
name: 'Sub',
extend: Base
});
var obj = new Sub();
expect(obj.baseCalled).toBe(true);
});
it('should call intialize after install', function() {
var initialized = false;
JX.install('TestClass', {
properties: {
foo: 'bar'
},
initialize: function() {
initialized = true;
}
});
expect(initialized).toBe(true);
});
it('should call base ctor when construct is not provided in JX.install',
function() {
JX.install('Base', {
construct: function() {
this.baseCalled = true;
}
});
JX.install('Sub', {
extend: 'Base'
});
var obj = new JX.Sub();
expect(obj.baseCalled).toBe(true);
});
it('[DEV] should throw when calling install with name', function() {
ensure__DEV__(true, function() {
expect(function() {
JX.install('AngryAnimal', {
name: 'Kitty'
});
}).toThrow();
});
});
it('[DEV] should throw when calling createClass with initialize', function() {
ensure__DEV__(true, function() {
expect(function() {
JX.createClass({
initialize: function() {
}
});
}).toThrow();
});
});
it('initialize() should be able to access the installed class', function() {
JX.install('SomeClassWithInitialize', {
initialize : function() {
expect(!!JX.SomeClassWithInitialize).toBe(true);
}
});
});
it('should work with toString and its friends', function() {
JX.install('NiceAnimal', {
members: {
toString: function() {
return 'I am very nice.';
},
hasOwnProperty: function() {
return true;
}
}
});
expect(new JX.NiceAnimal().toString()).toEqual('I am very nice.');
expect(new JX.NiceAnimal().hasOwnProperty('dont-haz')).toEqual(true);
});
});
-
diff --git a/webroot/rsrc/externals/javelin/docs/Base.js b/webroot/rsrc/externals/javelin/docs/Base.js
index dc207c7758..d2352e9c99 100644
--- a/webroot/rsrc/externals/javelin/docs/Base.js
+++ b/webroot/rsrc/externals/javelin/docs/Base.js
@@ -1,75 +1,70 @@
/**
* @requires javelin-install
* @javelin
*/
/**
* This is not a real class, but @{function:JX.install} provides several methods
* which exist on all Javelin classes. This class documents those methods.
*
* @task events Builtin Events
* @group install
*/
JX.install('Base', {
members : {
/**
* Invoke a class event, notifying all listeners. You must declare the
* events your class invokes when you install it; see @{function:JX.install}
* for documentation. Any arguments you provide will be passed to listener
* callbacks.
*
* @param string Event type, must be declared when class is
* installed.
* @param ... Zero or more arguments.
*
* @return @{JX.Event} Event object which was dispatched.
* @task events
*/
invoke : function(type, more) {
// <docstub only, see JX.install()> //
},
/**
* Listen for events emitted by this object instance. You can also use
* the static flavor of this method to listen to events emitted by any
* instance of this object.
*
* See also @{method:JX.Stratcom.listen}.
*
* @param string Type of event to listen for.
* @param function Function to call when this event occurs.
* @return object A reference to the installed listener. You can later
* remove the listener by calling this object's remove()
* method.
* @task events
*/
listen : function(type, callback) {
// <docstub only, see JX.install()> //
}
},
statics : {
/**
* Static listen interface for listening to events produced by any instance
* of this class. See @{method:listen} for documentation.
*
* @param string Type of event to listen for.
* @param function Function to call when this event occurs.
* @return object A reference to the installed listener. You can later
* remove the listener by calling this object's remove()
* method.
* @task events
*/
listen : function(type, callback) {
// <docstub only, see JX.install()> //
}
}
});
-
-
-
-
-
diff --git a/webroot/rsrc/externals/javelin/docs/concepts/behaviors.diviner b/webroot/rsrc/externals/javelin/docs/concepts/behaviors.diviner
index f41afe13cd..726762ecb4 100644
--- a/webroot/rsrc/externals/javelin/docs/concepts/behaviors.diviner
+++ b/webroot/rsrc/externals/javelin/docs/concepts/behaviors.diviner
@@ -1,182 +1,181 @@
@title Concepts: Behaviors
@group concepts
Javelin behaviors help you glue pieces of code together.
= Overview =
Javelin behaviors provide a place for you to put glue code. For instance, when
a page loads, you often need to instantiate objects, or set up event listeners,
or alert the user that they've won a hog, or create a dependency between two
objects, or modify the DOM, etc.
Sometimes there's enough code involved here or a particular setup step happens
often enough that it makes sense to write a class, but sometimes it's just a
few lines of one-off glue. Behaviors give you a structured place to put this
glue so that it's consistently organized and can benefit from Javelin
infrastructure.
= Behavior Basics =
Behaviors are defined with @{function:JX.behavior}:
lang=js
JX.behavior('win-a-hog', function(config, statics) {
alert("YOU WON A HOG NAMED " + config.hogName + "!");
});
They are called with @{function:JX.initBehaviors}:
lang=js
JX.initBehaviors({
"win-a-hog" : [{hogName : "Ethel"}]
});
Normally, you don't construct the @{function:JX.initBehaviors} call yourself,
but instead use a server-side library which manages behavior initialization for
you. For example, using the PHP library:
lang=php
$config = array('hogName' => 'Ethel');
JavelinHelper::initBehaviors('win-a-hog', $config);
Regardless, this will alert the user that they've won a hog (named Ethel, which
is a good name for a hog) when they load the page.
The callback you pass to @{function:JX.behavior} should have this signature:
lang=js
function(config, statics) {
// ...
}
The function will be invoked once for each configuration dictionary passed to
@{function:JX.initBehaviors}, and the dictionary will be passed as the
##config## parameter. For example, to alert the user that they've won two hogs:
lang=js
JX.initBehaviors({
"win-a-hog" : [{hogName : "Ethel"}, {hogName: "Beatrice"}]
});
This will invoke the function twice, once for each ##config## dictionary.
Usually, you invoke a behavior multiple times if you have several similar
controls on a page, like multiple @{class:JX.Tokenizer}s.
An initially empty object will be passed in the ##statics## parameter, but
changes to this object will persist across invocations of the behavior. For
example:
lang=js
JX.initBehaviors('win-a-hog', function(config, statics) {
statics.hogsWon = (statics.hogsWon || 0) + 1;
if (statics.hogsWon == 1) {
alert("YOU WON A HOG! YOUR HOG IS NAMED " + config.hogName + "!");
} else {
alert("YOU WON ANOTHER HOG!!! THIS ONE IS NAMED " + config.hogName + "!");
}
}
One way to think about behaviors are that they take the anonymous function
passed to @{function:JX.behavior} and put it in a private Javelin namespace,
which you access with @{function:JX.initBehavior}.
Another way to think about them is that you are defining methods which represent
the entirety of the API exposed by the document. The recommended approach to
glue code is that the server interact with Javascript on the client //only// by
invoking behaviors, so the set of available behaviors represent the complete set
of legal interactions available to the server.
= History and Rationale =
This section explains why behaviors exist and how they came about. You can
understand and use them without knowing any of this, but it may be useful or
interesting.
In early 2007, Facebook often solved the "glue code" problem through the use
of global functions and DOM Level 0 event handlers, by manually building HTML
tags in PHP:
lang=php
echo '<a href="#" '.
'onclick="win_a_hog('.escape_js_string($hog_name).'); return false;">'.
'Click here to win!'.
'</a>';
(This example produces a link which the user can click to be alerted they have
won a hog, which is slightly different from the automatic alert in the other
examples in this document. Some subtle distinctions are ignored or glossed
over here because they are not important to understanding behaviors.)
This has a wide array of technical and architectural problems:
- Correctly escaping parameters is cumbersome and difficult.
- It resists static analysis, and is difficult to even grep for. You can't
easily package, minify, or determine dependencies for the piece of JS in
the result string.
- DOM Level 0 events have a host of issues in a complex application
environment.
- The JS global namespace becomes polluted with application glue functions.
- The server and client are tightly and relatively arbitrarily coupled, since
many of these handlers called multiple functions or had logic in the
strings. There is no structure to the coupling, so many callers relied on
the full power of arbitrary JS execution.
- It's utterly hideous.
In 2007/2008, we introduced @{function@libphutil:jsprintf} and a function called
onloadRegister() to solve some of the obvious problems:
lang=php
onloadRegister('win_a_hog(%s);', $hog_name);
This registers the snippet for invocation after DOMContentReady fires. This API
makes escaping manageable, and was combined with recommendations to structure
code like this in order to address some of the other problems:
lang=php
$id = uniq_id();
echo '<a href="#" id="'.$id.'">Click here to win!</a>';
onloadRegister('new WinAHogController(%s, %s);', $id, $hog_name);
By 2010 (particularly with the introduction of XHP) the API had become more
sophisticated, but this is basically how most of Facebook's glue code still
works as of mid-2011. If you view the source of any page, you'll see a bunch
of ##onloadRegister()## calls in the markup which are generated like this.
This mitigates many of the problems but is still fairly awkward. Escaping is
easier, but still possible to get wrong. Stuff is a bit easier to grep for, but
not much. You can't get very far with static analysis unless you get very
complex. Coupling between the languages has been reduced but not eliminated. And
now you have a bunch of classes which only really have glue code in them.
Javelin behaviors provide a more structured solution to some of these problems:
- All your Javascript code is in Javascript files, not embedded in strings in
in some host language on the server side.
- You can use static analysis and minification tools normally.
- Provided you use a reasonable server-side library, you can't get escaping
wrong.
- Coupling is reduced because server only passes data to the client, never
code.
- The server declares client dependencies explicitly, not implicitly inside
a string literal. Behaviors are also relatively easy to grep for.
- Behaviors exist in a private, structured namespace instead of the global
namespace.
- Separation between the document's layout and behavior is a consequence of
the structure of behaviors.
- The entire interface the server may invoke against can be readily inferred.
Note that Javelin does provide @{function:JX.onload}, which behaves like
##onloadRegister()##. However, its use is discouraged.
The two major downsides to the behavior design appear to be:
- They have a higher setup cost than the ad-hoc methods, but Javelin
philosophically places a very low value on this.
- Because there's a further setup cost to migrate an existing behavior into a
class, behaviors sometimes grow little by little until they are too big,
have more than just glue code, and should have been refactored into a
real class some time ago. This is a pretty high-level drawback and is
manageable through awareness of the risk and code review.
-
diff --git a/webroot/rsrc/externals/javelin/docs/onload.js b/webroot/rsrc/externals/javelin/docs/onload.js
index 8772745045..7c76c08598 100644
--- a/webroot/rsrc/externals/javelin/docs/onload.js
+++ b/webroot/rsrc/externals/javelin/docs/onload.js
@@ -1,22 +1,21 @@
/**
* @javelin
*/
/**
* Register a callback for invocation after DOMContentReady.
*
* NOTE: Although it isn't private, use of this function is heavily discouraged.
* See @{article:Concepts: Behaviors} for information on using behaviors to
* structure and invoke glue code.
*
* This function is defined as a side effect of init.js.
*
* @param function Callback function to invoke after DOMContentReady.
* @return void
* @group util
*/
JX.onload = function(callback) {
// This isn't the real function definition, it's only defined here to let the
// documentation generator find it. The actual definition is in init.js.
};
-
diff --git a/webroot/rsrc/externals/javelin/ext/reactor/core/DynVal.js b/webroot/rsrc/externals/javelin/ext/reactor/core/DynVal.js
index b65950caa9..ace3fd8ebe 100644
--- a/webroot/rsrc/externals/javelin/ext/reactor/core/DynVal.js
+++ b/webroot/rsrc/externals/javelin/ext/reactor/core/DynVal.js
@@ -1,48 +1,47 @@
/**
* @provides javelin-dynval
* @requires javelin-install
* javelin-reactornode
* javelin-util
* javelin-reactor
* @javelin
*/
JX.install('DynVal', {
members : {
_lastPulseVal : null,
_reactorNode : null,
getValueNow : function() {
return this._lastPulseVal;
},
getChanges : function() {
return this._reactorNode;
},
forceValueNow : function(value) {
this.getChanges().forceSendValue(value);
},
transform : function(fn) {
return new JX.DynVal(
this.getChanges().transform(fn),
fn(this.getValueNow())
);
},
calm : function(min_interval) {
return new JX.DynVal(
this.getChanges().calm(min_interval),
this.getValueNow()
);
}
},
construct : function(stream, init) {
this._lastPulseVal = init;
this._reactorNode =
new JX.ReactorNode([stream], JX.bind(this, function(pulse) {
if (this._lastPulseVal == pulse) {
return JX.Reactor.DoNotPropagate;
}
this._lastPulseVal = pulse;
return pulse;
}));
}
});
-
diff --git a/webroot/rsrc/externals/javelin/ext/reactor/core/Reactor.js b/webroot/rsrc/externals/javelin/ext/reactor/core/Reactor.js
index 0dbefbf78c..a40d864dc8 100644
--- a/webroot/rsrc/externals/javelin/ext/reactor/core/Reactor.js
+++ b/webroot/rsrc/externals/javelin/ext/reactor/core/Reactor.js
@@ -1,90 +1,89 @@
/**
* @provides javelin-reactor
* @requires javelin-install
* javelin-util
* @javelin
*/
JX.install('Reactor', {
statics : {
/**
* Return this value from a ReactorNode transformer to indicate that
* its listeners should not be activated.
*/
DoNotPropagate : {},
/**
* For internal use by the Reactor system.
*/
propagatePulse : function(start_pulse, start_node) {
var reverse_post_order =
JX.Reactor._postOrder(start_node).reverse();
start_node.primeValue(start_pulse);
for (var ix = 0; ix < reverse_post_order.length; ix++) {
var node = reverse_post_order[ix];
var pulse = node.getNextPulse();
if (pulse === JX.Reactor.DoNotPropagate) {
continue;
}
var next_pulse = node.getTransformer()(pulse);
var sends_to = node.getListeners();
for (var jx = 0; jx < sends_to.length; jx++) {
sends_to[jx].primeValue(next_pulse);
}
}
},
/**
* For internal use by the Reactor system.
*/
_postOrder : function(node, result, pending) {
if (typeof result === "undefined") {
result = [];
pending = {};
}
pending[node.getGraphID()] = true;
var nexts = node.getListeners();
for (var ix = 0; ix < nexts.length; ix++) {
var next = nexts[ix];
if (pending[next.getGraphID()]) {
continue;
}
JX.Reactor._postOrder(next, result, pending);
}
result.push(node);
return result;
},
// Helper for lift.
_valueNow : function(fn, dynvals) {
var values = [];
for (var ix = 0; ix < dynvals.length; ix++) {
values.push(dynvals[ix].getValueNow());
}
return fn.apply(null, values);
},
/**
* Lift a function over normal values to be a function over dynvals.
* @param fn A function expecting normal values
* @param dynvals Array of DynVals whose instaneous values will be passed
* to fn.
* @return A DynVal representing the changing value of fn applies to dynvals
* over time.
*/
lift : function(fn, dynvals) {
var valueNow = JX.bind(null, JX.Reactor._valueNow, fn, dynvals);
var streams = [];
for (var ix = 0; ix < dynvals.length; ix++) {
streams.push(dynvals[ix].getChanges());
}
var result = new JX['ReactorNode'](streams, valueNow);
return new JX['DynVal'](result, valueNow());
}
}
});
-
diff --git a/webroot/rsrc/externals/javelin/ext/reactor/core/ReactorNode.js b/webroot/rsrc/externals/javelin/ext/reactor/core/ReactorNode.js
index 490b4c10f4..02c60f9c41 100644
--- a/webroot/rsrc/externals/javelin/ext/reactor/core/ReactorNode.js
+++ b/webroot/rsrc/externals/javelin/ext/reactor/core/ReactorNode.js
@@ -1,97 +1,96 @@
/**
* @provides javelin-reactornode
* @requires javelin-install
* javelin-reactor
* javelin-util
* javelin-reactor-node-calmer
* @javelin
*/
JX.install('ReactorNode', {
members : {
_transformer : null,
_sendsTo : null,
_nextPulse : null,
_graphID : null,
getGraphID : function() {
return this._graphID || this.__id__;
},
setGraphID : function(id) {
this._graphID = id;
return this;
},
setTransformer : function(fn) {
this._transformer = fn;
return this;
},
/**
* Set up dest as a listener to this.
*/
listen : function(dest) {
this._sendsTo[dest.__id__] = dest;
return { remove : JX.bind(null, this._removeListener, dest) };
},
/**
* Helper for listen.
*/
_removeListener : function(dest) {
delete this._sendsTo[dest.__id__];
},
/**
* For internal use by the Reactor system
*/
primeValue : function(value) {
this._nextPulse = value;
},
getListeners : function() {
var result = [];
for (var k in this._sendsTo) {
result.push(this._sendsTo[k]);
}
return result;
},
/**
* For internal use by the Reactor system
*/
getNextPulse : function(pulse) {
return this._nextPulse;
},
getTransformer : function() {
return this._transformer;
},
forceSendValue : function(pulse) {
JX.Reactor.propagatePulse(pulse, this);
},
// fn should return JX.Reactor.DoNotPropagate to indicate a value that
// should not be retransmitted.
transform : function(fn) {
return new JX.ReactorNode([this], fn);
},
/**
* Suppress events to happen at most once per min_interval.
* The last event that fires within an interval will fire at the end
* of the interval. Events that are sandwiched between other events
* within an interval are dropped.
*/
calm : function(min_interval) {
var result = new JX.ReactorNode([this], JX.id);
var transformer = new JX.ReactorNodeCalmer(result, min_interval);
result.setTransformer(JX.bind(transformer, transformer.onPulse));
return result;
}
},
construct : function(source_streams, transformer) {
this._nextPulse = JX.Reactor.DoNotPropagate;
this._transformer = transformer;
this._sendsTo = {};
for (var ix = 0; ix < source_streams.length; ix++) {
source_streams[ix].listen(this);
}
}
});
-
diff --git a/webroot/rsrc/externals/javelin/ext/reactor/core/ReactorNodeCalmer.js b/webroot/rsrc/externals/javelin/ext/reactor/core/ReactorNodeCalmer.js
index f06fd702dd..d652a7cbc0 100644
--- a/webroot/rsrc/externals/javelin/ext/reactor/core/ReactorNodeCalmer.js
+++ b/webroot/rsrc/externals/javelin/ext/reactor/core/ReactorNodeCalmer.js
@@ -1,48 +1,47 @@
/**
* @provides javelin-reactor-node-calmer
* @requires javelin-install
* javelin-reactor
* javelin-util
* @javelin
*/
JX.install('ReactorNodeCalmer', {
properties : {
lastTime : 0,
timeout : null,
minInterval : 0,
reactorNode : null,
isEnabled : true
},
construct : function(node, min_interval) {
this.setLastTime(-min_interval);
this.setMinInterval(min_interval);
this.setReactorNode(node);
},
members: {
onPulse : function(pulse) {
if (!this.getIsEnabled()) {
return pulse;
}
var current_time = JX.now();
if (current_time - this.getLastTime() > this.getMinInterval()) {
this.setLastTime(current_time);
return pulse;
} else {
clearTimeout(this.getTimeout());
this.setTimeout(setTimeout(
JX.bind(this, this.send, pulse),
this.getLastTime() + this.getMinInterval() - current_time
));
return JX.Reactor.DoNotPropagate;
}
},
send : function(pulse) {
this.setLastTime(JX.now());
this.setIsEnabled(false);
this.getReactorNode().forceSendValue(pulse);
this.setIsEnabled(true);
}
}
});
-
diff --git a/webroot/rsrc/externals/javelin/ext/reactor/dom/RDOM.js b/webroot/rsrc/externals/javelin/ext/reactor/dom/RDOM.js
index 48b7b9687f..f34907f27f 100644
--- a/webroot/rsrc/externals/javelin/ext/reactor/dom/RDOM.js
+++ b/webroot/rsrc/externals/javelin/ext/reactor/dom/RDOM.js
@@ -1,407 +1,405 @@
/**
* Javelin Reactive functions to work with the DOM.
* @provides javelin-reactor-dom
* @requires javelin-dom
* javelin-dynval
* javelin-reactor
* javelin-reactornode
* javelin-install
* javelin-util
* @javelin
*/
JX.install('RDOM', {
statics : {
_time : null,
/**
* DynVal of the current time in milliseconds.
*/
time : function() {
if (JX.RDOM._time === null) {
var time = new JX.ReactorNode([], JX.id);
window.setInterval(function() {
time.forceSendValue(JX.now());
}, 100);
JX.RDOM._time = new JX.DynVal(time, JX.now());
}
return JX.RDOM._time;
},
/**
* Given a DynVal[String], return a DOM text node whose value tracks it.
*/
$DT : function(dyn_string) {
var node = document.createTextNode(dyn_string.getValueNow());
dyn_string.transform(function(s) { node.data = s; });
return node;
},
_recvEventPulses : function(node, event) {
var reactor_node = new JX.ReactorNode([], JX.id);
var no_path = null;
JX.DOM.listen(
node,
event,
no_path,
JX.bind(reactor_node, reactor_node.forceSendValue)
);
reactor_node.setGraphID(JX.DOM.uniqID(node));
return reactor_node;
},
_recvChangePulses : function(node) {
return JX.RDOM._recvEventPulses(node, 'change').transform(function() {
return node.value;
});
},
/**
* Sets up a bidirectional DynVal for a node.
* @param node :: DOM Node
* @param inPulsesFn :: DOM Node -> ReactorNode
* @param inDynValFn :: DOM Node -> ReactorNode -> DynVal
* @param outFn :: ReactorNode -> DOM Node
*/
_bidi : function(node, inPulsesFn, inDynValFn, outFn) {
var inPulses = inPulsesFn(node);
var inDynVal = inDynValFn(node, inPulses);
outFn(inDynVal.getChanges(), node);
inDynVal.getChanges().listen(inPulses);
return inDynVal;
},
/**
* ReactorNode[String] of the incoming values of a radio group.
* @param Array of DOM elements, all the radio buttons in a group.
*/
_recvRadioPulses : function(buttons) {
var ins = [];
for (var ii = 0; ii < buttons.length; ii++) {
ins.push(JX.RDOM._recvChangePulses(buttons[ii]));
}
return new JX.ReactorNode(ins, JX.id);
},
/**
* DynVal[String] of the incoming values of a radio group.
* pulses is a ReactorNode[String] of the incoming values of the group
*/
_recvRadio : function(buttons, pulses) {
var init = '';
for (var ii = 0; ii < buttons.length; ii++) {
if (buttons[ii].checked) {
init = buttons[ii].value;
break;
}
}
return new JX.DynVal(pulses, init);
},
/**
* Send the pulses from the ReactorNode[String] to the radio group.
* Sending an invalid value will result in a log message in __DEV__.
*/
_sendRadioPulses : function(rnode, buttons) {
return rnode.transform(function(val) {
var found;
if (__DEV__) {
found = false;
}
for (var ii = 0; ii < buttons.length; ii++) {
if (buttons[ii].value == val) {
buttons[ii].checked = true;
if (__DEV__) {
found = true;
}
}
}
if (__DEV__) {
if (!found) {
throw new Error("Mismatched radio button value");
}
}
});
},
/**
* Bidirectional DynVal[String] for a radio group.
* Sending an invalid value will result in a log message in __DEV__.
*/
radio : function(input) {
return JX.RDOM._bidi(
input,
JX.RDOM._recvRadioPulses,
JX.RDOM._recvRadio,
JX.RDOM._sendRadioPulses
);
},
/**
* ReactorNode[Boolean] of the values of the checkbox when it changes.
*/
_recvCheckboxPulses : function(checkbox) {
return JX.RDOM._recvChangePulses(checkbox).transform(function(val) {
return Boolean(val);
});
},
/**
* DynVal[Boolean] of the value of a checkbox.
*/
_recvCheckbox : function(checkbox, pulses) {
return new JX.DynVal(pulses, Boolean(checkbox.checked));
},
/**
* Send the pulses from the ReactorNode[Boolean] to the checkbox
*/
_sendCheckboxPulses : function(rnode, checkbox) {
return rnode.transform(function(val) {
if (__DEV__) {
if (!(val === true || val === false)) {
throw new Error("Send boolean values to checkboxes.");
}
}
checkbox.checked = val;
});
},
/**
* Bidirectional DynVal[Boolean] for a checkbox.
*/
checkbox : function(input) {
return JX.RDOM._bidi(
input,
JX.RDOM._recvCheckboxPulses,
JX.RDOM._recvCheckbox,
JX.RDOM._sendCheckboxPulses
);
},
/**
* ReactorNode[String] of the changing values of a text input.
*/
_recvInputPulses : function(input) {
// This misses advanced changes like paste events.
var live_changes = [
JX.RDOM._recvChangePulses(input),
JX.RDOM._recvEventPulses(input, 'keyup'),
JX.RDOM._recvEventPulses(input, 'keypress'),
JX.RDOM._recvEventPulses(input, 'keydown')
];
return new JX.ReactorNode(live_changes, function() {
return input.value;
});
},
/**
* DynVal[String] of the value of a text input.
*/
_recvInput : function(input, pulses) {
return new JX.DynVal(pulses, input.value);
},
/**
* Send the pulses from the ReactorNode[String] to the input
*/
_sendInputPulses : function(rnode, input) {
var result = rnode.transform(function(val) {
input.value = val;
});
result.setGraphID(JX.DOM.uniqID(input));
return result;
},
/**
* Bidirectional DynVal[String] for a text input.
*/
input : function(input) {
return JX.RDOM._bidi(
input,
JX.RDOM._recvInputPulses,
JX.RDOM._recvInput,
JX.RDOM._sendInputPulses
);
},
/**
* ReactorNode[String] of the incoming changes in value of a select element.
*/
_recvSelectPulses : function(select) {
return JX.RDOM._recvChangePulses(select);
},
/**
* DynVal[String] of the value of a select element.
*/
_recvSelect : function(select, pulses) {
return new JX.DynVal(pulses, select.value);
},
/**
* Send the pulses from the ReactorNode[String] to the select.
* Sending an invalid value will result in a log message in __DEV__.
*/
_sendSelectPulses : function(rnode, select) {
return rnode.transform(function(val) {
select.value = val;
if (__DEV__) {
if (select.value !== val) {
throw new Error("Mismatched select value");
}
}
});
},
/**
* Bidirectional DynVal[String] for the value of a select.
*/
select : function(select) {
return JX.RDOM._bidi(
select,
JX.RDOM._recvSelectPulses,
JX.RDOM._recvSelect,
JX.RDOM._sendSelectPulses
);
},
/**
* ReactorNode[undefined] that fires when a button is clicked.
*/
clickPulses : function(button) {
return JX.RDOM._recvEventPulses(button, 'click').transform(function() {
return null;
});
},
/**
* ReactorNode[Boolean] of whether the mouse is over a target.
*/
_recvIsMouseOverPulses : function(target) {
var mouseovers = JX.RDOM._recvEventPulses(target, 'mouseover').transform(
function() {
return true;
});
var mouseouts = JX.RDOM._recvEventPulses(target, 'mouseout').transform(
function() {
return false;
});
return new JX.ReactorNode([mouseovers, mouseouts], JX.id);
},
/**
* DynVal[Boolean] of whether the mouse is over a target.
*/
isMouseOver : function(target) {
// Not worth it to initialize this properly.
return new JX.DynVal(JX.RDOM._recvIsMouseOverPulses(target), false);
},
/**
* ReactorNode[Boolean] of whether an element has the focus.
*/
_recvHasFocusPulses : function(target) {
var focuses = JX.RDOM._recvEventPulses(target, 'focus').transform(
function() {
return true;
});
var blurs = JX.RDOM._recvEventPulses(target, 'blur').transform(
function() {
return false;
});
return new JX.ReactorNode([focuses, blurs], JX.id);
},
/**
* DynVal[Boolean] of whether an element has the focus.
*/
_recvHasFocus : function(target) {
var is_focused_now = (target === document.activeElement);
return new JX.DynVal(JX.RDOM._recvHasFocusPulses(target), is_focused_now);
},
_sendHasFocusPulses : function(rnode, target) {
rnode.transform(function(should_focus) {
if (should_focus) {
target.focus();
} else {
target.blur();
}
return should_focus;
});
},
/**
* Bidirectional DynVal[Boolean] of whether an element has the focus.
*/
hasFocus : function(target) {
return JX.RDOM._bidi(
target,
JX.RDOM._recvHasFocusPulses,
JX.RDOM._recvHasFocus,
JX.RDOM._sendHasFocusPulses
);
},
/**
* Send a CSS class from a DynVal to a node
*/
sendClass : function(dynval, node, className) {
return dynval.transform(function(add) {
JX.DOM.alterClass(node, className, add);
});
},
/**
* Dynamically attach a set of DynVals to a DOM node's properties as
* specified by props.
* props: {left: someDynVal, style: {backgroundColor: someOtherDynVal}}
*/
sendProps : function(node, props) {
var dynvals = [];
var keys = [];
var style_keys = [];
for (var key in props) {
keys.push(key);
if (key === 'style') {
for (var style_key in props[key]) {
style_keys.push(style_key);
dynvals.push(props[key][style_key]);
node.style[style_key] = props[key][style_key].getValueNow();
}
} else {
dynvals.push(props[key]);
node[key] = props[key].getValueNow();
}
}
return JX.Reactor.lift(JX.bind(null, function(keys, style_keys, node) {
var args = JX.$A(arguments).slice(3);
for (var ii = 0; ii < args.length; ii++) {
if (keys[ii] === 'style') {
for (var jj = 0; jj < style_keys.length; jj++) {
node.style[style_keys[jj]] = args[ii];
ii++;
}
ii--;
} else {
node[keys[ii]] = args[ii];
}
}
}, keys, style_keys, node), dynvals);
}
}
});
-
-
diff --git a/webroot/rsrc/externals/javelin/ext/view/HTMLView.js b/webroot/rsrc/externals/javelin/ext/view/HTMLView.js
index aea6f7c7bd..244b252e05 100644
--- a/webroot/rsrc/externals/javelin/ext/view/HTMLView.js
+++ b/webroot/rsrc/externals/javelin/ext/view/HTMLView.js
@@ -1,138 +1,137 @@
/**
* Dumb HTML views. Mostly to demonstrate how the visitor pattern over these
* views works, as driven by validation. I'm not convinced it's actually a good
* idea to do validation.
*
* @provides javelin-view-html
* @requires javelin-install
* javelin-dom
* javelin-view-visitor
* javelin-util
*/
JX.install('HTMLView', {
extend: 'View',
members : {
render: function(rendered_children) {
return JX.$N(this.getName(), this.getAllAttributes(), rendered_children);
},
validate: function() {
this.accept(JX.HTMLView.getValidatingVisitor());
}
},
statics: {
getValidatingVisitor: function() {
return new JX.ViewVisitor(JX.HTMLView.validate);
},
validate: function(view, children) {
var spec = this._getHTMLSpec();
if (!(view.getName() in spec)) {
throw new Error("invalid tag");
}
var tag_spec = spec[view.getName()];
var attrs = view.getAllAttributes();
for (var attr in attrs) {
if (!(attr in tag_spec)) {
throw new Error("invalid attr");
}
var validator = tag_spec[attr];
if (typeof validator === "function") {
return validator(attrs[attr]);
}
}
return true;
},
_validateRel: function(target) {
return target in {
"_blank": 1,
"_self": 1,
"_parent": 1,
"_top": 1
};
},
_getHTMLSpec: function() {
var attrs_any_can_have = {
className: 1,
id: 1,
sigil: 1
};
var form_elem_attrs = {
name: 1,
value: 1
};
var spec = {
a: { href: 1, target: JX.HTMLView._validateRel },
b: {},
blockquote: {},
br: {},
button: JX.copy({}, form_elem_attrs),
canvas: {},
code: {},
dd: {},
div: {},
dl: {},
dt: {},
em: {},
embed: {},
fieldset: {},
form: { type: 1 },
h1: {},
h2: {},
h3: {},
h4: {},
h5: {},
h6: {},
hr: {},
i: {},
iframe: { src: 1 },
img: { src: 1, alt: 1 },
input: JX.copy({}, form_elem_attrs),
label: {'for': 1},
li: {},
ol: {},
optgroup: {},
option: JX.copy({}, form_elem_attrs),
p: {},
pre: {},
q: {},
select: {},
span: {},
strong: {},
sub: {},
sup: {},
table: {},
tbody: {},
td: {},
textarea: {},
tfoot: {},
th: {},
thead: {},
tr: {},
ul: {}
};
for (var k in spec) {
JX.copy(spec[k], attrs_any_can_have);
}
return spec;
},
registerToInterpreter: function(view_interpreter) {
var spec = this._getHTMLSpec();
for (var tag in spec) {
view_interpreter.register(tag, JX.HTMLView);
}
return view_interpreter;
}
}
});
-
diff --git a/webroot/rsrc/externals/javelin/ext/view/View.js b/webroot/rsrc/externals/javelin/ext/view/View.js
index 39d608a715..3e67320bcf 100644
--- a/webroot/rsrc/externals/javelin/ext/view/View.js
+++ b/webroot/rsrc/externals/javelin/ext/view/View.js
@@ -1,192 +1,191 @@
/**
* A View is a composable wrapper on JX.$N, allowing abstraction of higher-order
* views and a consistent pattern of parameterization. It is intended
* to be used either directly or as a building block for a syntactic sugar layer
* for concise expression of markup patterns.
*
* @provides javelin-view
* @requires javelin-install
* javelin-util
*/
JX.install('View', {
construct : function(attrs, children) {
this._attributes = JX.copy({}, this.getDefaultAttributeValues());
JX.copy(this._attributes, attrs);
this._rawChildren = {};
this._childKeys = [];
if (children) {
this.addChildren(JX.$AX(children));
}
this.setName(this.__class__.__readable__);
},
events: [
'change'
],
properties: {
'name': null
},
members : {
_attributes : null,
_rawChildren : null,
_childKeys: null, // keys of rawChildren, kept ordered.
_nextChildKey: 0, // next key to use for a new child
/*
* Don't override.
* TODO: Strongly typed attribute access (getIntAttr, getStringAttr...)?
*/
getAttr : function(attrName) {
return this._attributes[attrName];
},
/*
* Don't override.
*/
multisetAttr : function(attrs) {
JX.copy(this._attributes, attrs);
this.invoke('change');
return this;
},
/*
* Don't override.
*/
setAttr : function(attrName, value) {
this._attributes[attrName] = value;
this.invoke('change');
return this;
},
/*
* Child views can override to specify default values for attributes.
*/
getDefaultAttributeValues : function() {
return {};
},
/**
* Don't override.
*/
getAllAttributes: function() {
return JX.copy({}, this._attributes);
},
/**
* Get the children. Don't override.
*/
getChildren : function() {
var result = [];
var should_repack = false;
var ii;
var key;
for (ii = 0; ii < this._childKeys.length; ii++) {
key = this._childKeys[ii];
if (this._rawChildren[key] === undefined) {
should_repack = true;
} else {
result.push(this._rawChildren[key]);
}
}
if (should_repack) {
var new_child_keys = [];
for (ii = 0; ii < this._childKeys.length; ii++) {
key = this._childKeys[ii];
if (this._rawChildren[key] !== undefined) {
new_child_keys.push(key);
}
}
this._childKeys = new_child_keys;
}
return result;
},
/**
* Add children to the view. Returns array of removal handles.
* Don't override.
*/
addChildren : function(children) {
var result = [];
for (var ii = 0; ii < children.length; ii++) {
result.push(this._addChild(children[ii]));
}
this.invoke('change');
return result;
},
/**
* Add a single child view to the view.
* Returns a removal handle, i.e. an object that has a method remove(),
* that removes the added child from the view.
*
* Don't override.
*/
addChild: function(child) {
var result = this._addChild(child);
this.invoke('change');
return result;
},
_addChild: function(child) {
var key = this._nextChildKey++;
this._rawChildren[key] = child;
this._childKeys.push(key);
return {
remove: JX.bind(this, this._removeChild, key)
};
},
_removeChild: function(child_key) {
delete this._rawChildren[child_key];
this.invoke('change');
},
/**
* Accept visitors. This allows adding new behaviors to Views without
* having to change View classes themselves.
*
* This implements a post-order traversal over the tree of views. Children
* are processed before parents, and for convenience the results of the
* visitor on the children are passed to it when processing the parent.
*
* The visitor parameter is a callable which receives two parameters.
* The first parameter is the view to visit. The second parameter is an
* array of the results of visiting the view's children.
*
* Don't override.
*/
accept: function(visitor) {
var results = [];
var children = this.getChildren();
for(var ii = 0; ii < children.length; ii++) {
var result;
if (children[ii].accept) {
result = children[ii].accept(visitor);
} else {
result = children[ii];
}
results.push(result);
}
return visitor(this, results);
},
/**
* Given the already-rendered children, return the rendered result of
* this view.
* By default, just pass the children through.
*/
render: function(rendered_children) {
return rendered_children;
}
}
});
-
diff --git a/webroot/rsrc/externals/javelin/ext/view/ViewRenderer.js b/webroot/rsrc/externals/javelin/ext/view/ViewRenderer.js
index 3f11a3bec0..3fd3493683 100644
--- a/webroot/rsrc/externals/javelin/ext/view/ViewRenderer.js
+++ b/webroot/rsrc/externals/javelin/ext/view/ViewRenderer.js
@@ -1,20 +1,19 @@
/**
* @provides javelin-view-renderer
* @requires javelin-install
* javelin-util
*/
JX.install('ViewRenderer', {
members: {
visit: function(view, children) {
return view.render(children);
}
},
statics: {
render: function(view) {
var renderer = new JX.ViewRenderer();
return view.accept(JX.bind(renderer, renderer.visit));
}
}
});
-
diff --git a/webroot/rsrc/externals/javelin/ext/view/ViewVisitor.js b/webroot/rsrc/externals/javelin/ext/view/ViewVisitor.js
index 8c073cecb1..53d430c708 100644
--- a/webroot/rsrc/externals/javelin/ext/view/ViewVisitor.js
+++ b/webroot/rsrc/externals/javelin/ext/view/ViewVisitor.js
@@ -1,36 +1,35 @@
/**
* @provides javelin-view-visitor
* @requires javelin-install
* javelin-util
*
* Add new behaviors to views without changing the view classes themselves.
*
* Allows you to register specific visitor functions for certain view classes.
* If no visitor is registered for a view class, the default_visitor is used.
* If no default_visitor is invoked, a no-op visitor is used.
*
* Registered visitors should be functions with signature
* function(view, results_of_visiting_children) {}
* Children are visited before their containing parents, and the return values
* of the visitor on the children are passed to the parent.
*
*/
JX.install('ViewVisitor', {
construct: function(default_visitor) {
this._visitors = {};
this._default = default_visitor || JX.bag;
},
members: {
_visitors: null,
_default: null,
register: function(cls, visitor) {
this._visitors[cls] = visitor;
},
visit: function(view, children) {
var visitor = this._visitors[cls] || this._default;
return visitor(view, children);
}
}
});
-
diff --git a/webroot/rsrc/externals/javelin/lib/DOM.js b/webroot/rsrc/externals/javelin/lib/DOM.js
index 5c94d406bc..42608a8e92 100644
--- a/webroot/rsrc/externals/javelin/lib/DOM.js
+++ b/webroot/rsrc/externals/javelin/lib/DOM.js
@@ -1,965 +1,964 @@
/**
* @requires javelin-magical-init
* javelin-install
* javelin-util
* javelin-vector
* javelin-stratcom
* @provides javelin-dom
*
* @javelin-installs JX.$
* @javelin-installs JX.$N
* @javelin-installs JX.$H
*
* @javelin
*/
/**
* Select an element by its "id" attribute, like ##document.getElementById()##.
* For example:
*
* var node = JX.$('some_id');
*
* This will select the node with the specified "id" attribute:
*
* LANG=HTML
* <div id="some_id">...</div>
*
* If the specified node does not exist, @{JX.$()} will throw an exception.
*
* For other ways to select nodes from the document, see @{JX.DOM.scry()} and
* @{JX.DOM.find()}.
*
* @param string "id" attribute to select from the document.
* @return Node Node with the specified "id" attribute.
*
* @group dom
*/
JX.$ = function(id) {
if (__DEV__) {
if (!id) {
JX.$E('Empty ID passed to JX.$()!');
}
}
var node = document.getElementById(id);
if (!node || (node.id != id)) {
if (__DEV__) {
if (node && (node.id != id)) {
JX.$E(
'JX.$("'+id+'"): '+
'document.getElementById() returned an element without the '+
'correct ID. This usually means that the element you are trying '+
'to select is being masked by a form with the same value in its '+
'"name" attribute.');
}
}
JX.$E("JX.$('" + id + "') call matched no nodes.");
}
return node;
};
/**
* Upcast a string into an HTML object so it is treated as markup instead of
* plain text. See @{JX.$N} for discussion of Javelin's security model. Every
* time you call this function you potentially open up a security hole. Avoid
* its use wherever possible.
*
* This class intentionally supports only a subset of HTML because many browsers
* named "Internet Explorer" have awkward restrictions around what they'll
* accept for conversion to document fragments. Alter your datasource to emit
* valid HTML within this subset if you run into an unsupported edge case. All
* the edge cases are crazy and you should always be reasonably able to emit
* a cohesive tag instead of an unappendable fragment.
*
* You may use @{JX.$H} as a shortcut for creating new JX.HTML instances:
*
* JX.$N('div', {}, some_html_blob); // Treat as string (safe)
* JX.$N('div', {}, JX.$H(some_html_blob)); // Treat as HTML (unsafe!)
*
* @task build String into HTML
* @task nodes HTML into Nodes
*
* @group dom
*/
JX.install('HTML', {
construct : function(str) {
if (str instanceof JX.HTML) {
this._content = str._content;
return;
}
if (__DEV__) {
if ((typeof str !== 'string') && (!str || !str.match)) {
JX.$E(
'new JX.HTML(<empty?>): ' +
'call initializes an HTML object with an empty value.');
}
var tags = ['legend', 'thead', 'tbody', 'tfoot', 'column', 'colgroup',
'caption', 'tr', 'th', 'td', 'option'];
var evil_stuff = new RegExp('^\\s*<(' + tags.join('|') + ')\\b', 'i');
var match = str.match(evil_stuff);
if (match) {
JX.$E(
'new JX.HTML("<' + match[1] + '>..."): ' +
'call initializes an HTML object with an invalid partial fragment ' +
'and can not be converted into DOM nodes. The enclosing tag of an ' +
'HTML content string must be appendable to a document fragment. ' +
'For example, <table> is allowed but <tr> or <tfoot> are not.');
}
var really_evil = /<script\b/;
if (str.match(really_evil)) {
JX.$E(
'new JX.HTML("...<script>..."): ' +
'call initializes an HTML object with an embedded script tag! ' +
'Are you crazy?! Do NOT do this!!!');
}
var wont_work = /<object\b/;
if (str.match(wont_work)) {
JX.$E(
'new JX.HTML("...<object>..."): ' +
'call initializes an HTML object with an embedded <object> tag. IE ' +
'will not do the right thing with this.');
}
// TODO(epriestley): May need to deny <option> more broadly, see
// http://support.microsoft.com/kb/829907 and the whole mess in the
// heavy stack. But I seem to have gotten away without cloning into the
// documentFragment below, so this may be a nonissue.
}
this._content = str;
},
members : {
_content : null,
/**
* Convert the raw HTML string into a DOM node tree.
*
* @task nodes
* @return DocumentFragment A document fragment which contains the nodes
* corresponding to the HTML string you provided.
*/
getFragment : function() {
var wrapper = JX.$N('div');
wrapper.innerHTML = this._content;
var fragment = document.createDocumentFragment();
while (wrapper.firstChild) {
// TODO(epriestley): Do we need to do a bunch of cloning junk here?
// See heavy stack. I'm disconnecting the nodes instead; this seems
// to work but maybe my test case just isn't extensive enough.
fragment.appendChild(wrapper.removeChild(wrapper.firstChild));
}
return fragment;
},
/**
* Convert the raw HTML string into a single DOM node. This only works
* if the element has a single top-level element. Otherwise, use
* @{method:getFragment} to get a document fragment instead.
*
* @return Node Single node represented by the object.
* @task nodes
*/
getNode : function() {
var fragment = this.getFragment();
if (__DEV__) {
if (fragment.childNodes.length < 1) {
JX.$E('JX.HTML.getNode(): Markup has no root node!');
}
if (fragment.childNodes.length > 1) {
JX.$E('JX.HTML.getNode(): Markup has more than one root node!');
}
}
return fragment.firstChild;
}
}
});
/**
* Build a new HTML object from a trustworthy string. JX.$H is a shortcut for
* creating new JX.HTML instances.
*
* @task build
* @param string A string which you want to be treated as HTML, because you
* know it is from a trusted source and any data in it has been
* properly escaped.
* @return JX.HTML HTML object, suitable for use with @{JX.$N}.
*
* @group dom
*/
JX.$H = function(str) {
return new JX.HTML(str);
};
/**
* Create a new DOM node with attributes and content.
*
* var link = JX.$N('a');
*
* This creates a new, empty anchor tag without any attributes. The equivalent
* markup would be:
*
* LANG=HTML
* <a />
*
* You can also specify attributes by passing a dictionary:
*
* JX.$N('a', {name: 'anchor'});
*
* This is equivalent to:
*
* LANG=HTML
* <a name="anchor" />
*
* Additionally, you can specify content:
*
* JX.$N(
* 'a',
* {href: 'http://www.javelinjs.com'},
* 'Visit the Javelin Homepage');
*
* This is equivalent to:
*
* LANG=HTML
* <a href="http://www.javelinjs.com">Visit the Javelin Homepage</a>
*
* If you only want to specify content, you can omit the attribute parameter.
* That is, these calls are equivalent:
*
* JX.$N('div', {}, 'Lorem ipsum...'); // No attributes.
* JX.$N('div', 'Lorem ipsum...') // Same as above.
*
* Both are equivalent to:
*
* LANG=HTML
* <div>Lorem ipsum...</div>
*
* Note that the content is treated as plain text, not HTML. This means it is
* safe to use untrusted strings:
*
* JX.$N('div', '<script src="evil.com" />');
*
* This is equivalent to:
*
* LANG=HTML
* <div>&lt;script src="evil.com" /&gt;</div>
*
* That is, the content will be properly escaped and will not create a
* vulnerability. If you want to set HTML content, you can use @{JX.HTML}:
*
* JX.$N('div', JX.$H(some_html));
*
* **This is potentially unsafe**, so make sure you understand what you're
* doing. You should usually avoid passing HTML around in string form. See
* @{JX.HTML} for discussion.
*
* You can create new nodes with a Javelin sigil (and, optionally, metadata) by
* providing "sigil" and "meta" keys in the attribute dictionary.
*
* @param string Tag name, like 'a' or 'div'.
* @param dict|string|@{JX.HTML}? Property dictionary, or content if you don't
* want to specify any properties.
* @param string|@{JX.HTML}? Content string (interpreted as plain text)
* or @{JX.HTML} object (interpreted as HTML,
* which may be dangerous).
* @return Node New node with whatever attributes and
* content were specified.
*
* @group dom
*/
JX.$N = function(tag, attr, content) {
if (typeof content == 'undefined' &&
(typeof attr != 'object' || attr instanceof JX.HTML)) {
content = attr;
attr = {};
}
if (__DEV__) {
if (tag.toLowerCase() != tag) {
JX.$E(
'$N("'+tag+'", ...): '+
'tag name must be in lower case; '+
'use "'+tag.toLowerCase()+'", not "'+tag+'".');
}
}
var node = document.createElement(tag);
if (attr.style) {
JX.copy(node.style, attr.style);
delete attr.style;
}
if (attr.sigil) {
JX.Stratcom.addSigil(node, attr.sigil);
delete attr.sigil;
}
if (attr.meta) {
JX.Stratcom.addData(node, attr.meta);
delete attr.meta;
}
if (__DEV__) {
if (('metadata' in attr) || ('data' in attr)) {
JX.$E(
'$N(' + tag + ', ...): ' +
'use the key "meta" to specify metadata, not "data" or "metadata".');
}
}
JX.copy(node, attr);
if (content) {
JX.DOM.setContent(node, content);
}
return node;
};
/**
* Query and update the DOM. Everything here is static, this is essentially
* a collection of common utility functions.
*
* @task stratcom Attaching Event Listeners
* @task content Changing DOM Content
* @task nodes Updating Nodes
* @task serialize Serializing Forms
* @task test Testing DOM Properties
* @task convenience Convenience Methods
* @task query Finding Nodes in the DOM
* @task view Changing View State
*
* @group dom
*/
JX.install('DOM', {
statics : {
_autoid : 0,
_uniqid : 0,
_metrics : {},
/* -( Changing DOM Content )----------------------------------------------- */
/**
* Set the content of some node. This uses the same content semantics as
* other Javelin content methods, see @{function:JX.$N} for a detailed
* explanation. Previous content will be replaced: you can also
* @{method:prependContent} or @{method:appendContent}.
*
* @param Node Node to set content of.
* @param mixed Content to set.
* @return void
* @task content
*/
setContent : function(node, content) {
if (__DEV__) {
if (!JX.DOM.isNode(node)) {
JX.$E(
'JX.DOM.setContent(<yuck>, ...): '+
'first argument must be a DOM node.');
}
}
while (node.firstChild) {
JX.DOM.remove(node.firstChild);
}
JX.DOM.appendContent(node, content);
},
/**
* Prepend content to some node. This method uses the same content semantics
* as other Javelin methods, see @{function:JX.$N} for an explanation. You
* can also @{method:setContent} or @{method:appendContent}.
*
* @param Node Node to prepend content to.
* @param mixed Content to prepend.
* @return void
* @task content
*/
prependContent : function(node, content) {
if (__DEV__) {
if (!JX.DOM.isNode(node)) {
JX.$E(
'JX.DOM.prependContent(<junk>, ...): '+
'first argument must be a DOM node.');
}
}
this._insertContent(node, content, this._mechanismPrepend, true);
},
/**
* Append content to some node. This method uses the same content semantics
* as other Javelin methods, see @{function:JX.$N} for an explanation. You
* can also @{method:setContent} or @{method:prependContent}.
*
* @param Node Node to append the content of.
* @param mixed Content to append.
* @return void
* @task content
*/
appendContent : function(node, content) {
if (__DEV__) {
if (!JX.DOM.isNode(node)) {
JX.$E(
'JX.DOM.appendContent(<bleh>, ...): '+
'first argument must be a DOM node.');
}
}
this._insertContent(node, content, this._mechanismAppend);
},
/**
* Internal, add content to a node by prepending.
*
* @param Node Node to prepend content to.
* @param Node Node to prepend.
* @return void
* @task content
*/
_mechanismPrepend : function(node, content) {
node.insertBefore(content, node.firstChild);
},
/**
* Internal, add content to a node by appending.
*
* @param Node Node to append content to.
* @param Node Node to append.
* @task content
*/
_mechanismAppend : function(node, content) {
node.appendChild(content);
},
/**
* Internal, add content to a node using some specified mechanism.
*
* @param Node Node to add content to.
* @param mixed Content to add.
* @param function Callback for actually adding the nodes.
* @param bool True if array elements should be passed to the mechanism
* in reverse order, i.e. the mechanism prepends nodes.
* @return void
* @task content
*/
_insertContent : function(parent, content, mechanism, reverse) {
if (JX.isArray(content)) {
if (reverse) {
content = [].concat(content).reverse();
}
for (var ii = 0; ii < content.length; ii++) {
JX.DOM._insertContent(parent, content[ii], mechanism, reverse);
}
} else {
var type = typeof content;
if (content instanceof JX.HTML) {
content = content.getFragment();
} else if (type == 'string' || type == 'number') {
content = document.createTextNode(content);
}
if (__DEV__) {
if (content && !content.nodeType) {
JX.$E(
'JX.DOM._insertContent(<node>, ...): '+
'second argument must be a string, a number, ' +
'a DOM node or a JX.HTML instance');
}
}
content && mechanism(parent, content);
}
},
/* -( Updating Nodes )----------------------------------------------------- */
/**
* Remove a node from its parent, so it is no longer a child of any other
* node.
*
* @param Node Node to remove.
* @return Node The node.
* @task nodes
*/
remove : function(node) {
node.parentNode && JX.DOM.replace(node, null);
return node;
},
/**
* Replace a node with some other piece of content. This method obeys
* Javelin content semantics, see @{function:JX.$N} for an explanation.
* You can also @{method:setContent}, @{method:prependContent}, or
* @{method:appendContent}.
*
* @param Node Node to replace.
* @param mixed Content to replace it with.
* @return Node the original node.
* @task nodes
*/
replace : function(node, replacement) {
if (__DEV__) {
if (!node.parentNode) {
JX.$E(
'JX.DOM.replace(<node>, ...): '+
'node has no parent node, so it can not be replaced.');
}
}
var mechanism;
if (node.nextSibling) {
mechanism = JX.bind(node.nextSibling, function(parent, content) {
parent.insertBefore(content, this);
});
} else {
mechanism = this._mechanismAppend;
}
var parent = node.parentNode;
parent.removeChild(node);
this._insertContent(parent, replacement, mechanism);
return node;
},
/* -( Serializing Froms )-------------------------------------------------- */
/**
* Converts a form into a list of <name, value> pairs.
*
* Note: This function explicity does not match for submit inputs as there
* could be multiple in a form. It's the caller's obligation to add the
* submit input value if desired.
*
* @param Node The form element to convert into a list of pairs.
* @return List A list of <name, value> pairs.
* @task serialize
*/
convertFormToListOfPairs : function(form) {
var elements = form.getElementsByTagName('*');
var data = [];
for (var ii = 0; ii < elements.length; ++ii) {
if (!elements[ii].name) {
continue;
}
if (elements[ii].disabled) {
continue;
}
var type = elements[ii].type;
var tag = elements[ii].tagName;
if ((type in {radio: 1, checkbox: 1} && elements[ii].checked) ||
type in {text: 1, hidden: 1, password: 1, email: 1, tel: 1,
number: 1} ||
tag in {TEXTAREA: 1, SELECT: 1}) {
data.push([elements[ii].name, elements[ii].value]);
}
}
return data;
},
/**
* Converts a form into a dictionary mapping input names to values. This
* will overwrite duplicate inputs in an undefined way.
*
* @param Node The form element to convert into a dictionary.
* @return Dict A dictionary of form values.
* @task serialize
*/
convertFormToDictionary : function(form) {
var data = {};
var pairs = JX.DOM.convertFormToListOfPairs(form);
for (var ii = 0; ii < pairs.length; ii++) {
data[pairs[ii][0]] = pairs[ii][1];
}
return data;
},
/* -( Testing DOM Properties )--------------------------------------------- */
/**
* Test if an object is a valid Node.
*
* @param wild Something which might be a Node.
* @return bool True if the parameter is a DOM node.
* @task test
*/
isNode : function(node) {
return !!(node && node.nodeName && (node !== window));
},
/**
* Test if an object is a node of some specific (or one of several) types.
* For example, this tests if the node is an ##<input />##, ##<select />##,
* or ##<textarea />##.
*
* JX.DOM.isType(node, ['input', 'select', 'textarea']);
*
* @param wild Something which might be a Node.
* @param string|list One or more tags which you want to test for.
* @return bool True if the object is a node, and it's a node of one
* of the provided types.
* @task test
*/
isType : function(node, of_type) {
node = ('' + (node.nodeName || '')).toUpperCase();
of_type = JX.$AX(of_type);
for (var ii = 0; ii < of_type.length; ++ii) {
if (of_type[ii].toUpperCase() == node) {
return true;
}
}
return false;
},
/**
* Listen for events occuring beneath a specific node in the DOM. This is
* similar to @{JX.Stratcom.listen()}, but allows you to specify some node
* which serves as a scope instead of the default scope (the whole document)
* which you get if you install using @{JX.Stratcom.listen()} directly. For
* example, to listen for clicks on nodes with the sigil 'menu-item' below
* the root menu node:
*
* var the_menu = getReferenceToTheMenuNodeSomehow();
* JX.DOM.listen(the_menu, 'click', 'menu-item', function(e) { ... });
*
* @task stratcom
* @param Node The node to listen for events underneath.
* @param string|list One or more event types to listen for.
* @param list? A path to listen on, or a list of paths.
* @param function Callback to invoke when a matching event occurs.
* @return object A reference to the installed listener. You can later
* remove the listener by calling this object's remove()
* method.
*/
listen : function(node, type, path, callback) {
var auto_id = ['autoid:' + JX.DOM._getAutoID(node)];
path = JX.$AX(path || []);
if (!path.length) {
path = auto_id;
} else {
for (var ii = 0; ii < path.length; ii++) {
path[ii] = auto_id.concat(JX.$AX(path[ii]));
}
}
return JX.Stratcom.listen(type, path, callback);
},
/**
* Invoke a custom event on a node. This method is a companion to
* @{method:JX.DOM.listen} and parallels @{method:JX.Stratcom.invoke} in
* the same way that method parallels @{method:JX.Stratcom.listen}.
*
* This method can not be used to invoke native events (like 'click').
*
* @param Node The node to invoke an event on.
* @param string Custom event type.
* @param dict Event data.
* @return JX.Event The event object which was dispatched to listeners.
* The main use of this is to test whether any
* listeners prevented the event.
*/
invoke : function(node, type, data) {
if (__DEV__) {
if (type in JX.__allowedEvents) {
throw new Error(
'JX.DOM.invoke(..., "' + type + '", ...): ' +
'you cannot invoke with the same type as a native event.');
}
}
return JX.Stratcom.dispatch({
target: node,
type: type,
customData: data
});
},
uniqID : function(node) {
if (!node.getAttribute('id')) {
node.setAttribute('id', 'uniqid_'+(++JX.DOM._uniqid));
}
return node.getAttribute('id');
},
alterClass : function(node, className, add) {
if (__DEV__) {
if (add !== false && add !== true) {
JX.$E(
'JX.DOM.alterClass(...): ' +
'expects the third parameter to be Boolean: ' +
add + ' was provided');
}
}
var has = ((' '+node.className+' ').indexOf(' '+className+' ') > -1);
if (add && !has) {
node.className += ' '+className;
} else if (has && !add) {
node.className = node.className.replace(
new RegExp('(^|\\s)' + className + '(?:\\s|$)', 'g'), ' ');
}
},
htmlize : function(str) {
return (''+str)
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
},
/**
* Show one or more elements, by removing their "display" style. This
* assumes you have hidden them with @{method:hide}, or explicitly set
* the style to `display: none;`.
*
* @task convenience
* @param ... One or more nodes to remove "display" styles from.
* @return void
*/
show : function() {
var ii;
if (__DEV__) {
for (ii = 0; ii < arguments.length; ++ii) {
if (!arguments[ii]) {
JX.$E(
'JX.DOM.show(...): ' +
'one or more arguments were null or empty.');
}
}
}
for (ii = 0; ii < arguments.length; ++ii) {
arguments[ii].style.display = '';
}
},
/**
* Hide one or more elements, by setting `display: none;` on them. This is
* a convenience method. See also @{method:show}.
*
* @task convenience
* @param ... One or more nodes to set "display: none" on.
* @return void
*/
hide : function() {
var ii;
if (__DEV__) {
for (ii = 0; ii < arguments.length; ++ii) {
if (!arguments[ii]) {
JX.$E(
'JX.DOM.hide(...): ' +
'one or more arguments were null or empty.');
}
}
}
for (ii = 0; ii < arguments.length; ++ii) {
arguments[ii].style.display = 'none';
}
},
textMetrics : function(node, pseudoclass, x) {
if (!this._metrics[pseudoclass]) {
var n = JX.$N(
'var',
{className: pseudoclass});
this._metrics[pseudoclass] = n;
}
var proxy = this._metrics[pseudoclass];
document.body.appendChild(proxy);
proxy.style.width = x ? (x+'px') : '';
JX.DOM.setContent(
proxy,
JX.$H(JX.DOM.htmlize(node.value).replace(/\n/g, '<br />')));
var metrics = JX.Vector.getDim(proxy);
document.body.removeChild(proxy);
return metrics;
},
/**
* Search the document for DOM nodes by providing a root node to look
* beneath, a tag name, and (optionally) a sigil. Nodes which match all
* specified conditions are returned.
*
* @task query
*
* @param Node Root node to search beneath.
* @param string Tag name, like 'a' or 'textarea'.
* @param string Optionally, a sigil which nodes are required to have.
*
* @return list List of matching nodes, which may be empty.
*/
scry : function(root, tagname, sigil) {
if (__DEV__) {
if (!JX.DOM.isNode(root)) {
JX.$E(
'JX.DOM.scry(<yuck>, ...): '+
'first argument must be a DOM node.');
}
}
var nodes = root.getElementsByTagName(tagname);
if (!sigil) {
return JX.$A(nodes);
}
var result = [];
for (var ii = 0; ii < nodes.length; ii++) {
if (JX.Stratcom.hasSigil(nodes[ii], sigil)) {
result.push(nodes[ii]);
}
}
return result;
},
/**
* Select a node uniquely identified by a root, tagname and sigil. This
* is similar to JX.DOM.scry() but expects exactly one result.
*
* @task query
*
* @param Node Root node to search beneath.
* @param string Tag name, like 'a' or 'textarea'.
* @param string Optionally, sigil which selected node must have.
*
* @return Node Node uniquely identified by the criteria.
*/
find : function(root, tagname, sigil) {
if (__DEV__) {
if (!JX.DOM.isNode(root)) {
JX.$E(
'JX.DOM.find(<glop>, "'+tagname+'", "'+sigil+'"): '+
'first argument must be a DOM node.');
}
}
var result = JX.DOM.scry(root, tagname, sigil);
if (__DEV__) {
if (result.length > 1) {
JX.$E(
'JX.DOM.find(<node>, "'+tagname+'", "'+sigil+'"): '+
'matched more than one node.');
}
}
if (!result.length) {
JX.$E(
'JX.DOM.find(<node>, "' + tagname + '", "' + sigil + '"): ' +
'matched no nodes.');
}
return result[0];
},
/**
* Select a node uniquely identified by an anchor, tagname, and sigil. This
* is similar to JX.DOM.find() but walks up the DOM tree instead of down
* it.
*
* @param Node Node to look above.
* @param string Tag name, like 'a' or 'textarea'.
* @param string Optionally, sigil which selected node must have.
* @return Node Matching node.
*
* @task query
*/
findAbove : function(anchor, tagname, sigil) {
if (__DEV__) {
if (!JX.DOM.isNode(anchor)) {
JX.$E(
'JX.DOM.findAbove(<glop>, "' + tagname + '", "' + sigil + '"): ' +
'first argument must be a DOM node.');
}
}
var result = anchor.parentNode;
while (true) {
if (!result) {
break;
}
if (JX.DOM.isType(result, tagname)) {
if (!sigil || JX.Stratcom.hasSigil(result, sigil)) {
break;
}
}
result = result.parentNode;
}
if (!result) {
JX.$E(
'JX.DOM.findAbove(<node>, "' + tagname + '", "' + sigil + '"): ' +
'no matching node.');
}
return result;
},
/**
* Focus a node safely. This is just a convenience wrapper that allows you
* to avoid IE's habit of throwing when nearly any focus operation is
* invoked.
*
* @task convenience
* @param Node Node to move cursor focus to, if possible.
* @return void
*/
focus : function(node) {
try { node.focus(); } catch (lol_ie) {}
},
/**
* Scroll to the position of an element in the document.
* @task view
* @param Node Node to move document scroll position to, if possible.
* @return void
*/
scrollTo : function(node) {
window.scrollTo(0, JX.$V(node).y);
},
_getAutoID : function(node) {
if (!node.getAttribute('data-autoid')) {
node.setAttribute('data-autoid', 'autoid_'+(++JX.DOM._autoid));
}
return node.getAttribute('data-autoid');
}
}
});
-
diff --git a/webroot/rsrc/externals/javelin/lib/__tests__/URI.js b/webroot/rsrc/externals/javelin/lib/__tests__/URI.js
index 62387c3b9e..a162ad59b8 100644
--- a/webroot/rsrc/externals/javelin/lib/__tests__/URI.js
+++ b/webroot/rsrc/externals/javelin/lib/__tests__/URI.js
@@ -1,303 +1,302 @@
/**
* @requires javelin-uri javelin-php-serializer
*/
describe('Javelin URI', function() {
it('should understand parts of a uri', function() {
var uri = JX.$U('http://www.facebook.com:123/home.php?key=value#fragment');
expect(uri.getProtocol()).toEqual('http');
expect(uri.getDomain()).toEqual('www.facebook.com');
expect(uri.getPort()).toEqual('123');
expect(uri.getPath()).toEqual('/home.php');
expect(uri.getQueryParams()).toEqual({'key' : 'value'});
expect(uri.getFragment()).toEqual('fragment');
});
it('can accept null as uri string', function() {
var uri = JX.$U(null);
expect(uri.getProtocol()).toEqual(undefined);
expect(uri.getDomain()).toEqual(undefined);
expect(uri.getPath()).toEqual(undefined);
expect(uri.getQueryParams()).toEqual({});
expect(uri.getFragment()).toEqual(undefined);
expect(uri.toString()).toEqual('');
});
it('can accept empty string as uri string', function() {
var uri = JX.$U('');
expect(uri.getProtocol()).toEqual(undefined);
expect(uri.getDomain()).toEqual(undefined);
expect(uri.getPath()).toEqual(undefined);
expect(uri.getQueryParams()).toEqual({});
expect(uri.getFragment()).toEqual(undefined);
expect(uri.toString()).toEqual('');
});
it('should understand relative uri', function() {
var uri = JX.$U('/home.php?key=value#fragment');
expect(uri.getProtocol()).toEqual(undefined);
expect(uri.getDomain()).toEqual(undefined);
expect(uri.getPath()).toEqual('/home.php');
expect(uri.getQueryParams()).toEqual({'key' : 'value'});
expect(uri.getFragment()).toEqual('fragment');
});
function charRange(from, to) {
res = '';
for (var i = from.charCodeAt(0); i <= to.charCodeAt(0); i++) {
res += String.fromCharCode(i);
}
return res;
}
it('should reject unsafe domains', function() {
var unsafe_chars =
'\x00;\\%\u2047\u2048\ufe56\ufe5f\uff03\uff0f\uff1f' +
charRange('\ufdd0', '\ufdef') + charRange('\ufff0', '\uffff');
for (var i = 0; i < unsafe_chars.length; i++) {
expect(function() {
JX.$U('http://foo' + unsafe_chars.charAt(i) + 'bar');
}).toThrow();
}
});
it('should allow safe domains', function() {
var safe_chars =
'-._' + charRange('a', 'z') + charRange('A', 'Z') + charRange('0', '9') +
'\u2046\u2049\ufdcf\ufdf0\uffef';
for (var i = 0; i < safe_chars.length; i++) {
var domain = 'foo' + safe_chars.charAt(i) + 'bar';
var uri = JX.$U('http://' + domain);
expect(uri.getDomain()).toEqual(domain);
}
});
it('should set slash as the default path', function() {
var uri = JX.$U('http://www.facebook.com');
expect(uri.getPath()).toEqual('/');
});
it('should set empty map as the default query data', function() {
var uri = JX.$U('http://www.facebook.com/');
expect(uri.getQueryParams()).toEqual({});
});
it('should set undefined as the default fragment', function() {
var uri = JX.$U('http://www.facebook.com/');
expect(uri.getFragment()).toEqual(undefined);
});
it('should understand uri with no path', function() {
var uri = JX.$U('http://www.facebook.com?key=value');
expect(uri.getPath()).toEqual('/');
expect(uri.getQueryParams()).toEqual({'key' : 'value'});
});
it('should understand multiple query keys', function() {
var uri = JX.$U('/?clown=town&herp=derp');
expect(uri.getQueryParams()).toEqual({
'clown' : 'town',
'herp' : 'derp'
});
});
it('does not set keys for nonexistant data', function() {
var uri = JX.$U('/?clown=town');
expect(uri.getQueryParams().herp).toEqual(undefined);
});
it('does not parse different types of query data', function() {
var uri = JX.$U('/?str=string&int=123&bool=true&badbool=false&raw');
expect(uri.getQueryParams()).toEqual({
'str' : 'string',
'int' : '123',
'bool' : 'true',
'badbool' : 'false',
'raw' : ''
});
});
it('should act as string', function() {
var string = 'http://www.facebook.com/home.php?key=value';
var uri = JX.$U(string);
expect(uri.toString()).toEqual(string);
expect('' + uri).toEqual(string);
});
it('can remove path', function() {
var uri = JX.$U('http://www.facebook.com/home.php?key=value');
uri.setPath(undefined);
expect(uri.getPath()).toEqual(undefined);
expect(uri.toString()).toEqual('http://www.facebook.com/?key=value');
});
it('can remove queryData by undefining it', function() {
var uri = JX.$U('http://www.facebook.com/home.php?key=value');
uri.setQueryParams(undefined);
expect(uri.getQueryParams()).toEqual(undefined);
expect(uri.toString()).toEqual('http://www.facebook.com/home.php');
});
it('can remove queryData by replacing it', function() {
var uri = JX.$U('http://www.facebook.com/home.php?key=value');
uri.setQueryParams({});
expect(uri.getQueryParams()).toEqual({});
expect(uri.toString()).toEqual('http://www.facebook.com/home.php');
});
it('can amend to removed queryData', function() {
var uri = JX.$U('http://www.facebook.com/home.php?key=value');
uri.setQueryParams({});
expect(uri.getQueryParams()).toEqual({});
uri.addQueryParams({'herp' : 'derp'});
expect(uri.getQueryParams()).toEqual({'herp' : 'derp'});
expect(uri.toString()).toEqual(
'http://www.facebook.com/home.php?herp=derp');
});
it('should properly decode entities', function() {
var uri = JX.$U('/?from=clown+town&to=cloud%20city&pass=cloud%2Bcountry');
expect(uri.getQueryParams()).toEqual({
'from' : 'clown town',
'to' : 'cloud city',
'pass' : 'cloud+country'
});
expect(uri.toString()).toEqual(
'/?from=clown%20town&to=cloud%20city&pass=cloud%2Bcountry');
});
it('can add query data', function() {
var uri = JX.$U('http://www.facebook.com/');
uri.addQueryParams({'key' : 'value'});
expect(uri.getQueryParams()).toEqual({'key' : 'value'});
expect(uri.toString()).toEqual('http://www.facebook.com/?key=value');
uri.setQueryParam('key', 'lock');
expect(uri.getQueryParams()).toEqual({'key' : 'lock'});
expect(uri.toString()).toEqual('http://www.facebook.com/?key=lock');
});
it('can add different types of query data', function() {
var uri = new JX.URI();
uri.setQueryParams({
'str' : 'string',
'int' : 123,
'bool' : true,
'badbool' : false,
'raw' : ''
});
expect(uri.toString()).toEqual(
'?str=string&int=123&bool=true&badbool=false&raw');
});
it('should properly encode entities in added query data', function() {
var uri = new JX.URI();
uri.addQueryParams({'key' : 'two words'});
expect(uri.getQueryParams()).toEqual({'key' : 'two words'});
expect(uri.toString()).toEqual('?key=two%20words');
});
it('can add multiple query data', function() {
var uri = JX.$U('http://www.facebook.com/');
uri.addQueryParams({
'clown' : 'town',
'herp' : 'derp'
});
expect(uri.getQueryParams()).toEqual({
'clown' : 'town',
'herp' : 'derp'
});
expect(uri.toString()).toEqual(
'http://www.facebook.com/?clown=town&herp=derp');
});
it('can append to existing query data', function() {
var uri = JX.$U('/?key=value');
uri.addQueryParams({'clown' : 'town'});
expect(uri.getQueryParams()).toEqual({
'key' : 'value',
'clown' : 'town'
});
expect(uri.toString()).toEqual('/?key=value&clown=town');
});
it('can merge with existing query data', function() {
var uri = JX.$U('/?key=value&clown=town');
uri.addQueryParams({
'clown' : 'ville',
'herp' : 'derp'
});
expect(uri.getQueryParams()).toEqual({
'key' : 'value',
'clown' : 'ville',
'herp' : 'derp'
});
expect(uri.toString()).toEqual('/?key=value&clown=ville&herp=derp');
});
it('can replace query data', function() {
var uri = JX.$U('/?key=value&clown=town');
uri.setQueryParams({'herp' : 'derp'});
expect(uri.getQueryParams()).toEqual({'herp' : 'derp'});
expect(uri.toString()).toEqual('/?herp=derp');
});
it('can remove query data', function() {
var uri = JX.$U('/?key=value&clown=town');
uri.addQueryParams({'key' : null});
expect(uri.getQueryParams()).toEqual({
'clown' : 'town',
'key' : null
});
expect(uri.toString()).toEqual('/?clown=town');
});
it('can remove multiple query data', function() {
var uri = JX.$U('/?key=value&clown=town&herp=derp');
uri.addQueryParams({'key' : null, 'herp' : undefined});
expect(uri.getQueryParams()).toEqual({
'clown' : 'town',
'key' : null,
'herp' : undefined
});
expect(uri.toString()).toEqual('/?clown=town');
});
it('can remove non existent query data', function() {
var uri = JX.$U('/?key=value');
uri.addQueryParams({'magic' : null});
expect(uri.getQueryParams()).toEqual({
'key' : 'value',
'magic' : null
});
expect(uri.toString()).toEqual('/?key=value');
});
it('can build uri from scratch', function() {
var uri = new JX.URI();
uri.setProtocol('http');
uri.setDomain('www.facebook.com');
uri.setPath('/home.php');
uri.setQueryParams({'key' : 'value'});
uri.setFragment('fragment');
expect(uri.toString()).toEqual(
'http://www.facebook.com/home.php?key=value#fragment');
});
it('no global state interference', function() {
var uri1 = JX.$U('/?key=value');
var uri2 = JX.$U();
expect(uri2.getQueryParams()).not.toEqual({'key' : 'value'});
});
it('should not loop indefinitely when parsing empty params', function() {
expect(JX.$U('/?&key=value').getQueryParams()).toEqual({'key' : 'value'});
expect(JX.$U('/?&&&key=value').getQueryParams()).toEqual({'key' : 'value'});
expect(JX.$U('/?&&').getQueryParams()).toEqual({});
});
it('should parse values with =', function() {
expect(JX.$U('/?x=1=1').getQueryParams()).toEqual({'x' : '1=1'});
});
});
-
diff --git a/webroot/rsrc/externals/javelin/lib/__tests__/behavior.js b/webroot/rsrc/externals/javelin/lib/__tests__/behavior.js
index 11c47d4e36..3d25bead50 100644
--- a/webroot/rsrc/externals/javelin/lib/__tests__/behavior.js
+++ b/webroot/rsrc/externals/javelin/lib/__tests__/behavior.js
@@ -1,96 +1,95 @@
/**
* @requires javelin-behavior
*/
describe('Javelin Behaviors', function() {
beforeEach(function() {
// Don't try this at home, kids.
JX.behavior._behaviors = {};
JX.behavior._initialized = {};
JX.behavior._statics = {};
});
it('JX.behavior should not work with clowny names', function() {
ensure__DEV__(true, function() {
expect(function() {
JX.behavior('toString', function() {});
}).toThrow();
});
});
it('JX.initBehavior should pass a config object', function() {
var called = false;
var config = 'no-value';
JX.behavior('my-behavior', function(cfg) {
called = true;
config = cfg;
});
JX.initBehaviors({});
expect(called).toBe(false);
expect(config).toEqual('no-value');
called = false;
config = null;
JX.initBehaviors({ 'my-behavior': [] });
expect(called).toBe(true);
expect(config).toBeNull();
called = false;
config = null;
JX.initBehaviors({ 'my-behavior': ['foo'] });
expect(called).toBe(true);
expect(config).toEqual('foo');
});
it('JX.initBehavior should init a behavior with no config once', function() {
var count = 0;
JX.behavior('foo', function() {
count++;
});
JX.initBehaviors({ 'foo': [] });
expect(count).toEqual(1);
JX.initBehaviors({ 'foo': [] });
expect(count).toEqual(1);
JX.initBehaviors({ 'foo': ['test'] });
expect(count).toEqual(2);
});
it('Behavior statics should persist across behavior invocations', function() {
var expect_value;
var asserted = 0;
JX.behavior('static-test', function(config, statics) {
statics.value = (statics.value || 0) + 1;
expect(statics.value).toBe(expect_value);
asserted++;
});
expect_value = 1;
JX.initBehaviors({'static-test' : [{ hog : 0 }]});
expect_value = 2;
JX.initBehaviors({'static-test' : [{ hog : 0 }]});
// Test that we actually invoked the behavior.
expect(asserted).toBe(2);
});
it('should throw for undefined behaviors', function() {
var called;
JX.behavior('can-haz', function() {
called = true;
});
expect(function() {
JX.initBehaviors({
'no-can-haz': [],
'can-haz': [],
'i-fail': []
});
}).toThrow();
expect(called).toBe(true);
});
});
-
diff --git a/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js b/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js
index 950d6876c7..aa5fb16d36 100644
--- a/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js
+++ b/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js
@@ -1,78 +1,77 @@
/**
* @requires javelin-install
* javelin-typeahead-source
* javelin-util
* @provides javelin-typeahead-composite-source
* @javelin
*/
/**
* @group control
*/
JX.install('TypeaheadCompositeSource', {
extend : 'TypeaheadSource',
construct : function(sources) {
JX.TypeaheadSource.call(this);
this.sources = sources;
for (var ii = 0; ii < this.sources.length; ++ii) {
var child = this.sources[ii];
child.listen('waiting', JX.bind(this, this.childWaiting));
child.listen('resultsready', JX.bind(this, this.childResultsReady));
child.listen('complete', JX.bind(this, this.childComplete));
}
},
members : {
sources : null,
results : null,
completeCount : 0,
didChange : function(value) {
this.results = [];
this.completeCount = 0;
for (var ii = 0; ii < this.sources.length; ++ii) {
this.sources[ii].didChange(value);
}
},
didStart : function() {
for (var ii = 0; ii < this.sources.length; ++ii) {
this.sources[ii].didStart();
}
},
childWaiting : function() {
if (!this.results || !this.results.length) {
this.invoke('waiting');
}
},
childResultsReady : function(nodes, value) {
this.results = this.mergeResults(this.results || [], nodes);
this.invoke('resultsready', this.results, value);
},
childComplete : function() {
this.completeCount++;
if (this.completeCount == this.sources.length) {
this.invoke('complete');
}
},
/**
* Overrideable strategy for combining results.
* By default, appends results as they come in
* so that results don't jump around.
*/
mergeResults : function(oldResults, newResults) {
for (var ii = 0; ii < newResults.length; ++ii) {
oldResults.push(newResults[ii]);
}
return oldResults;
}
}
});
-
diff --git a/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js b/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js
index 6fc10b3c57..626d43378a 100644
--- a/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js
+++ b/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js
@@ -1,103 +1,101 @@
/**
* @requires javelin-install
* javelin-util
* javelin-request
* javelin-typeahead-source
* @provides javelin-typeahead-ondemand-source
* @javelin
*/
/**
* @group control
*/
JX.install('TypeaheadOnDemandSource', {
extend : 'TypeaheadSource',
construct : function(uri) {
JX.TypeaheadSource.call(this);
this.uri = uri;
this.haveData = {
'' : true
};
},
properties : {
/**
* Configures how many milliseconds we wait after the user stops typing to
* send a request to the server. Setting a value of 250 means "wait 250
* milliseconds after the user stops typing to request typeahead data".
* Higher values reduce server load but make the typeahead less responsive.
*/
queryDelay : 125,
/**
* Auxiliary data to pass along when sending the query for server results.
*/
auxiliaryData : {}
},
members : {
uri : null,
lastChange : null,
haveData : null,
didChange : function(raw_value) {
this.lastChange = JX.now();
var value = this.normalize(raw_value);
if (this.haveData[value]) {
this.matchResults(raw_value);
} else {
// If we have data for any prefix of the query, send those results
// back immediately. This allows "alinc" -> "alinco" to show partial
// results without the UI flickering. We'll still show the loading
// state, and then can show better results once we get everything
// back.
for (var ii = value.length - 1; ii > 0; ii--) {
var substr = value.substring(0, ii);
if (this.haveData[substr]) {
this.matchResults(raw_value, true);
break;
}
}
this.waitForResults();
setTimeout(
JX.bind(this, this.sendRequest, this.lastChange, value, raw_value),
this.getQueryDelay()
);
}
},
sendRequest : function(when, value, raw_value) {
if (when != this.lastChange) {
return;
}
var r = new JX.Request(
this.uri,
JX.bind(this, this.ondata, this.lastChange, raw_value));
r.setMethod('GET');
r.setData(JX.copy(this.getAuxiliaryData(), {q : value, raw: raw_value}));
r.send();
},
ondata : function(when, raw_value, results) {
if (results) {
for (var ii = 0; ii < results.length; ii++) {
this.addResult(results[ii]);
}
}
var value = this.normalize(raw_value);
this.haveData[value] = true;
if (when != this.lastChange) {
return;
}
this.matchResults(raw_value);
}
}
});
-
-
diff --git a/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js b/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js
index ace130610a..d0bd4672d9 100644
--- a/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js
+++ b/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js
@@ -1,61 +1,58 @@
/**
* @requires javelin-install
* javelin-util
* javelin-request
* javelin-typeahead-source
* @provides javelin-typeahead-preloaded-source
* @javelin
*/
/**
* Simple datasource that loads all possible results from a single call to a
* URI. This is appropriate if the total data size is small (up to perhaps a
* few thousand items). If you have more items so you can't ship them down to
* the client in one repsonse, use @{JX.TypeaheadOnDemandSource}.
*
* @group control
*/
JX.install('TypeaheadPreloadedSource', {
extend : 'TypeaheadSource',
construct : function(uri) {
JX.TypeaheadSource.call(this);
this.uri = uri;
},
members : {
ready : false,
uri : null,
lastValue : null,
didChange : function(value) {
if (this.ready) {
this.matchResults(value);
} else {
this.lastValue = value;
this.waitForResults();
}
},
didStart : function() {
var r = new JX.Request(this.uri, JX.bind(this, this.ondata));
r.setMethod('GET');
r.send();
},
ondata : function(results) {
for (var ii = 0; ii < results.length; ++ii) {
this.addResult(results[ii]);
}
if (this.lastValue !== null) {
this.matchResults(this.lastValue);
}
this.ready = true;
}
}
});
-
-
-
diff --git a/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js b/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js
index ac7045671d..39705f4dcd 100644
--- a/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js
+++ b/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js
@@ -1,40 +1,37 @@
/**
* @requires javelin-install
* javelin-typeahead-source
* @provides javelin-typeahead-static-source
* @javelin
*/
/**
* Typeahead source that uses static data passed to the constructor. For larger
* datasets, use @{class:JX.TypeaheadPreloadedSource} or
* @{class:JX.TypeaheadOnDemandSource} to improve performance.
*
* @group control
*/
JX.install('TypeaheadStaticSource', {
extend : 'TypeaheadSource',
construct : function(data) {
JX.TypeaheadSource.call(this);
this._data = data;
},
members : {
_data : null,
didChange : function(value) {
this.matchResults(value);
},
didStart : function() {
for (var ii = 0; ii < this._data.length; ii++) {
this.addResult(this._data[ii]);
}
}
}
});
-
-
-
diff --git a/webroot/rsrc/image/icon/fatcow/README b/webroot/rsrc/image/icon/fatcow/README
index c294834741..327a315cd4 100644
--- a/webroot/rsrc/image/icon/fatcow/README
+++ b/webroot/rsrc/image/icon/fatcow/README
@@ -1,17 +1,17 @@
These icons come from the FatCow icon set:
http://www.fatcow.com/free-icons
They are available under the Creative Commons Attribution 3.0 License:
http://creativecommons.org/licenses/by/3.0/us/
Some icons have been adapted from the FatCow set for use in Phabricator:
key_question.png (from key_*.png)
source/web.png (from world.png)
source/email.png (from email.png)
source/conduit.png (from satellite_dish.png)
source/mobile.png (from phone.png)
source/tablet.png (from tablet.png)
- source/fax.png (from fax.png)
\ No newline at end of file
+ source/fax.png (from fax.png)
diff --git a/webroot/rsrc/js/application/config/behavior-reorder-fields.js b/webroot/rsrc/js/application/config/behavior-reorder-fields.js
index deb9da61b7..eb2f5bc36d 100644
--- a/webroot/rsrc/js/application/config/behavior-reorder-fields.js
+++ b/webroot/rsrc/js/application/config/behavior-reorder-fields.js
@@ -1,58 +1,57 @@
/**
* @provides javelin-behavior-config-reorder-fields
* @requires javelin-behavior
* javelin-stratcom
* javelin-dom
* javelin-json
* phabricator-draggable-list
*/
JX.behavior('config-reorder-fields', function(config) {
var fields = config.fields;
var root = JX.$(config.listID);
var list = new JX.DraggableList('field-spec', root)
.setFindItemsHandler(function() {
return JX.DOM.scry(root, 'li', 'field-spec');
});
list.listen('didDrop', function(node, after) {
write_state_to_form();
});
JX.DOM.listen(root, 'click', 'field-spec-toggle', function(e) {
e.kill();
var key = e.getNodeData('field-spec').fieldKey;
fields[key].disabled = !fields[key].disabled;
JX.DOM.replace(
e.getNode('field-spec'),
JX.$H(
fields[key].disabled ?
fields[key].disabledMarkup :
fields[key].enabledMarkup));
write_state_to_form();
});
var write_state_to_form = function() {
var nodes = list.findItems();
var order = [];
var key;
for (var ii = 0; ii < nodes.length; ii++) {
key = JX.Stratcom.getData(nodes[ii]).fieldKey;
if (key) {
order.push({
key: key,
disabled: fields[key].disabled
});
}
}
JX.$(config.inputID).value = JX.JSON.stringify(order);
};
});
-
diff --git a/webroot/rsrc/js/application/countdown/timer.js b/webroot/rsrc/js/application/countdown/timer.js
index ce8f0a6bf4..16a5366cf6 100644
--- a/webroot/rsrc/js/application/countdown/timer.js
+++ b/webroot/rsrc/js/application/countdown/timer.js
@@ -1,53 +1,52 @@
/**
* @provides javelin-behavior-countdown-timer
* @requires javelin-behavior
* javelin-dom
*/
JX.behavior('countdown-timer', function(config) {
var container = JX.$(config.container);
calculateTimeLeft();
function setComponent(which, content) {
var component = JX.DOM.find(container, '*', 'phabricator-timer-' + which);
JX.DOM.setContent(component, content);
}
function calculateTimeLeft() {
var days = 0;
var hours = 0;
var minutes = 0;
var seconds = 0;
var partial = 0;
var current_timestamp = +new Date();
var delta = (config.timestamp * 1000) - current_timestamp;
if (delta > 0) {
days = Math.floor(delta / 86400000);
delta -= days * 86400000;
hours = Math.floor(delta / 3600000);
delta -= hours * 3600000;
minutes = Math.floor(delta / 60000);
delta -= minutes * 60000;
seconds = Math.floor(delta / 1000);
delta -= seconds * 1000;
partial = Math.floor(delta / 100) || '0';
setTimeout(calculateTimeLeft, 100);
}
setComponent('days', days);
setComponent('hours', hours);
setComponent('minutes', minutes);
setComponent('seconds', [seconds, JX.$N('small', {}, ['.', partial])]);
}
});
-
diff --git a/webroot/rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js b/webroot/rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js
index 216c1d4492..3d712444d0 100644
--- a/webroot/rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js
+++ b/webroot/rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js
@@ -1,48 +1,47 @@
/**
* @provides javelin-behavior-differential-add-reviewers-and-ccs
* @requires javelin-behavior
* javelin-dom
* phabricator-prefab
*/
JX.behavior('differential-add-reviewers-and-ccs', function(config) {
var dynamic = {};
for (var k in config.dynamic) {
var props = config.dynamic[k];
props.id = k;
var tokenizer = JX.Prefab.buildTokenizer(props).tokenizer;
tokenizer.start();
dynamic[k] = {
row : JX.$(props.row),
tokenizer : tokenizer,
actions : props.actions,
labels: props.labels
};
}
JX.DOM.listen(
JX.$(config.select),
'change',
null,
function(e) {
var v = JX.$(config.select).value;
for (var k in dynamic) {
if (dynamic[k].actions[v]) {
JX.DOM.show(dynamic[k].row);
if (dynamic[k].labels) {
var label_node = JX.DOM.find(dynamic[k].row, 'label');
if (label_node) {
JX.DOM.setContent(label_node, dynamic[k].labels[v]);
}
}
dynamic[k].tokenizer.refresh();
} else {
JX.DOM.hide(dynamic[k].row);
}
}
});
});
-
diff --git a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js
index 1d83eab062..6210ee6b80 100644
--- a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js
+++ b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js
@@ -1,270 +1,269 @@
/**
* @provides javelin-behavior-differential-edit-inline-comments
* @requires javelin-behavior
* javelin-stratcom
* javelin-dom
* javelin-util
* javelin-vector
* differential-inline-comment-editor
*/
JX.behavior('differential-edit-inline-comments', function(config) {
var selecting = false;
var reticle = JX.$N('div', {className: 'differential-reticle'});
JX.DOM.hide(reticle);
document.body.appendChild(reticle);
var origin = null;
var target = null;
var root = null;
var changeset = null;
var editor = null;
function updateReticle() {
var top = origin;
var bot = target;
if (JX.$V(top).y > JX.$V(bot).y) {
var tmp = top;
top = bot;
bot = tmp;
}
var code = target.nextSibling;
var pos = JX.$V(top).add(1 + JX.Vector.getDim(target).x, 0);
var dim = JX.Vector.getDim(code).add(-4, 0);
if (isOnRight(target)) {
dim.x += JX.Vector.getDim(code.nextSibling).x;
}
dim.y = (JX.$V(bot).y - pos.y) + JX.Vector.getDim(bot).y;
pos.setPos(reticle);
dim.setDim(reticle);
JX.DOM.show(reticle);
}
function hideReticle() {
JX.DOM.hide(reticle);
}
JX.DifferentialInlineCommentEditor.listen('done', function() {
selecting = false;
editor = false;
hideReticle();
set_link_state(false);
});
function isOnRight(node) {
return node.parentNode.firstChild != node;
}
function isNewFile(node) {
var data = JX.Stratcom.getData(root);
return isOnRight(node) || (data.left != data.right);
}
function getRowNumber(th_node) {
try {
return parseInt(th_node.id.match(/^C\d+[ON]L(\d+)$/)[1], 10);
} catch (x) {
return undefined;
}
}
var set_link_state = function(active) {
JX.DOM.alterClass(JX.$(config.stage), 'inline-editor-active', active);
};
JX.Stratcom.listen(
'mousedown',
['differential-changeset', 'tag:th'],
function(e) {
if (editor ||
selecting ||
e.isRightButton() ||
getRowNumber(e.getTarget()) === undefined) {
return;
}
selecting = true;
root = e.getNode('differential-changeset');
origin = target = e.getTarget();
var data = e.getNodeData('differential-changeset');
if (isOnRight(target)) {
changeset = data.right;
} else {
changeset = data.left;
}
updateReticle();
e.kill();
});
JX.Stratcom.listen(
'mouseover',
['differential-changeset', 'tag:th'],
function(e) {
if (!selecting ||
editor ||
(getRowNumber(e.getTarget()) === undefined) ||
(isOnRight(e.getTarget()) != isOnRight(origin)) ||
(e.getNode('differential-changeset') !== root)) {
return;
}
target = e.getTarget();
updateReticle();
});
JX.Stratcom.listen(
'mouseup',
null,
function(e) {
if (editor || !selecting) {
return;
}
var o = getRowNumber(origin);
var t = getRowNumber(target);
var insert;
var len;
if (t < o) {
len = (o - t);
o = t;
insert = origin.parentNode;
} else {
len = (t - o);
insert = target.parentNode;
}
editor = new JX.DifferentialInlineCommentEditor(config.uri)
.setTemplates(config.undo_templates)
.setOperation('new')
.setChangeset(changeset)
.setLineNumber(o)
.setLength(len)
.setIsNew(isNewFile(target) ? 1 : 0)
.setOnRight(isOnRight(target) ? 1 : 0)
.setRow(insert.nextSibling)
.setTable(insert.parentNode)
.start();
set_link_state(true);
e.kill();
});
JX.Stratcom.listen(
['mouseover', 'mouseout'],
'differential-inline-comment',
function(e) {
if (e.getType() == 'mouseout') {
hideReticle();
} else {
root = e.getNode('differential-changeset');
if (root) {
var data = e.getNodeData('differential-inline-comment');
var change = e.getNodeData('differential-changeset');
var id_part = data.on_right ? change.right : change.left;
// NOTE: We can't just look for 'tag:td' because the event might be
// inside a table which is inside an inline comment.
var comment = e.getNode('differential-inline-comment');
var td = JX.DOM.findAbove(comment, 'td');
var th = td.previousSibling;
var new_part = isNewFile(th) ? 'N' : 'O';
var prefix = 'C' + id_part + new_part + 'L';
origin = JX.$(prefix + data.number);
target = JX.$(prefix + (parseInt(data.number, 10) +
parseInt(data.length, 10)));
updateReticle();
}
}
});
var action_handler = function(op, e) {
e.kill();
if (editor) {
return;
}
var node = e.getNode('differential-inline-comment');
handle_inline_action(node, op);
};
var handle_inline_action = function(node, op) {
var data = JX.Stratcom.getData(node);
var row = node.parentNode.parentNode;
var other_rows = [];
if (JX.Stratcom.hasSigil(node, 'differential-inline-comment-preview')) {
// The DOM structure around the comment is different if it's part of the
// preview, so make sure not to pass the wrong container.
row = node;
if (op === 'delete') {
// Furthermore, deleting a comment in the preview does not automatically
// delete other occurrences of the same comment, so do that manually.
var nodes = JX.DOM.scry(
document.body,
'div',
'differential-inline-comment');
for (var i = 0; i < nodes.length; ++i) {
if (JX.Stratcom.getData(nodes[i]).id === data.id) {
other_rows.push(nodes[i]);
}
}
}
}
var original = data.original;
if (op == 'reply') {
// If the user hit "reply", the original text is empty (a new reply), not
// the text of the comment they're replying to.
original = '';
}
editor = new JX.DifferentialInlineCommentEditor(config.uri)
.setTemplates(config.undo_templates)
.setOperation(op)
.setID(data.id)
.setLineNumber(data.number)
.setLength(data.length)
.setOnRight(data.on_right)
.setOriginalText(original)
.setRow(row)
.setOtherRows(other_rows)
.setTable(row.parentNode)
.start();
set_link_state(true);
};
for (var op in {'edit' : 1, 'delete' : 1, 'reply' : 1}) {
JX.Stratcom.listen(
'click',
['differential-inline-comment', 'differential-inline-' + op],
JX.bind(null, action_handler, op));
}
JX.Stratcom.listen(
'differential-inline-action',
null,
function(e) {
var data = e.getData();
handle_inline_action(data.node, data.op);
});
});
-
diff --git a/webroot/rsrc/js/application/differential/behavior-keyboard-nav.js b/webroot/rsrc/js/application/differential/behavior-keyboard-nav.js
index 98d0b73a04..c7ed6b268e 100644
--- a/webroot/rsrc/js/application/differential/behavior-keyboard-nav.js
+++ b/webroot/rsrc/js/application/differential/behavior-keyboard-nav.js
@@ -1,277 +1,276 @@
/**
* @provides javelin-behavior-differential-keyboard-navigation
* @requires javelin-behavior
* javelin-dom
* javelin-stratcom
* phabricator-keyboard-shortcut
*/
JX.behavior('differential-keyboard-navigation', function(config) {
var cursor = -1;
var changesets;
var selection_begin = null;
var selection_end = null;
var refreshFocus = function() {};
function init() {
if (changesets) {
return;
}
changesets = JX.DOM.scry(document.body, 'div', 'differential-changeset');
}
function getBlocks(cursor) {
// TODO: This might not be terribly fast; we can't currently memoize it
// because it can change as ajax requests come in (e.g., content loads).
var rows = JX.DOM.scry(changesets[cursor], 'tr');
var blocks = [[changesets[cursor], changesets[cursor]]];
var start = null;
var type;
var ii;
// Don't show code blocks inside a collapsed file.
var diff = JX.DOM.scry(changesets[cursor], 'table', 'differential-diff');
if (diff.length == 1 && JX.Stratcom.getData(diff[0]).hidden) {
return blocks;
}
function push() {
if (start) {
blocks.push([start, rows[ii - 1]]);
}
start = null;
}
for (ii = 0; ii < rows.length; ii++) {
type = getRowType(rows[ii]);
if (type == 'comment') {
// If we see these types of rows, make a block for each one.
push();
}
if (!type) {
push();
} else if (type && !start) {
start = rows[ii];
}
}
push();
return blocks;
}
function getRowType(row) {
// NOTE: Being somewhat over-general here to allow other types of objects
// to be easily focused in the future (inline comments, 'show more..').
if (row.className.indexOf('inline') !== -1) {
return 'comment';
}
if (row.className.indexOf('differential-changeset') !== -1) {
return 'file';
}
var cells = JX.DOM.scry(row, 'td');
for (var ii = 0; ii < cells.length; ii++) {
// NOTE: The semantic use of classnames here is for performance; don't
// emulate this elsewhere since it's super terrible.
if (cells[ii].className.indexOf('old') !== -1 ||
cells[ii].className.indexOf('new') !== -1) {
return 'change';
}
}
return null;
}
function jump(manager, delta, jump_to_type) {
init();
if (cursor < 0) {
if (delta < 0) {
// If the user goes "back" without a selection, just reject the action.
return;
} else {
cursor = 0;
}
}
while (true) {
var blocks = getBlocks(cursor);
var focus;
if (delta < 0) {
focus = blocks.length;
} else {
focus = -1;
}
for (var ii = 0; ii < blocks.length; ii++) {
if (blocks[ii][0] == selection_begin) {
focus = ii;
break;
}
}
while (true) {
focus += delta;
if (blocks[focus]) {
var row_type = getRowType(blocks[focus][0]);
if (jump_to_type && row_type != jump_to_type) {
continue;
}
selection_begin = blocks[focus][0];
selection_end = blocks[focus][1];
manager.scrollTo(selection_begin);
refreshFocus = function() {
manager.focusOn(selection_begin, selection_end);
};
refreshFocus();
return;
} else {
var adjusted = (cursor + delta);
if (adjusted < 0 || adjusted >= changesets.length) {
// Stop cursor movement when the user reaches either end.
return;
}
cursor = adjusted;
// Break the inner loop and go to the next file.
break;
}
}
}
}
// When inline comments are updated, wipe out our cache of blocks since
// comments may have been added or deleted.
JX.Stratcom.listen(
null,
'differential-inline-comment-update',
function() {
changesets = null;
});
// Same thing when a file is hidden or shown; don't want to highlight
// invisible code.
JX.Stratcom.listen(
'differential-toggle-file-toggled',
null,
function() {
changesets = null;
init();
refreshFocus();
});
var haunt_mode = 0;
function haunt() {
haunt_mode = (haunt_mode + 1) % 3;
var el = JX.$(config.haunt);
for (var ii = 1; ii <= 2; ii++) {
JX.DOM.alterClass(el, 'differential-haunt-mode-'+ii, (haunt_mode == ii));
}
}
new JX.KeyboardShortcut('j', 'Jump to next change.')
.setHandler(function(manager) {
jump(manager, 1);
})
.register();
new JX.KeyboardShortcut('k', 'Jump to previous change.')
.setHandler(function(manager) {
jump(manager, -1);
})
.register();
new JX.KeyboardShortcut('J', 'Jump to next file.')
.setHandler(function(manager) {
jump(manager, 1, 'file');
})
.register();
new JX.KeyboardShortcut('K', 'Jump to previous file.')
.setHandler(function(manager) {
jump(manager, -1, 'file');
})
.register();
new JX.KeyboardShortcut('n', 'Jump to next inline comment.')
.setHandler(function(manager) {
jump(manager, 1, 'comment');
})
.register();
new JX.KeyboardShortcut('p', 'Jump to previous inline comment.')
.setHandler(function(manager) {
jump(manager, -1, 'comment');
})
.register();
new JX.KeyboardShortcut('t', 'Jump to the table of contents.')
.setHandler(function(manager) {
var toc = JX.$('toc');
manager.scrollTo(toc);
})
.register();
new JX.KeyboardShortcut(
'h',
'Collapse or expand the file display (after jump).')
.setHandler(function(manager) {
if (!changesets || !changesets[cursor]) {
return;
}
JX.Stratcom.invoke('differential-toggle-file', null, {
diff: JX.DOM.scry(changesets[cursor], 'table', 'differential-diff')
});
})
.register();
function inline_op(node, op) {
if (!JX.DOM.scry(node, 'a', 'differential-inline-' + op)) {
// No link for this operation, e.g. editing a comment you can't edit.
return;
}
var data = {
node: JX.DOM.find(node, 'div', 'differential-inline-comment'),
op: op
};
JX.Stratcom.invoke('differential-inline-action', null, data);
}
new JX.KeyboardShortcut('r', 'Reply to selected inline comment.')
.setHandler(function(manager) {
inline_op(selection_begin, 'reply');
})
.register();
new JX.KeyboardShortcut('e', 'Edit selected inline comment.')
.setHandler(function(manager) {
inline_op(selection_begin, 'edit');
})
.register();
if (config.haunt) {
new JX.KeyboardShortcut('z', 'Cycle comment panel haunting modes.')
.setHandler(haunt)
.register();
}
});
-
diff --git a/webroot/rsrc/js/application/diffusion/behavior-commit-branches.js b/webroot/rsrc/js/application/diffusion/behavior-commit-branches.js
index d72b5206f7..5911a84a09 100644
--- a/webroot/rsrc/js/application/diffusion/behavior-commit-branches.js
+++ b/webroot/rsrc/js/application/diffusion/behavior-commit-branches.js
@@ -1,19 +1,18 @@
/**
* @provides javelin-behavior-diffusion-commit-branches
* @requires javelin-behavior
* javelin-dom
* javelin-util
* javelin-request
*/
JX.behavior('diffusion-commit-branches', function(config) {
for (var uri in config) {
JX.DOM.setContent(JX.$(config[uri]), 'Loading...');
new JX.Request(uri, JX.bind(config[uri], function(r) {
JX.DOM.setContent(JX.$(this), JX.$H(r));
})).send();
}
});
-
diff --git a/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js b/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js
index e9ab4b12bb..75844369b2 100644
--- a/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js
+++ b/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js
@@ -1,143 +1,142 @@
/**
* @provides javelin-behavior-diffusion-commit-graph
* @requires javelin-behavior
* javelin-dom
* javelin-stratcom
*/
JX.behavior('diffusion-commit-graph', function(config) {
var nodes = JX.DOM.scry(document.body, 'div', 'commit-graph');
var cxt;
// Pick the color for column 'c'.
function color(c) {
var colors = [
'#cc0000',
'#cc0099',
'#6600cc',
'#0033cc',
'#00cccc',
'#00cc33',
'#66cc00',
'#cc9900'
];
return colors[c % colors.length];
}
// Stroke a line (for lines between commits).
function lstroke(c) {
cxt.lineWidth = 3;
cxt.strokeStyle = '#ffffff';
cxt.stroke();
cxt.lineWidth = 1;
cxt.strokeStyle = color(c);
cxt.stroke();
}
// Stroke with fill (for commit circles).
function fstroke(c) {
cxt.fillStyle = color(c);
cxt.strokeStyle = '#ffffff';
cxt.fill();
cxt.stroke();
}
for (var ii = 0; ii < nodes.length; ii++) {
var data = JX.Stratcom.getData(nodes[ii]);
var cell = 12; // Width of each thread.
var xpos = function(col) {
return (col * cell) + (cell / 2);
};
var h = 26;
var w = cell * config.count;
var canvas = JX.$N('canvas', {width: w, height: h});
cxt = canvas.getContext('2d');
cxt.lineWidth = 3;
// This gives us sharper lines, since lines drawn on an integer (like 5)
// are drawn from 4.5 to 5.5.
cxt.translate(0.5, 0.5);
cxt.strokeStyle = '#ffffff';
cxt.fillStyle = '#ffffff';
// First, figure out which column this commit appears in. It is marked by
// "o" (if it has a commit after it) or "^" (if no other commit has it as
// a parent). We use this to figure out where to draw the join/split lines.
var origin = null;
var jj;
var x;
var c;
for (jj = 0; jj < data.line.length; jj++) {
c = data.line.charAt(jj);
switch (c) {
case 'o':
case '^':
origin = xpos(jj);
break;
}
}
// Draw all the join lines. These start at some column at the top of the
// canvas and join the commit's column. They indicate branching.
for (jj = 0; jj < data.join.length; jj++) {
var join = data.join[jj];
x = xpos(join);
cxt.beginPath();
cxt.moveTo(x, 0);
cxt.bezierCurveTo(x, h/4, origin, h/4, origin, h/2);
lstroke(join);
}
// Draw all the split lines. These start at the commit and end at some
// column on the bottom of the canvas. They indicate merging.
for (jj = 0; jj < data.split.length; jj++) {
var split = data.split[jj];
x = xpos(split);
cxt.beginPath();
cxt.moveTo(origin, h/2);
cxt.bezierCurveTo(origin, 3*h/4, x, 3*h/4, x, h);
lstroke(split);
}
// Draw the vertical lines (a branch with no activity at this commit) and
// the commit circles.
for (jj = 0; jj < data.line.length; jj++) {
c = data.line.charAt(jj);
switch (c) {
case 'o':
case '^':
case '|':
if (c == 'o' || c == '^') {
origin = xpos(jj);
}
cxt.beginPath();
cxt.moveTo(xpos(jj), (c == '^' ? h/2 : 0));
cxt.lineTo(xpos(jj), h);
lstroke(jj);
if (c == 'o' || c == '^') {
cxt.beginPath();
cxt.arc(xpos(jj), h/2, 3, 0, 2 * Math.PI, true);
fstroke(jj);
}
break;
}
}
JX.DOM.setContent(nodes[ii], canvas);
}
});
-
diff --git a/webroot/rsrc/js/application/diffusion/behavior-pull-lastmodified.js b/webroot/rsrc/js/application/diffusion/behavior-pull-lastmodified.js
index 01e8238718..fbf924df5b 100644
--- a/webroot/rsrc/js/application/diffusion/behavior-pull-lastmodified.js
+++ b/webroot/rsrc/js/application/diffusion/behavior-pull-lastmodified.js
@@ -1,22 +1,21 @@
/**
* @provides javelin-behavior-diffusion-pull-lastmodified
* @requires javelin-behavior
* javelin-dom
* javelin-util
* javelin-request
*/
JX.behavior('diffusion-pull-lastmodified', function(config) {
for (var uri in config) {
new JX.Request(uri, JX.bind(config[uri], function(r) {
for (var k in r) {
if (this[k]) {
JX.DOM.setContent(JX.$(this[k]), JX.$H(r[k]));
}
}
})).send();
}
});
-
diff --git a/webroot/rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js b/webroot/rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js
index 17a3aa7aa5..c7b850f44f 100644
--- a/webroot/rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js
+++ b/webroot/rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js
@@ -1,65 +1,64 @@
/**
* @provides javelin-behavior-doorkeeper-tag
* @requires javelin-behavior
* javelin-dom
* javelin-json
* javelin-workflow
* javelin-magical-init
*/
JX.behavior('doorkeeper-tag', function(config, statics) {
statics.tags = (statics.tags || []).concat(config.tags);
statics.cache = statics.cache || {};
// NOTE: We keep a cache in the browser of external objects that we've already
// looked up. This is mostly to keep previews from being flickery messes.
var load = function() {
var tags = statics.tags;
statics.tags = [];
if (!tags.length) {
return;
}
var have = [];
var need = [];
var keys = {};
var draw = function(tags) {
for (var ii = 0; ii < tags.length; ii++) {
try {
JX.DOM.replace(JX.$(tags[ii].id), JX.$H(tags[ii].markup));
} catch (ignored) {
// The tag may have been wiped out of the body by the time the
// response returns, for whatever reason. That's fine, just don't
// bother drawing it.
}
statics.cache[keys[tags[ii].id]] = tags[ii].markup;
}
};
for (var ii = 0; ii < tags.length; ii++) {
var tag_key = tags[ii].ref.join('@');
if (tag_key in statics.cache) {
have.push({id: tags[ii].id, markup: statics.cache[tag_key]});
} else {
need.push(tags[ii]);
keys[tags[ii].id] = tag_key;
}
}
if (have.length) {
draw(have);
}
if (need.length) {
new JX.Workflow('/doorkeeper/tags/', {tags: JX.JSON.stringify(need)})
.setHandler(function(r) { draw(r.tags); })
.start();
}
};
JX.onload(load);
});
-
diff --git a/webroot/rsrc/js/application/files/behavior-icon-composer.js b/webroot/rsrc/js/application/files/behavior-icon-composer.js
index 2967f49e2f..8f574c0dc4 100644
--- a/webroot/rsrc/js/application/files/behavior-icon-composer.js
+++ b/webroot/rsrc/js/application/files/behavior-icon-composer.js
@@ -1,76 +1,75 @@
/**
* @provides javelin-behavior-icon-composer
* @requires javelin-behavior
* javelin-dom
* javelin-stratcom
*/
JX.behavior('icon-composer', function(config) {
var nodes = {
root: JX.$(config.dialogID),
colorInput: JX.$(config.colorInputID),
iconInput: JX.$(config.iconInputID),
preview: JX.$(config.previewID)
};
var selected = {
color: config.defaultColor,
icon: config.defaultIcon
};
var redraw = function() {
var ii;
var colors = JX.DOM.scry(nodes.root, 'button', 'compose-select-color');
for (ii = 0; ii < colors.length; ii++) {
JX.DOM.alterClass(
colors[ii],
'profile-image-button-selected',
(JX.Stratcom.getData(colors[ii]).color == selected.color));
}
var icons = JX.DOM.scry(nodes.root, 'button', 'compose-select-icon');
for (ii = 0; ii < icons.length; ii++) {
JX.DOM.alterClass(
icons[ii],
'profile-image-button-selected',
(JX.Stratcom.getData(icons[ii]).icon == selected.icon));
}
nodes.colorInput.value = selected.color;
nodes.iconInput.value = selected.icon;
var classes = ['phui-icon-view', 'sprite-projects'];
classes.push('compose-background-' + selected.color);
classes.push('projects-' + selected.icon);
nodes.preview.className = classes.join(' ');
};
JX.DOM.listen(
nodes.root,
'click',
'compose-select-color',
function (e) {
e.kill();
selected.color = e.getNodeData('compose-select-color').color;
redraw();
});
JX.DOM.listen(
nodes.root,
'click',
'compose-select-icon',
function (e) {
e.kill();
selected.icon = e.getNodeData('compose-select-icon').icon;
redraw();
});
redraw();
});
-
diff --git a/webroot/rsrc/js/application/files/behavior-launch-icon-composer.js b/webroot/rsrc/js/application/files/behavior-launch-icon-composer.js
index 327a7efc54..65a1903690 100644
--- a/webroot/rsrc/js/application/files/behavior-launch-icon-composer.js
+++ b/webroot/rsrc/js/application/files/behavior-launch-icon-composer.js
@@ -1,25 +1,24 @@
/**
* @provides javelin-behavior-launch-icon-composer
* @requires javelin-behavior
* javelin-dom
* javelin-workflow
*/
JX.behavior('launch-icon-composer', function(config) {
JX.DOM.listen(
JX.$(config.launchID),
'click',
null,
function(e) {
e.kill();
new JX.Workflow('/file/compose/')
.setHandler(function(r) {
JX.$(config.inputID).value = r.phid;
JX.DOM.findAbove(e.getTarget(), 'form').submit();
})
.start();
});
});
-
diff --git a/webroot/rsrc/js/application/harbormaster/behavior-reorder-steps.js b/webroot/rsrc/js/application/harbormaster/behavior-reorder-steps.js
index f47bea8ecd..3bbae89489 100644
--- a/webroot/rsrc/js/application/harbormaster/behavior-reorder-steps.js
+++ b/webroot/rsrc/js/application/harbormaster/behavior-reorder-steps.js
@@ -1,42 +1,41 @@
/**
* @provides javelin-behavior-harbormaster-reorder-steps
* @requires javelin-behavior
* javelin-stratcom
* javelin-workflow
* javelin-dom
* phabricator-draggable-list
*/
JX.behavior('harbormaster-reorder-steps', function(config) {
var root = JX.$(config.listID);
var list = new JX.DraggableList('build-step', root)
.setFindItemsHandler(function() {
return JX.DOM.scry(root, 'li', 'build-step');
});
list.listen('didDrop', function(node, after) {
var nodes = list.findItems();
var order = [];
var key;
for (var ii = 0; ii < nodes.length; ii++) {
key = JX.Stratcom.getData(nodes[ii]).stepID;
if (key) {
order.push(key);
}
}
list.lock();
JX.DOM.alterClass(node, 'drag-sending', true);
new JX.Workflow(config.orderURI, {order: order.join()})
.setHandler(function(e) {
JX.DOM.alterClass(node, 'drag-sending', false);
list.unlock();
})
.start();
});
});
-
diff --git a/webroot/rsrc/js/application/maniphest/behavior-line-chart.js b/webroot/rsrc/js/application/maniphest/behavior-line-chart.js
index cfa32e719e..0466e16ce8 100644
--- a/webroot/rsrc/js/application/maniphest/behavior-line-chart.js
+++ b/webroot/rsrc/js/application/maniphest/behavior-line-chart.js
@@ -1,89 +1,88 @@
/**
* @provides javelin-behavior-line-chart
* @requires javelin-behavior
* javelin-dom
* javelin-vector
*/
JX.behavior('line-chart', function(config) {
var h = JX.$(config.hardpoint);
var p = JX.$V(h);
var d = JX.Vector.getDim(h);
var mx = 60;
var my = 30;
var r = Raphael(p.x, p.y, d.x, d.y);
var l = r.linechart(
mx, my,
d.x - (2 * mx), d.y - (2 * my),
config.x,
config.y,
{
nostroke: false,
axis: "0 0 1 1",
shade: true,
gutter: 1,
colors: config.colors || ['#d06']
});
function format(value, type) {
switch (type) {
case 'epoch':
return new Date(parseInt(value, 10) * 1000).toLocaleDateString();
case 'int':
return parseInt(value, 10);
default:
return value;
}
}
// Format the X axis.
var n = 2;
var ii = 0;
var text = l.axis[0].text.items;
for (var k in text) {
if (ii++ % n) {
text[k].attr({text: ''});
} else {
var cur = text[k].attr('text');
str = format(cur, config.xformat);
text[k].attr({text: str});
}
}
// Show values on hover.
l.hoverColumn(function() {
this.tags = r.set();
for (var yy = 0; yy < config.y.length; yy++) {
var yvalue = 0;
for (var ii = 0; ii < config.x[0].length; ii++) {
if (config.x[0][ii] > this.axis) {
break;
}
yvalue = format(config.y[yy][ii], config.yformat);
}
var xvalue = format(this.axis, config.xformat);
var tag = r.tag(
this.x,
this.y[yy],
[xvalue, yvalue].join("\n"),
180,
24);
tag
.insertBefore(this)
.attr([{fill : '#fff'}, {fill: '#000'}]);
this.tags.push(tag);
}
}, function() {
this.tags && this.tags.remove();
});
});
-
diff --git a/webroot/rsrc/js/application/maniphest/behavior-transaction-controls.js b/webroot/rsrc/js/application/maniphest/behavior-transaction-controls.js
index b4087067a2..213ed47b11 100644
--- a/webroot/rsrc/js/application/maniphest/behavior-transaction-controls.js
+++ b/webroot/rsrc/js/application/maniphest/behavior-transaction-controls.js
@@ -1,36 +1,35 @@
/**
* @provides javelin-behavior-maniphest-transaction-controls
* @requires javelin-behavior
* javelin-dom
* phabricator-prefab
*/
JX.behavior('maniphest-transaction-controls', function(config) {
var tokenizers = {};
for (var k in config.tokenizers) {
var tconfig = config.tokenizers[k];
tokenizers[k] = JX.Prefab.buildTokenizer(tconfig).tokenizer;
tokenizers[k].start();
}
JX.DOM.listen(
JX.$(config.select),
'change',
null,
function(e) {
for (var k in config.controlMap) {
if (k == JX.$(config.select).value) {
JX.DOM.show(JX.$(config.controlMap[k]));
if (tokenizers[k]) {
tokenizers[k].refresh();
}
} else {
JX.DOM.hide(JX.$(config.controlMap[k]));
}
}
});
});
-
diff --git a/webroot/rsrc/js/application/search/behavior-reorder-queries.js b/webroot/rsrc/js/application/search/behavior-reorder-queries.js
index 8a7359f12c..38568193e3 100644
--- a/webroot/rsrc/js/application/search/behavior-reorder-queries.js
+++ b/webroot/rsrc/js/application/search/behavior-reorder-queries.js
@@ -1,42 +1,41 @@
/**
* @provides javelin-behavior-search-reorder-queries
* @requires javelin-behavior
* javelin-stratcom
* javelin-workflow
* javelin-dom
* phabricator-draggable-list
*/
JX.behavior('search-reorder-queries', function(config) {
var root = JX.$(config.listID);
var list = new JX.DraggableList('named-query', root)
.setFindItemsHandler(function() {
return JX.DOM.scry(root, 'li', 'named-query');
});
list.listen('didDrop', function(node, after) {
var nodes = list.findItems();
var order = [];
var key;
for (var ii = 0; ii < nodes.length; ii++) {
key = JX.Stratcom.getData(nodes[ii]).queryKey;
if (key) {
order.push(key);
}
}
list.lock();
JX.DOM.alterClass(node, 'drag-sending', true);
new JX.Workflow(config.orderURI, {order: order.join()})
.setHandler(function(e) {
JX.DOM.alterClass(node, 'drag-sending', false);
list.unlock();
})
.start();
});
});
-
diff --git a/webroot/rsrc/js/core/FileUpload.js b/webroot/rsrc/js/core/FileUpload.js
index bdc28f427f..2d9e2d9b1d 100644
--- a/webroot/rsrc/js/core/FileUpload.js
+++ b/webroot/rsrc/js/core/FileUpload.js
@@ -1,114 +1,113 @@
/**
* @requires javelin-install
* javelin-dom
* phabricator-notification
* @provides phabricator-file-upload
* @javelin
*/
JX.install('PhabricatorFileUpload', {
construct : function() {
this._notification = new JX.Notification();
},
properties : {
name : null,
totalBytes : null,
uploadedBytes : null,
ID : null,
PHID : null,
URI : null,
status : null,
markup : null,
error : null
},
members : {
_notification : null,
update : function() {
if (!this._notification) {
return;
}
this._notification
.setDuration(0)
.show();
var content;
switch (this.getStatus()) {
case 'done':
var link = JX.$N('a', {href: this.getURI()}, 'F' + this.getID());
content = [
JX.$N('strong', {}, ['Upload Complete (', link, ')']),
JX.$N('br'),
this.getName()
];
this._notification
.setContent(content)
.alterClassName('jx-notification-done', true)
.setDuration(12000);
this._notification = null;
break;
case 'error':
content = [
JX.$N('strong', {}, 'Upload Failure'),
JX.$N('br'),
this.getName(),
JX.$N('br'),
JX.$N('br'),
this.getError()
];
this._notification
.setContent(content)
.alterClassName('jx-notification-error', true);
this._notification = null;
break;
default:
var info = '';
if (this.getTotalBytes()) {
var p = this._renderPercentComplete();
var f = this._renderFileSize();
info = ' (' + p + ' of ' + f + ')';
}
info = 'Uploading "' + this.getName() + '"' + info + '...';
this._notification
.setContent(info);
break;
}
return this;
},
_renderPercentComplete : function() {
if (!this.getTotalBytes()) {
return null;
}
var ratio = this.getUploadedBytes() / this.getTotalBytes();
return parseInt(100 * ratio, 10) + '%';
},
_renderFileSize : function() {
if (!this.getTotalBytes()) {
return null;
}
var s = 3;
var n = this.getTotalBytes();
while (s && n >= 1000) {
n = Math.round(n / 100);
n = n / 10;
s--;
}
s = ['GB', 'MB', 'KB', 'bytes'][s];
return n + ' ' + s;
}
}
});
-
diff --git a/webroot/rsrc/js/core/MultirowRowManager.js b/webroot/rsrc/js/core/MultirowRowManager.js
index 319ff353c2..8c3655ddfe 100644
--- a/webroot/rsrc/js/core/MultirowRowManager.js
+++ b/webroot/rsrc/js/core/MultirowRowManager.js
@@ -1,146 +1,145 @@
/**
* @requires javelin-install
* javelin-stratcom
* javelin-dom
* javelin-util
* @provides multirow-row-manager
* @javelin
*/
/**
* Give a MultirowRowManager a table DOM elem to manage.
* You can add rows, and provide a given ID if you like.
* You can update rows by ID.
* Rows are automatically equipped with a removal button.
* You can listen to the 'row-removed' event on the Manager to get
* notifications of these row removals, with the DOM id of the removed
* row as event data.
*/
JX.install('MultirowRowManager', {
/**
* @param DOM element <table> root Container for rows
*/
construct : function(root, minRows) {
this._root = root;
this._rows = [];
if (typeof minRows !== "undefined") {
this._minRows = minRows;
} else {
this._minRows = 1;
}
JX.DOM.listen(
this._root,
'click',
JX.MultirowRowManager._removeSigil,
JX.bind(this, this._onrowremoved));
},
members : {
_count : 0,
_nextID : 0,
_root : null,
_rows : null,
_generateRowID : function() {
return "" + this._nextID++;
},
_wrapRowContents : function(row_id, row_contents) {
var row = JX.$N('tr',
{ sigil : JX.MultirowRowManager.getRowSigil(),
meta : { multirow_row_manager_row_id : row_id }
},
row_contents);
var removeButton = JX.$N(
'td',
{},
JX.$N(
'a',
{ className: "button",
sigil: JX.MultirowRowManager._removeSigil
},
'-'));
JX.DOM.appendContent(row, removeButton);
return row;
},
getRowID : function(row) {
return JX.Stratcom.getData(row).multirow_row_manager_row_id;
},
/**
* @param row_contents [DOM elements] New contents of row
* @param row_id row ID to update, will throw if this row has been removed
*/
updateRow : function(row_id, row_contents) {
if (__DEV__) {
if (typeof this._rows[row_id] === "undefined") {
throw new Error("JX.MultirowRowManager.updateRow(row_id, " +
"row_contents): provided row id does not exist." +
" Use addRow to create a new row and make sure " +
"not to update rows that have been deleted.");
}
}
var old_row = this._rows[row_id];
var new_row = this._wrapRowContents(row_id, row_contents);
JX.copy(JX.Stratcom.getData(new_row), JX.Stratcom.getData(old_row));
JX.DOM.replace(old_row, new_row);
this._rows[row_id] = new_row;
this._oncountchanged(); // Fix the new button.
return new_row;
},
addRow : function(row_contents) {
var row_id = this._generateRowID();
var row = this._wrapRowContents(row_id, row_contents);
JX.DOM.appendContent(this._root, row);
this._count++;
this._oncountchanged();
this._rows[row_id] = row;
return row;
},
_onrowremoved : function(e) {
if (!JX.Stratcom.getData(e.getTarget()).enabled) {
return;
}
var row = e.getNode(JX.MultirowRowManager.getRowSigil());
var row_id = this.getRowID(row);
delete this._rows[row_id];
JX.DOM.remove(row);
this._count--;
this._oncountchanged();
this.invoke('row-removed', row_id);
},
_oncountchanged : function(e) {
var buttons = JX.DOM.scry(
this._root,
'a',
JX.MultirowRowManager._removeSigil);
var disable = (this._minRows >= 0 && this._count <= this._minRows);
for (var i = 0; i < buttons.length; i++) {
var button = buttons[i];
JX.DOM.alterClass(button, 'disabled', disable);
JX.Stratcom.getData(button).enabled = !disable;
}
}
},
events : ['row-removed'],
statics : {
getRowSigil : function() {
return "tools-multirow-row-manager-row";
},
_removeSigil : "tools-multirow-row-manager-row-remove"
}
});
-
diff --git a/webroot/rsrc/js/core/Notification.js b/webroot/rsrc/js/core/Notification.js
index 494508c32d..0b66825b9f 100644
--- a/webroot/rsrc/js/core/Notification.js
+++ b/webroot/rsrc/js/core/Notification.js
@@ -1,191 +1,190 @@
/**
* @requires javelin-install
* javelin-dom
* javelin-stratcom
* javelin-util
* phabricator-notification-css
* @provides phabricator-notification
* @javelin
*/
/**
* Show a notification popup on screen. Usage:
*
* var n = new JX.Notification()
* .setContent('click me!');
* n.listen('activate', function(e) { alert('you clicked!'); });
* n.show();
*
*/
JX.install('Notification', {
events : ['activate', 'close'],
members : {
_container : null,
_visible : false,
_hideTimer : null,
_duration : 12000,
show : function() {
if (!this._visible) {
this._visible = true;
var self = JX.Notification;
self._show(this);
this._updateTimer();
}
return this;
},
hide : function() {
if (this._visible) {
this._visible = false;
var self = JX.Notification;
self._hide(this);
this._updateTimer();
}
return this;
},
alterClassName : function(name, enable) {
JX.DOM.alterClass(this._getContainer(), name, enable);
return this;
},
setContent : function(content) {
JX.DOM.setContent(this._getContainer(), content);
return this;
},
/**
* Set duration before the notification fades away, in milliseconds. If set
* to 0, the notification persists until dismissed.
*
* @param int Notification duration, in milliseconds.
* @return this
*/
setDuration : function(milliseconds) {
this._duration = milliseconds;
this._updateTimer(false);
return this;
},
_updateTimer : function() {
if (this._hideTimer) {
clearTimeout(this._hideTimer);
this._hideTimer = null;
}
if (this._visible && this._duration) {
this._hideTimer = setTimeout(JX.bind(this, this.hide), this._duration);
}
},
_getContainer : function() {
if (!this._container) {
this._container = JX.$N(
'div',
{
className: 'jx-notification',
sigil: 'jx-notification'
});
}
return this._container;
}
},
statics : {
_container : null,
_listening : false,
_active : [],
_show : function(notification) {
var self = JX.Notification;
self._installListener();
self._active.push(notification);
self._redraw();
},
_hide : function(notification) {
var self = JX.Notification;
for (var ii = 0; ii < self._active.length; ii++) {
if (self._active[ii] === notification) {
notification.invoke('close');
self._active.splice(ii, 1);
break;
}
}
self._redraw();
},
_installListener : function() {
var self = JX.Notification;
if (self._listening) {
return;
} else {
self._listening = true;
}
JX.Stratcom.listen(
'click',
'jx-notification',
function(e) {
// NOTE: Don't kill the event since the user might have clicked a
// link, and we want to follow the link if they did. Instead, invoke
// the activate event for the active notification and dismiss it if it
// isn't handled.
var target = e.getNode('jx-notification');
for (var ii = 0; ii < self._active.length; ii++) {
var n = self._active[ii];
if (n._getContainer() === target) {
var activation = n.invoke('activate');
if (!activation.getPrevented()) {
n.hide();
}
return;
}
}
});
},
_redraw : function() {
var self = JX.Notification;
if (!self._active.length) {
if (self._container) {
JX.DOM.remove(self._container);
self._container = null;
}
return;
}
if (!self._container) {
self._container = JX.$N(
'div',
{
className: 'jx-notification-container'
});
document.body.appendChild(self._container);
}
// Show only a limited number of notifications at once.
var limit = 5;
var notifications = [];
for (var ii = 0; ii < self._active.length; ii++) {
notifications.push(self._active[ii]._getContainer());
if (!(--limit)) {
break;
}
}
JX.DOM.setContent(self._container, notifications);
}
}
});
-
diff --git a/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js b/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js
index 352c617a96..c5f3bf3a30 100644
--- a/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js
+++ b/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js
@@ -1,42 +1,41 @@
/**
* @provides javelin-behavior-aphront-drag-and-drop-textarea
* @requires javelin-behavior
* javelin-dom
* phabricator-drag-and-drop-file-upload
* phabricator-textareautils
*/
JX.behavior('aphront-drag-and-drop-textarea', function(config) {
var target = JX.$(config.target);
function onupload(f) {
var text = JX.TextAreaUtils.getSelectionText(target);
var ref = '{F' + f.getID() + '}';
// If the user has dragged and dropped multiple files, we'll get an event
// each time an upload completes. Rather than overwriting the first
// reference, append the new reference if the selected text looks like an
// existing file reference.
if (text.match(/^\{F/)) {
ref = text + "\n\n" + ref;
}
JX.TextAreaUtils.setSelectionText(target, ref);
}
if (JX.PhabricatorDragAndDropFileUpload.isSupported()) {
var drop = new JX.PhabricatorDragAndDropFileUpload(target)
.setURI(config.uri);
drop.listen('didBeginDrag', function(e) {
JX.DOM.alterClass(target, config.activatedClass, true);
});
drop.listen('didEndDrag', function(e) {
JX.DOM.alterClass(target, config.activatedClass, false);
});
drop.listen('didUpload', onupload);
drop.start();
}
});
-
diff --git a/webroot/rsrc/js/core/behavior-global-drag-and-drop.js b/webroot/rsrc/js/core/behavior-global-drag-and-drop.js
index ad66625599..93986f4c99 100644
--- a/webroot/rsrc/js/core/behavior-global-drag-and-drop.js
+++ b/webroot/rsrc/js/core/behavior-global-drag-and-drop.js
@@ -1,68 +1,67 @@
/**
* @provides javelin-behavior-global-drag-and-drop
* @requires javelin-behavior
* javelin-dom
* javelin-uri
* javelin-mask
* phabricator-drag-and-drop-file-upload
*/
JX.behavior('global-drag-and-drop', function(config) {
if (!JX.PhabricatorDragAndDropFileUpload.isSupported()) {
return;
}
var pending = 0;
var files = [];
var errors = false;
if (config.ifSupported) {
JX.$(config.ifSupported).style.display = '';
}
var drop = new JX.PhabricatorDragAndDropFileUpload(document.documentElement)
.setURI(config.uploadURI);
drop.listen('didBeginDrag', function(f) {
JX.Mask.show();
JX.DOM.show(JX.$(config.instructions));
});
drop.listen('didEndDrag', function(f) {
JX.Mask.hide();
JX.DOM.hide(JX.$(config.instructions));
});
drop.listen('willUpload', function(f) {
pending++;
});
drop.listen('didUpload', function(f) {
files.push(f);
pending--;
if (pending === 0 && !errors) {
// If whatever the user dropped in has finished uploading, send them to
// their uploads.
var uri;
uri = JX.$U(config.browseURI);
var ids = [];
for (var ii = 0; ii < files.length; ii++) {
ids.push(files[ii].getID());
}
uri.setQueryParam('h', ids.join(','));
files = [];
uri.go();
}
});
drop.listen('didError', function(f) {
pending--;
errors = true;
});
drop.start();
});
-
diff --git a/webroot/rsrc/swf/test.html b/webroot/rsrc/swf/test.html
index 1cdf65ae62..37b96bde53 100644
--- a/webroot/rsrc/swf/test.html
+++ b/webroot/rsrc/swf/test.html
@@ -1,4 +1,4 @@
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="550" height="400" id="movie_name" align="middle">
<param name="movie" value="aphlict.swf" />
<param name="allowScriptAccess" value="always" />
-</object>
\ No newline at end of file
+</object>

File Metadata

Mime Type
text/x-diff
Expires
Sun, Sep 7, 10:18 AM (1 d, 2 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
223085
Default Alt Text
(805 KB)

Event Timeline