Compare commits
87 Commits
blender-tw
...
blender-tw
Author | SHA1 | Date | |
---|---|---|---|
81827bee9a | |||
![]() |
c041cfa1bd | ||
e53bf0655e | |||
114b2b9f1b | |||
883046f69f | |||
1810f1dda9 | |||
14aa6a671b | |||
![]() |
34082efb02 | ||
![]() |
bf8707d3a9 | ||
![]() |
ae5a38f334 | ||
![]() |
c04147328f | ||
![]() |
671986592b | ||
![]() |
bc4f86d279 | ||
![]() |
b2e96df3a3 | ||
![]() |
2b8bbae5fb | ||
![]() |
058d2489e7 | ||
![]() |
1f7c736f9a | ||
![]() |
0f27cd46cc | ||
![]() |
0f0e94ca71 | ||
![]() |
a5f20f7106 | ||
![]() |
58d3f6145a | ||
![]() |
f21a00a315 | ||
![]() |
a754c694de | ||
![]() |
969587f7b0 | ||
![]() |
2a83df5786 | ||
![]() |
6f78e2a91c | ||
![]() |
737e7c8541 | ||
![]() |
93ef902ffa | ||
![]() |
a39c590442 | ||
![]() |
cebde34425 | ||
![]() |
e454c3dafe | ||
![]() |
7d6874d9f0 | ||
![]() |
3a80efa440 | ||
![]() |
f6238f9d9b | ||
![]() |
a9506097ea | ||
![]() |
a745055813 | ||
![]() |
367cd28927 | ||
![]() |
3dfa89dd5d | ||
![]() |
6e1b5da112 | ||
![]() |
7daaaa8463 | ||
![]() |
0854425d19 | ||
![]() |
72f149bf39 | ||
![]() |
429543b637 | ||
![]() |
0b64092d25 | ||
![]() |
49af92e903 | ||
![]() |
57f9450bcf | ||
![]() |
8aec3f916b | ||
![]() |
36dac46ff2 | ||
![]() |
57ee6649aa | ||
![]() |
2b0632b442 | ||
![]() |
7087c0439a | ||
![]() |
cd09ba5e19 | ||
![]() |
9fa2525384 | ||
![]() |
46695c76eb | ||
![]() |
c6de7c66a3 | ||
![]() |
9afc5c6287 | ||
![]() |
c8a279957d | ||
![]() |
60e9f64190 | ||
![]() |
7fd6bf26a9 | ||
![]() |
81e4e5b7f9 | ||
![]() |
5454175973 | ||
![]() |
79375c6c53 | ||
![]() |
ce0dc9a2ba | ||
![]() |
dbdfac1e07 | ||
![]() |
2db1955159 | ||
![]() |
98e0440d45 | ||
![]() |
c1eeacd850 | ||
![]() |
017ef1927c | ||
![]() |
a27c83757d | ||
![]() |
78d1b62bb8 | ||
![]() |
5f0535934d | ||
![]() |
fcb75d0503 | ||
![]() |
8f9ba48528 | ||
![]() |
0ed5569e9f | ||
![]() |
37ffb71c4d | ||
![]() |
1d4d860cb5 | ||
![]() |
6e85b521fe | ||
![]() |
b21b73b8dd | ||
![]() |
73c4240415 | ||
![]() |
56838c0e3d | ||
![]() |
205657ac76 | ||
![]() |
7d496f2c6d | ||
![]() |
a7b3ba5a6f | ||
![]() |
22de618d3b | ||
![]() |
d91abf50f7 | ||
![]() |
9ce1271805 | ||
![]() |
8c7f114b4d |
722
externals/amazon-ses/ses.php
vendored
722
externals/amazon-ses/ses.php
vendored
@@ -1,722 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* Copyright (c) 2011, Dan Myers.
|
||||
* Parts copyright (c) 2008, Donovan Schonknecht.
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This is a modified BSD license (the third clause has been removed).
|
||||
* The BSD license may be found here:
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*
|
||||
* Amazon Simple Email Service is a trademark of Amazon.com, Inc. or its affiliates.
|
||||
*
|
||||
* SimpleEmailService is based on Donovan Schonknecht's Amazon S3 PHP class, found here:
|
||||
* http://undesigned.org.za/2007/10/22/amazon-s3-php-class
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Amazon SimpleEmailService PHP class
|
||||
*
|
||||
* @link http://sourceforge.net/projects/php-aws-ses/
|
||||
* version 0.8.1
|
||||
*
|
||||
*/
|
||||
class SimpleEmailService
|
||||
{
|
||||
protected $__accessKey; // AWS Access key
|
||||
protected $__secretKey; // AWS Secret key
|
||||
protected $__host;
|
||||
|
||||
public function getAccessKey() { return $this->__accessKey; }
|
||||
public function getSecretKey() { return $this->__secretKey; }
|
||||
public function getHost() { return $this->__host; }
|
||||
|
||||
protected $__verifyHost = 1;
|
||||
protected $__verifyPeer = 1;
|
||||
|
||||
// verifyHost and verifyPeer determine whether curl verifies ssl certificates.
|
||||
// It may be necessary to disable these checks on certain systems.
|
||||
// These only have an effect if SSL is enabled.
|
||||
public function verifyHost() { return $this->__verifyHost; }
|
||||
public function enableVerifyHost($enable = true) { $this->__verifyHost = $enable; }
|
||||
|
||||
public function verifyPeer() { return $this->__verifyPeer; }
|
||||
public function enableVerifyPeer($enable = true) { $this->__verifyPeer = $enable; }
|
||||
|
||||
// If you use exceptions, errors will be communicated by throwing a
|
||||
// SimpleEmailServiceException. By default, they will be trigger_error()'d.
|
||||
protected $__useExceptions = 0;
|
||||
public function useExceptions() { return $this->__useExceptions; }
|
||||
public function enableUseExceptions($enable = true) { $this->__useExceptions = $enable; }
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $accessKey Access key
|
||||
* @param string $secretKey Secret key
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($accessKey = null, $secretKey = null, $host = 'email.us-east-1.amazonaws.com') {
|
||||
if (!function_exists('simplexml_load_string')) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'The PHP SimpleXML extension is not available, but this '.
|
||||
'extension is required to send mail via Amazon SES, because '.
|
||||
'Amazon SES returns API responses in XML format. Install or '.
|
||||
'enable the SimpleXML extension.'));
|
||||
}
|
||||
|
||||
// Catch mistakes with reading the wrong column out of the SES
|
||||
// documentation. See T10728.
|
||||
if (preg_match('(-smtp)', $host)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Amazon SES is not configured correctly: the configured SES '.
|
||||
'endpoint ("%s") is an SMTP endpoint. Instead, use an API (HTTPS) '.
|
||||
'endpoint.',
|
||||
$host));
|
||||
}
|
||||
|
||||
if ($accessKey !== null && $secretKey !== null) {
|
||||
$this->setAuth($accessKey, $secretKey);
|
||||
}
|
||||
|
||||
$this->__host = $host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set AWS access key and secret key
|
||||
*
|
||||
* @param string $accessKey Access key
|
||||
* @param string $secretKey Secret key
|
||||
* @return void
|
||||
*/
|
||||
public function setAuth($accessKey, $secretKey) {
|
||||
$this->__accessKey = $accessKey;
|
||||
$this->__secretKey = $secretKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the email addresses that have been verified and can be used as the 'From' address
|
||||
*
|
||||
* @return An array containing two items: a list of verified email addresses, and the request id.
|
||||
*/
|
||||
public function listVerifiedEmailAddresses() {
|
||||
$rest = new SimpleEmailServiceRequest($this, 'GET');
|
||||
$rest->setParameter('Action', 'ListVerifiedEmailAddresses');
|
||||
|
||||
$rest = $rest->getResponse();
|
||||
|
||||
$response = array();
|
||||
if(!isset($rest->body)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$addresses = array();
|
||||
foreach($rest->body->ListVerifiedEmailAddressesResult->VerifiedEmailAddresses->member as $address) {
|
||||
$addresses[] = (string)$address;
|
||||
}
|
||||
|
||||
$response['Addresses'] = $addresses;
|
||||
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests verification of the provided email address, so it can be used
|
||||
* as the 'From' address when sending emails through SimpleEmailService.
|
||||
*
|
||||
* After submitting this request, you should receive a verification email
|
||||
* from Amazon at the specified address containing instructions to follow.
|
||||
*
|
||||
* @param string email The email address to get verified
|
||||
* @return The request id for this request.
|
||||
*/
|
||||
public function verifyEmailAddress($email) {
|
||||
$rest = new SimpleEmailServiceRequest($this, 'POST');
|
||||
$rest->setParameter('Action', 'VerifyEmailAddress');
|
||||
$rest->setParameter('EmailAddress', $email);
|
||||
|
||||
$rest = $rest->getResponse();
|
||||
|
||||
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified email address from the list of verified addresses.
|
||||
*
|
||||
* @param string email The email address to remove
|
||||
* @return The request id for this request.
|
||||
*/
|
||||
public function deleteVerifiedEmailAddress($email) {
|
||||
$rest = new SimpleEmailServiceRequest($this, 'DELETE');
|
||||
$rest->setParameter('Action', 'DeleteVerifiedEmailAddress');
|
||||
$rest->setParameter('EmailAddress', $email);
|
||||
|
||||
$rest = $rest->getResponse();
|
||||
|
||||
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves information on the current activity limits for this account.
|
||||
* See http://docs.amazonwebservices.com/ses/latest/APIReference/API_GetSendQuota.html
|
||||
*
|
||||
* @return An array containing information on this account's activity limits.
|
||||
*/
|
||||
public function getSendQuota() {
|
||||
$rest = new SimpleEmailServiceRequest($this, 'GET');
|
||||
$rest->setParameter('Action', 'GetSendQuota');
|
||||
|
||||
$rest = $rest->getResponse();
|
||||
|
||||
$response = array();
|
||||
if(!isset($rest->body)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response['Max24HourSend'] = (string)$rest->body->GetSendQuotaResult->Max24HourSend;
|
||||
$response['MaxSendRate'] = (string)$rest->body->GetSendQuotaResult->MaxSendRate;
|
||||
$response['SentLast24Hours'] = (string)$rest->body->GetSendQuotaResult->SentLast24Hours;
|
||||
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves statistics for the last two weeks of activity on this account.
|
||||
* See http://docs.amazonwebservices.com/ses/latest/APIReference/API_GetSendStatistics.html
|
||||
*
|
||||
* @return An array of activity statistics. Each array item covers a 15-minute period.
|
||||
*/
|
||||
public function getSendStatistics() {
|
||||
$rest = new SimpleEmailServiceRequest($this, 'GET');
|
||||
$rest->setParameter('Action', 'GetSendStatistics');
|
||||
|
||||
$rest = $rest->getResponse();
|
||||
|
||||
$response = array();
|
||||
if(!isset($rest->body)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$datapoints = array();
|
||||
foreach($rest->body->GetSendStatisticsResult->SendDataPoints->member as $datapoint) {
|
||||
$p = array();
|
||||
$p['Bounces'] = (string)$datapoint->Bounces;
|
||||
$p['Complaints'] = (string)$datapoint->Complaints;
|
||||
$p['DeliveryAttempts'] = (string)$datapoint->DeliveryAttempts;
|
||||
$p['Rejects'] = (string)$datapoint->Rejects;
|
||||
$p['Timestamp'] = (string)$datapoint->Timestamp;
|
||||
|
||||
$datapoints[] = $p;
|
||||
}
|
||||
|
||||
$response['SendDataPoints'] = $datapoints;
|
||||
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
public function sendRawEmail($raw) {
|
||||
$rest = new SimpleEmailServiceRequest($this, 'POST');
|
||||
$rest->setParameter('Action', 'SendRawEmail');
|
||||
$rest->setParameter('RawMessage.Data', base64_encode($raw));
|
||||
|
||||
$rest = $rest->getResponse();
|
||||
|
||||
$response['MessageId'] = (string)$rest->body->SendEmailResult->MessageId;
|
||||
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a SimpleEmailServiceMessage object, submits the message to the service for sending.
|
||||
*
|
||||
* @return An array containing the unique identifier for this message and a separate request id.
|
||||
* Returns false if the provided message is missing any required fields.
|
||||
*/
|
||||
public function sendEmail($sesMessage) {
|
||||
if(!$sesMessage->validate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$rest = new SimpleEmailServiceRequest($this, 'POST');
|
||||
$rest->setParameter('Action', 'SendEmail');
|
||||
|
||||
$i = 1;
|
||||
foreach($sesMessage->to as $to) {
|
||||
$rest->setParameter('Destination.ToAddresses.member.'.$i, $to);
|
||||
$i++;
|
||||
}
|
||||
|
||||
if(is_array($sesMessage->cc)) {
|
||||
$i = 1;
|
||||
foreach($sesMessage->cc as $cc) {
|
||||
$rest->setParameter('Destination.CcAddresses.member.'.$i, $cc);
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
if(is_array($sesMessage->bcc)) {
|
||||
$i = 1;
|
||||
foreach($sesMessage->bcc as $bcc) {
|
||||
$rest->setParameter('Destination.BccAddresses.member.'.$i, $bcc);
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
if(is_array($sesMessage->replyto)) {
|
||||
$i = 1;
|
||||
foreach($sesMessage->replyto as $replyto) {
|
||||
$rest->setParameter('ReplyToAddresses.member.'.$i, $replyto);
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
$rest->setParameter('Source', $sesMessage->from);
|
||||
|
||||
if($sesMessage->returnpath != null) {
|
||||
$rest->setParameter('ReturnPath', $sesMessage->returnpath);
|
||||
}
|
||||
|
||||
if($sesMessage->subject != null && strlen($sesMessage->subject) > 0) {
|
||||
$rest->setParameter('Message.Subject.Data', $sesMessage->subject);
|
||||
if($sesMessage->subjectCharset != null && strlen($sesMessage->subjectCharset) > 0) {
|
||||
$rest->setParameter('Message.Subject.Charset', $sesMessage->subjectCharset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if($sesMessage->messagetext != null && strlen($sesMessage->messagetext) > 0) {
|
||||
$rest->setParameter('Message.Body.Text.Data', $sesMessage->messagetext);
|
||||
if($sesMessage->messageTextCharset != null && strlen($sesMessage->messageTextCharset) > 0) {
|
||||
$rest->setParameter('Message.Body.Text.Charset', $sesMessage->messageTextCharset);
|
||||
}
|
||||
}
|
||||
|
||||
if($sesMessage->messagehtml != null && strlen($sesMessage->messagehtml) > 0) {
|
||||
$rest->setParameter('Message.Body.Html.Data', $sesMessage->messagehtml);
|
||||
if($sesMessage->messageHtmlCharset != null && strlen($sesMessage->messageHtmlCharset) > 0) {
|
||||
$rest->setParameter('Message.Body.Html.Charset', $sesMessage->messageHtmlCharset);
|
||||
}
|
||||
}
|
||||
|
||||
$rest = $rest->getResponse();
|
||||
|
||||
$response['MessageId'] = (string)$rest->body->SendEmailResult->MessageId;
|
||||
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger an error message
|
||||
*
|
||||
* @internal Used by member functions to output errors
|
||||
* @param array $error Array containing error information
|
||||
* @return string
|
||||
*/
|
||||
public function __triggerError($functionname, $error)
|
||||
{
|
||||
if($error == false) {
|
||||
$message = sprintf("SimpleEmailService::%s(): Encountered an error, but no description given", $functionname);
|
||||
}
|
||||
else if(isset($error['curl']) && $error['curl'])
|
||||
{
|
||||
$message = sprintf("SimpleEmailService::%s(): %s %s", $functionname, $error['code'], $error['message']);
|
||||
}
|
||||
else if(isset($error['Error']))
|
||||
{
|
||||
$e = $error['Error'];
|
||||
$message = sprintf("SimpleEmailService::%s(): %s - %s: %s\nRequest Id: %s\n", $functionname, $e['Type'], $e['Code'], $e['Message'], $error['RequestId']);
|
||||
}
|
||||
|
||||
if ($this->useExceptions()) {
|
||||
throw new SimpleEmailServiceException($message);
|
||||
} else {
|
||||
trigger_error($message, E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for 503 retries.
|
||||
*
|
||||
* @internal Used by SimpleDBRequest to call the user-specified callback, if set
|
||||
* @param $attempt The number of failed attempts so far
|
||||
* @return The retry delay in microseconds, or 0 to stop retrying.
|
||||
*/
|
||||
public function __executeServiceTemporarilyUnavailableRetryDelay($attempt)
|
||||
{
|
||||
if(is_callable($this->__serviceUnavailableRetryDelayCallback)) {
|
||||
$callback = $this->__serviceUnavailableRetryDelayCallback;
|
||||
return $callback($attempt);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
final class SimpleEmailServiceRequest
|
||||
{
|
||||
private $ses, $verb, $parameters = array();
|
||||
public $response;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $ses The SimpleEmailService object making this request
|
||||
* @param string $action action
|
||||
* @param string $verb HTTP verb
|
||||
* @return mixed
|
||||
*/
|
||||
function __construct($ses, $verb) {
|
||||
$this->ses = $ses;
|
||||
$this->verb = $verb;
|
||||
$this->response = new STDClass;
|
||||
$this->response->error = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set request parameter
|
||||
*
|
||||
* @param string $key Key
|
||||
* @param string $value Value
|
||||
* @param boolean $replace Whether to replace the key if it already exists (default true)
|
||||
* @return void
|
||||
*/
|
||||
public function setParameter($key, $value, $replace = true) {
|
||||
if(!$replace && isset($this->parameters[$key]))
|
||||
{
|
||||
$temp = (array)($this->parameters[$key]);
|
||||
$temp[] = $value;
|
||||
$this->parameters[$key] = $temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->parameters[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response
|
||||
*
|
||||
* @return object | false
|
||||
*/
|
||||
public function getResponse() {
|
||||
|
||||
$params = array();
|
||||
foreach ($this->parameters as $var => $value)
|
||||
{
|
||||
if(is_array($value))
|
||||
{
|
||||
foreach($value as $v)
|
||||
{
|
||||
$params[] = $var.'='.$this->__customUrlEncode($v);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$params[] = $var.'='.$this->__customUrlEncode($value);
|
||||
}
|
||||
}
|
||||
|
||||
sort($params, SORT_STRING);
|
||||
|
||||
// must be in format 'Sun, 06 Nov 1994 08:49:37 GMT'
|
||||
$date = gmdate('D, d M Y H:i:s e');
|
||||
|
||||
$query = implode('&', $params);
|
||||
|
||||
$headers = array();
|
||||
$headers[] = 'Date: '.$date;
|
||||
$headers[] = 'Host: '.$this->ses->getHost();
|
||||
|
||||
$auth = 'AWS3-HTTPS AWSAccessKeyId='.$this->ses->getAccessKey();
|
||||
$auth .= ',Algorithm=HmacSHA256,Signature='.$this->__getSignature($date);
|
||||
$headers[] = 'X-Amzn-Authorization: '.$auth;
|
||||
|
||||
$url = 'https://'.$this->ses->getHost().'/';
|
||||
|
||||
// Basic setup
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_USERAGENT, 'SimpleEmailService/php');
|
||||
|
||||
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, ($this->ses->verifyHost() ? 2 : 0));
|
||||
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, ($this->ses->verifyPeer() ? 1 : 0));
|
||||
|
||||
// Request types
|
||||
switch ($this->verb) {
|
||||
case 'GET':
|
||||
$url .= '?'.$query;
|
||||
break;
|
||||
case 'POST':
|
||||
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, $query);
|
||||
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
|
||||
break;
|
||||
case 'DELETE':
|
||||
$url .= '?'.$query;
|
||||
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($curl, CURLOPT_HEADER, false);
|
||||
|
||||
curl_setopt($curl, CURLOPT_URL, $url);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
|
||||
curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
|
||||
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
|
||||
|
||||
// Execute, grab errors
|
||||
if (!curl_exec($curl)) {
|
||||
throw new SimpleEmailServiceException(
|
||||
pht(
|
||||
'Encountered an error while making an HTTP request to Amazon SES '.
|
||||
'(cURL Error #%d): %s',
|
||||
curl_errno($curl),
|
||||
curl_error($curl)));
|
||||
}
|
||||
|
||||
$this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
if ($this->response->code != 200) {
|
||||
throw new SimpleEmailServiceException(
|
||||
pht(
|
||||
'Unexpected HTTP status while making request to Amazon SES: '.
|
||||
'expected 200, got %s.',
|
||||
$this->response->code));
|
||||
}
|
||||
|
||||
@curl_close($curl);
|
||||
|
||||
// Parse body into XML
|
||||
if ($this->response->error === false && isset($this->response->body)) {
|
||||
$this->response->body = simplexml_load_string($this->response->body);
|
||||
|
||||
// Grab SES errors
|
||||
if (!in_array($this->response->code, array(200, 201, 202, 204))
|
||||
&& isset($this->response->body->Error)) {
|
||||
$error = $this->response->body->Error;
|
||||
$output = array();
|
||||
$output['curl'] = false;
|
||||
$output['Error'] = array();
|
||||
$output['Error']['Type'] = (string)$error->Type;
|
||||
$output['Error']['Code'] = (string)$error->Code;
|
||||
$output['Error']['Message'] = (string)$error->Message;
|
||||
$output['RequestId'] = (string)$this->response->body->RequestId;
|
||||
|
||||
$this->response->error = $output;
|
||||
unset($this->response->body);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* CURL write callback
|
||||
*
|
||||
* @param resource &$curl CURL resource
|
||||
* @param string &$data Data
|
||||
* @return integer
|
||||
*/
|
||||
private function __responseWriteCallback(&$curl, &$data) {
|
||||
if(!isset($this->response->body)) $this->response->body = '';
|
||||
$this->response->body .= $data;
|
||||
return strlen($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Contributed by afx114
|
||||
* URL encode the parameters as per http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html?Query_QueryAuth.html
|
||||
* PHP's rawurlencode() follows RFC 1738, not RFC 3986 as required by Amazon. The only difference is the tilde (~), so convert it back after rawurlencode
|
||||
* See: http://www.morganney.com/blog/API/AWS-Product-Advertising-API-Requires-a-Signed-Request.php
|
||||
*
|
||||
* @param string $var String to encode
|
||||
* @return string
|
||||
*/
|
||||
private function __customUrlEncode($var) {
|
||||
return str_replace('%7E', '~', rawurlencode($var));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the auth string using Hmac-SHA256
|
||||
*
|
||||
* @internal Used by SimpleDBRequest::getResponse()
|
||||
* @param string $string String to sign
|
||||
* @return string
|
||||
*/
|
||||
private function __getSignature($string) {
|
||||
return base64_encode(hash_hmac('sha256', $string, $this->ses->getSecretKey(), true));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final class SimpleEmailServiceMessage {
|
||||
|
||||
// these are public for convenience only
|
||||
// these are not to be used outside of the SimpleEmailService class!
|
||||
public $to, $cc, $bcc, $replyto;
|
||||
public $from, $returnpath;
|
||||
public $subject, $messagetext, $messagehtml;
|
||||
public $subjectCharset, $messageTextCharset, $messageHtmlCharset;
|
||||
|
||||
function __construct() {
|
||||
$to = array();
|
||||
$cc = array();
|
||||
$bcc = array();
|
||||
$replyto = array();
|
||||
|
||||
$from = null;
|
||||
$returnpath = null;
|
||||
|
||||
$subject = null;
|
||||
$messagetext = null;
|
||||
$messagehtml = null;
|
||||
|
||||
$subjectCharset = null;
|
||||
$messageTextCharset = null;
|
||||
$messageHtmlCharset = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* addTo, addCC, addBCC, and addReplyTo have the following behavior:
|
||||
* If a single address is passed, it is appended to the current list of addresses.
|
||||
* If an array of addresses is passed, that array is merged into the current list.
|
||||
*/
|
||||
function addTo($to) {
|
||||
if(!is_array($to)) {
|
||||
$this->to[] = $to;
|
||||
}
|
||||
else {
|
||||
$this->to = array_merge($this->to, $to);
|
||||
}
|
||||
}
|
||||
|
||||
function addCC($cc) {
|
||||
if(!is_array($cc)) {
|
||||
$this->cc[] = $cc;
|
||||
}
|
||||
else {
|
||||
$this->cc = array_merge($this->cc, $cc);
|
||||
}
|
||||
}
|
||||
|
||||
function addBCC($bcc) {
|
||||
if(!is_array($bcc)) {
|
||||
$this->bcc[] = $bcc;
|
||||
}
|
||||
else {
|
||||
$this->bcc = array_merge($this->bcc, $bcc);
|
||||
}
|
||||
}
|
||||
|
||||
function addReplyTo($replyto) {
|
||||
if(!is_array($replyto)) {
|
||||
$this->replyto[] = $replyto;
|
||||
}
|
||||
else {
|
||||
$this->replyto = array_merge($this->replyto, $replyto);
|
||||
}
|
||||
}
|
||||
|
||||
function setFrom($from) {
|
||||
$this->from = $from;
|
||||
}
|
||||
|
||||
function setReturnPath($returnpath) {
|
||||
$this->returnpath = $returnpath;
|
||||
}
|
||||
|
||||
function setSubject($subject) {
|
||||
$this->subject = $subject;
|
||||
}
|
||||
|
||||
function setSubjectCharset($charset) {
|
||||
$this->subjectCharset = $charset;
|
||||
}
|
||||
|
||||
function setMessageFromString($text, $html = null) {
|
||||
$this->messagetext = $text;
|
||||
$this->messagehtml = $html;
|
||||
}
|
||||
|
||||
function setMessageFromFile($textfile, $htmlfile = null) {
|
||||
if(file_exists($textfile) && is_file($textfile) && is_readable($textfile)) {
|
||||
$this->messagetext = file_get_contents($textfile);
|
||||
}
|
||||
if(file_exists($htmlfile) && is_file($htmlfile) && is_readable($htmlfile)) {
|
||||
$this->messagehtml = file_get_contents($htmlfile);
|
||||
}
|
||||
}
|
||||
|
||||
function setMessageFromURL($texturl, $htmlurl = null) {
|
||||
$this->messagetext = file_get_contents($texturl);
|
||||
if($htmlurl !== null) {
|
||||
$this->messagehtml = file_get_contents($htmlurl);
|
||||
}
|
||||
}
|
||||
|
||||
function setMessageCharset($textCharset, $htmlCharset = null) {
|
||||
$this->messageTextCharset = $textCharset;
|
||||
$this->messageHtmlCharset = $htmlCharset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether the message object has sufficient information to submit a request to SES.
|
||||
* This does not guarantee the message will arrive, nor that the request will succeed;
|
||||
* instead, it makes sure that no required fields are missing.
|
||||
*
|
||||
* This is used internally before attempting a SendEmail or SendRawEmail request,
|
||||
* but it can be used outside of this file if verification is desired.
|
||||
* May be useful if e.g. the data is being populated from a form; developers can generally
|
||||
* use this function to verify completeness instead of writing custom logic.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function validate() {
|
||||
if(count($this->to) == 0)
|
||||
return false;
|
||||
if($this->from == null || strlen($this->from) == 0)
|
||||
return false;
|
||||
if($this->messagetext == null)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Thrown by SimpleEmailService when errors occur if you call
|
||||
* enableUseExceptions(true).
|
||||
*/
|
||||
final class SimpleEmailServiceException extends Exception {
|
||||
|
||||
}
|
@@ -9,13 +9,13 @@ return array(
|
||||
'names' => array(
|
||||
'conpherence.pkg.css' => '0e3cf785',
|
||||
'conpherence.pkg.js' => '020aebcf',
|
||||
'core.pkg.css' => '0fbedea0',
|
||||
'core.pkg.js' => '74ad315f',
|
||||
'core.pkg.css' => '704ac9c8',
|
||||
'core.pkg.js' => '0c06d967',
|
||||
'dark-console.pkg.js' => '187792c2',
|
||||
'differential.pkg.css' => '5c459f92',
|
||||
'differential.pkg.js' => '218fda21',
|
||||
'differential.pkg.js' => '5080baf4',
|
||||
'diffusion.pkg.css' => '42c75c37',
|
||||
'diffusion.pkg.js' => 'a98c0bf7',
|
||||
'diffusion.pkg.js' => '78c9885d',
|
||||
'maniphest.pkg.css' => '35995d6d',
|
||||
'maniphest.pkg.js' => 'c9308721',
|
||||
'rsrc/audio/basic/alert.mp3' => '17889334',
|
||||
@@ -73,7 +73,7 @@ return array(
|
||||
'rsrc/css/application/diffusion/diffusion-icons.css' => '23b31a1b',
|
||||
'rsrc/css/application/diffusion/diffusion-readme.css' => 'b68a76e4',
|
||||
'rsrc/css/application/diffusion/diffusion-repository.css' => 'b89e8c6c',
|
||||
'rsrc/css/application/diffusion/diffusion.css' => 'b54c77b0',
|
||||
'rsrc/css/application/diffusion/diffusion.css' => 'e46232d6',
|
||||
'rsrc/css/application/feed/feed.css' => 'd8b6e3f8',
|
||||
'rsrc/css/application/files/global-drag-and-drop.css' => '1d2713a4',
|
||||
'rsrc/css/application/flag/flag.css' => '2b77be8d',
|
||||
@@ -113,14 +113,18 @@ return array(
|
||||
'rsrc/css/application/slowvote/slowvote.css' => '1694baed',
|
||||
'rsrc/css/application/tokens/tokens.css' => 'ce5a50bd',
|
||||
'rsrc/css/application/uiexample/example.css' => 'b4795059',
|
||||
'rsrc/css/core/core.css' => '1b29ed61',
|
||||
'rsrc/css/core/remarkup.css' => 'c286eaef',
|
||||
'rsrc/css/core/core.css' => 'b3ebd90d',
|
||||
'rsrc/css/core/remarkup.css' => '24d48a73',
|
||||
'rsrc/css/core/syntax.css' => '548567f6',
|
||||
'rsrc/css/core/z-index.css' => 'ac3bfcd4',
|
||||
'rsrc/css/diviner/diviner-shared.css' => '4bd263b0',
|
||||
'rsrc/css/font/font-awesome.css' => '3883938a',
|
||||
'rsrc/css/font/font-lato.css' => '23631304',
|
||||
'rsrc/css/font/phui-font-icon-base.css' => '303c9b87',
|
||||
'rsrc/css/fuel/fuel-grid.css' => '66697240',
|
||||
'rsrc/css/fuel/fuel-handle-list.css' => '2c4cbeca',
|
||||
'rsrc/css/fuel/fuel-map.css' => 'd6e31510',
|
||||
'rsrc/css/fuel/fuel-menu.css' => '21f5d199',
|
||||
'rsrc/css/layout/phabricator-source-code-view.css' => '03d7ac28',
|
||||
'rsrc/css/phui/button/phui-button-bar.css' => 'a4aa75c4',
|
||||
'rsrc/css/phui/button/phui-button-simple.css' => '1ff278aa',
|
||||
@@ -133,11 +137,11 @@ return array(
|
||||
'rsrc/css/phui/object-item/phui-oi-color.css' => 'b517bfa0',
|
||||
'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => 'da15d3dc',
|
||||
'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '490e2e2e',
|
||||
'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'd7723ecc',
|
||||
'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'af98a277',
|
||||
'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => '6a30fa46',
|
||||
'rsrc/css/phui/phui-action-list.css' => '1b0085b2',
|
||||
'rsrc/css/phui/phui-action-panel.css' => '6c386cbf',
|
||||
'rsrc/css/phui/phui-badge.css' => '666e25ad',
|
||||
'rsrc/css/phui/phui-badge.css' => '96576409',
|
||||
'rsrc/css/phui/phui-basic-nav-view.css' => '56ebd66d',
|
||||
'rsrc/css/phui/phui-big-info-view.css' => '362ad37b',
|
||||
'rsrc/css/phui/phui-box.css' => '5ed3b8cb',
|
||||
@@ -188,6 +192,14 @@ return array(
|
||||
'rsrc/css/sprite-tokens.css' => 'f1896dc5',
|
||||
'rsrc/css/syntax/syntax-default.css' => '055fc231',
|
||||
'rsrc/custom/css/phabricator-welcome-page.css' => 'a641fcc9',
|
||||
'rsrc/custom/image/badges/badge_devfund_bronze.png' => '0f98ccf2',
|
||||
'rsrc/custom/image/badges/badge_devfund_diamond.png' => '196d8206',
|
||||
'rsrc/custom/image/badges/badge_devfund_gold.png' => 'd03e1722',
|
||||
'rsrc/custom/image/badges/badge_devfund_platinum.png' => '3e517840',
|
||||
'rsrc/custom/image/badges/badge_devfund_silver.png' => '73594dee',
|
||||
'rsrc/custom/image/badges/badge_devfund_titanium.png' => 'e30aa898',
|
||||
'rsrc/custom/image/badges/badge_sprite_fright.png' => '6f4b20e6',
|
||||
'rsrc/custom/image/badges/badge_studio.png' => 'ffbdcabb',
|
||||
'rsrc/custom/image/blender_logo.png' => '86dc8498',
|
||||
'rsrc/externals/d3/d3.min.js' => '9d068042',
|
||||
'rsrc/externals/font/fontawesome/fontawesome-webfont.eot' => '23f8c698',
|
||||
@@ -381,7 +393,7 @@ return array(
|
||||
'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => 'a2ab19be',
|
||||
'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '1e413dc9',
|
||||
'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => '0116d3e8',
|
||||
'rsrc/js/application/diff/DiffChangeset.js' => '39dcf2c3',
|
||||
'rsrc/js/application/diff/DiffChangeset.js' => '3b6e1fde',
|
||||
'rsrc/js/application/diff/DiffChangesetList.js' => 'cc2c5de5',
|
||||
'rsrc/js/application/diff/DiffInline.js' => '511a1315',
|
||||
'rsrc/js/application/diff/DiffPathView.js' => '8207abf9',
|
||||
@@ -392,7 +404,7 @@ return array(
|
||||
'rsrc/js/application/diffusion/ExternalEditorLinkEngine.js' => '48a8641f',
|
||||
'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'b7b73831',
|
||||
'rsrc/js/application/diffusion/behavior-commit-branches.js' => '4b671572',
|
||||
'rsrc/js/application/diffusion/behavior-commit-graph.js' => 'ef836bf2',
|
||||
'rsrc/js/application/diffusion/behavior-commit-graph.js' => 'ac10c917',
|
||||
'rsrc/js/application/diffusion/behavior-locate-file.js' => '87428eb2',
|
||||
'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'c715c123',
|
||||
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a',
|
||||
@@ -475,7 +487,7 @@ return array(
|
||||
'rsrc/js/core/behavior-choose-control.js' => '04f8a1e3',
|
||||
'rsrc/js/core/behavior-copy.js' => 'cf32921f',
|
||||
'rsrc/js/core/behavior-detect-timezone.js' => '78bc5d94',
|
||||
'rsrc/js/core/behavior-device.js' => '0cf79f45',
|
||||
'rsrc/js/core/behavior-device.js' => 'ac2b1e01',
|
||||
'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '7ad020a5',
|
||||
'rsrc/js/core/behavior-fancy-datepicker.js' => '956f3eeb',
|
||||
'rsrc/js/core/behavior-form.js' => '55d7b788',
|
||||
@@ -569,13 +581,17 @@ return array(
|
||||
'differential-revision-history-css' => '8aa3eac5',
|
||||
'differential-revision-list-css' => '93d2df7d',
|
||||
'differential-table-of-contents-css' => 'bba788b9',
|
||||
'diffusion-css' => 'b54c77b0',
|
||||
'diffusion-css' => 'e46232d6',
|
||||
'diffusion-icons-css' => '23b31a1b',
|
||||
'diffusion-readme-css' => 'b68a76e4',
|
||||
'diffusion-repository-css' => 'b89e8c6c',
|
||||
'diviner-shared-css' => '4bd263b0',
|
||||
'font-fontawesome' => '3883938a',
|
||||
'font-lato' => '23631304',
|
||||
'fuel-grid-css' => '66697240',
|
||||
'fuel-handle-list-css' => '2c4cbeca',
|
||||
'fuel-map-css' => 'd6e31510',
|
||||
'fuel-menu-css' => '21f5d199',
|
||||
'global-drag-and-drop-css' => '1d2713a4',
|
||||
'harbormaster-css' => '8dfe16b2',
|
||||
'herald-css' => '648d39e2',
|
||||
@@ -613,11 +629,11 @@ return array(
|
||||
'javelin-behavior-day-view' => '727a5a61',
|
||||
'javelin-behavior-desktop-notifications-control' => '070679fe',
|
||||
'javelin-behavior-detect-timezone' => '78bc5d94',
|
||||
'javelin-behavior-device' => '0cf79f45',
|
||||
'javelin-behavior-device' => 'ac2b1e01',
|
||||
'javelin-behavior-differential-diff-radios' => '925fe8cd',
|
||||
'javelin-behavior-differential-populate' => 'b86ef6c2',
|
||||
'javelin-behavior-diffusion-commit-branches' => '4b671572',
|
||||
'javelin-behavior-diffusion-commit-graph' => 'ef836bf2',
|
||||
'javelin-behavior-diffusion-commit-graph' => 'ac10c917',
|
||||
'javelin-behavior-diffusion-locate-file' => '87428eb2',
|
||||
'javelin-behavior-diffusion-pull-lastmodified' => 'c715c123',
|
||||
'javelin-behavior-document-engine' => '243d6c22',
|
||||
@@ -773,12 +789,12 @@ return array(
|
||||
'phabricator-busy' => '5202e831',
|
||||
'phabricator-chatlog-css' => 'abdc76ee',
|
||||
'phabricator-content-source-view-css' => 'cdf0d579',
|
||||
'phabricator-core-css' => '1b29ed61',
|
||||
'phabricator-core-css' => 'b3ebd90d',
|
||||
'phabricator-countdown-css' => 'bff8012f',
|
||||
'phabricator-darklog' => '3b869402',
|
||||
'phabricator-darkmessage' => '26cd4b73',
|
||||
'phabricator-dashboard-css' => '5a205b9d',
|
||||
'phabricator-diff-changeset' => '39dcf2c3',
|
||||
'phabricator-diff-changeset' => '3b6e1fde',
|
||||
'phabricator-diff-changeset-list' => 'cc2c5de5',
|
||||
'phabricator-diff-inline' => '511a1315',
|
||||
'phabricator-diff-path-view' => '8207abf9',
|
||||
@@ -800,7 +816,7 @@ return array(
|
||||
'phabricator-object-selector-css' => 'ee77366f',
|
||||
'phabricator-phtize' => '2f1db1ed',
|
||||
'phabricator-prefab' => '5793d835',
|
||||
'phabricator-remarkup-css' => 'c286eaef',
|
||||
'phabricator-remarkup-css' => '24d48a73',
|
||||
'phabricator-search-results-css' => '9ea70ace',
|
||||
'phabricator-shaped-request' => '995f5102',
|
||||
'phabricator-slowvote-css' => '1694baed',
|
||||
@@ -823,7 +839,7 @@ return array(
|
||||
'phrequent-css' => 'bd79cc67',
|
||||
'phriction-document-css' => '03380da0',
|
||||
'phui-action-panel-css' => '6c386cbf',
|
||||
'phui-badge-view-css' => '666e25ad',
|
||||
'phui-badge-view-css' => '96576409',
|
||||
'phui-basic-nav-view-css' => '56ebd66d',
|
||||
'phui-big-info-view-css' => '362ad37b',
|
||||
'phui-box-css' => '5ed3b8cb',
|
||||
@@ -869,7 +885,7 @@ return array(
|
||||
'phui-oi-color-css' => 'b517bfa0',
|
||||
'phui-oi-drag-ui-css' => 'da15d3dc',
|
||||
'phui-oi-flush-ui-css' => '490e2e2e',
|
||||
'phui-oi-list-view-css' => 'd7723ecc',
|
||||
'phui-oi-list-view-css' => 'af98a277',
|
||||
'phui-oi-simple-ui-css' => '6a30fa46',
|
||||
'phui-pager-css' => 'd022c7ad',
|
||||
'phui-pinboard-view-css' => '1f08f5d8',
|
||||
@@ -1005,13 +1021,6 @@ return array(
|
||||
'javelin-dom',
|
||||
'phabricator-draggable-list',
|
||||
),
|
||||
'0cf79f45' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
'javelin-dom',
|
||||
'javelin-vector',
|
||||
'javelin-install',
|
||||
),
|
||||
'0d2490ce' => array(
|
||||
'javelin-install',
|
||||
),
|
||||
@@ -1231,7 +1240,14 @@ return array(
|
||||
'trigger-rule',
|
||||
'trigger-rule-type',
|
||||
),
|
||||
'39dcf2c3' => array(
|
||||
'3ae89b20' => array(
|
||||
'phui-workcard-view-css',
|
||||
),
|
||||
'3b4899b0' => array(
|
||||
'javelin-behavior',
|
||||
'phabricator-prefab',
|
||||
),
|
||||
'3b6e1fde' => array(
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
'javelin-stratcom',
|
||||
@@ -1245,13 +1261,6 @@ return array(
|
||||
'phuix-button-view',
|
||||
'javelin-external-editor-link-engine',
|
||||
),
|
||||
'3ae89b20' => array(
|
||||
'phui-workcard-view-css',
|
||||
),
|
||||
'3b4899b0' => array(
|
||||
'javelin-behavior',
|
||||
'phabricator-prefab',
|
||||
),
|
||||
'3dc5ad43' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
@@ -1924,6 +1933,18 @@ return array(
|
||||
'javelin-dom',
|
||||
'phabricator-notification',
|
||||
),
|
||||
'ac10c917' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
),
|
||||
'ac2b1e01' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
'javelin-dom',
|
||||
'javelin-vector',
|
||||
'javelin-install',
|
||||
),
|
||||
'ad258e28' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
@@ -2176,11 +2197,6 @@ return array(
|
||||
'ee77366f' => array(
|
||||
'aphront-dialog-view-css',
|
||||
),
|
||||
'ef836bf2' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
),
|
||||
'f340a484' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
|
@@ -40,7 +40,8 @@ foreach ($lists as $list) {
|
||||
if (!$username_okay) {
|
||||
echo pht(
|
||||
'Failed to migrate mailing list "%s": unable to generate a unique '.
|
||||
'username for it.')."\n";
|
||||
'username for it.',
|
||||
$name)."\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@@ -66,6 +66,8 @@ class Configuration {
|
||||
$this->config_file = "$gitolite_root/conf/gitolite.conf";
|
||||
|
||||
$this->collectSystemPublicKeys();
|
||||
|
||||
$this->used_keys = array();
|
||||
$this->committers = array();
|
||||
|
||||
if (!file_exists($this->config_file)) {
|
||||
@@ -121,8 +123,10 @@ class Configuration {
|
||||
$variable_name = trim($parts[0]);
|
||||
if (array_key_exists($variable_name, $this->committers)) {
|
||||
$system_committers = $this->getNonPhabtricatorUsers($parts[1]);
|
||||
$committers = implode(' ', array_merge(
|
||||
$system_committers, $this->committers[$variable_name]));
|
||||
$all_committers = array_merge(
|
||||
$system_committers, $this->committers[$variable_name]);
|
||||
$unique_committers = array_unique($all_committers);
|
||||
$committers = implode(' ', $unique_committers);
|
||||
$line = "$variable_name = $committers";
|
||||
}
|
||||
}
|
||||
@@ -339,7 +343,7 @@ function handleCustomPolicy($config, $policy) {
|
||||
|
||||
$policy_config_user_names = array();
|
||||
$rule_type = $rule['rule'];
|
||||
if ($rule_type == 'PhabricatorPolicyRuleUsers') {
|
||||
if ($rule_type == 'PhabricatorUsersPolicyRule') {
|
||||
$policy_config_user_names =
|
||||
handleUsersPolicyRule($config, $rule);
|
||||
} else if ($rule_type == 'PhabricatorProjectsPolicyRule') {
|
||||
|
@@ -211,21 +211,29 @@ try {
|
||||
->setUniqueMethod('getName')
|
||||
->execute();
|
||||
|
||||
$command_list = array_keys($workflows);
|
||||
$command_list = implode(', ', $command_list);
|
||||
|
||||
$error_lines = array();
|
||||
$error_lines[] = pht('Welcome to Phabricator.');
|
||||
$error_lines[] = pht(
|
||||
'You are logged in as %s.',
|
||||
$user_name);
|
||||
|
||||
if (!$original_argv) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
"Welcome to Phabricator.\n\n".
|
||||
"You are logged in as %s.\n\n".
|
||||
"You haven't specified a command to run. This means you're requesting ".
|
||||
"an interactive shell, but Phabricator does not provide an ".
|
||||
"interactive shell over SSH.\n\n".
|
||||
"Usually, you should run a command like `%s` or `%s` ".
|
||||
"rather than connecting directly with SSH.\n\n".
|
||||
"Supported commands are: %s.",
|
||||
$user_name,
|
||||
'git clone',
|
||||
'hg push',
|
||||
implode(', ', array_keys($workflows))));
|
||||
$error_lines[] = pht(
|
||||
'You have not specified a command to run. This means you are requesting '.
|
||||
'an interactive shell, but Phabricator does not provide interactive '.
|
||||
'shells over SSH.');
|
||||
$error_lines[] = pht(
|
||||
'(Usually, you should run a command like "git clone" or "hg push" '.
|
||||
'instead of connecting directly with SSH.)');
|
||||
$error_lines[] = pht(
|
||||
'Supported commands are: %s.',
|
||||
$command_list);
|
||||
|
||||
$error_lines = implode("\n\n", $error_lines);
|
||||
throw new PhutilArgumentUsageException($error_lines);
|
||||
}
|
||||
|
||||
$log_argv = implode(' ', $original_argv);
|
||||
@@ -247,7 +255,20 @@ try {
|
||||
$parsed_args = new PhutilArgumentParser($parseable_argv);
|
||||
|
||||
if (empty($workflows[$command])) {
|
||||
throw new Exception(pht('Invalid command.'));
|
||||
$error_lines[] = pht(
|
||||
'You have specified the command "%s", but that command is not '.
|
||||
'supported by Phabricator. As received by Phabricator, your entire '.
|
||||
'argument list was:',
|
||||
$command);
|
||||
|
||||
$error_lines[] = csprintf(' $ ssh ... -- %Ls', $parseable_argv);
|
||||
|
||||
$error_lines[] = pht(
|
||||
'Supported commands are: %s.',
|
||||
$command_list);
|
||||
|
||||
$error_lines = implode("\n\n", $error_lines);
|
||||
throw new PhutilArgumentUsageException($error_lines);
|
||||
}
|
||||
|
||||
$workflow = $parsed_args->parseWorkflows($workflows);
|
||||
|
@@ -770,6 +770,7 @@ phutil_register_library_map(array(
|
||||
'DiffusionCommitEditEngine' => 'applications/diffusion/editor/DiffusionCommitEditEngine.php',
|
||||
'DiffusionCommitFerretEngine' => 'applications/repository/search/DiffusionCommitFerretEngine.php',
|
||||
'DiffusionCommitFulltextEngine' => 'applications/repository/search/DiffusionCommitFulltextEngine.php',
|
||||
'DiffusionCommitGraphView' => 'applications/diffusion/view/DiffusionCommitGraphView.php',
|
||||
'DiffusionCommitHasPackageEdgeType' => 'applications/diffusion/edge/DiffusionCommitHasPackageEdgeType.php',
|
||||
'DiffusionCommitHasRevisionEdgeType' => 'applications/diffusion/edge/DiffusionCommitHasRevisionEdgeType.php',
|
||||
'DiffusionCommitHasRevisionRelationship' => 'applications/diffusion/relationships/DiffusionCommitHasRevisionRelationship.php',
|
||||
@@ -782,7 +783,6 @@ phutil_register_library_map(array(
|
||||
'DiffusionCommitHookEngine' => 'applications/diffusion/engine/DiffusionCommitHookEngine.php',
|
||||
'DiffusionCommitHookRejectException' => 'applications/diffusion/exception/DiffusionCommitHookRejectException.php',
|
||||
'DiffusionCommitListController' => 'applications/diffusion/controller/DiffusionCommitListController.php',
|
||||
'DiffusionCommitListView' => 'applications/diffusion/view/DiffusionCommitListView.php',
|
||||
'DiffusionCommitMergeHeraldField' => 'applications/diffusion/herald/DiffusionCommitMergeHeraldField.php',
|
||||
'DiffusionCommitMessageHeraldField' => 'applications/diffusion/herald/DiffusionCommitMessageHeraldField.php',
|
||||
'DiffusionCommitPackageAuditHeraldField' => 'applications/diffusion/herald/DiffusionCommitPackageAuditHeraldField.php',
|
||||
@@ -861,12 +861,8 @@ phutil_register_library_map(array(
|
||||
'DiffusionGitWireProtocolCapabilities' => 'applications/diffusion/protocol/DiffusionGitWireProtocolCapabilities.php',
|
||||
'DiffusionGitWireProtocolRef' => 'applications/diffusion/protocol/DiffusionGitWireProtocolRef.php',
|
||||
'DiffusionGitWireProtocolRefList' => 'applications/diffusion/protocol/DiffusionGitWireProtocolRefList.php',
|
||||
'DiffusionGraphController' => 'applications/diffusion/controller/DiffusionGraphController.php',
|
||||
'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php',
|
||||
'DiffusionHistoryListView' => 'applications/diffusion/view/DiffusionHistoryListView.php',
|
||||
'DiffusionHistoryQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php',
|
||||
'DiffusionHistoryTableView' => 'applications/diffusion/view/DiffusionHistoryTableView.php',
|
||||
'DiffusionHistoryView' => 'applications/diffusion/view/DiffusionHistoryView.php',
|
||||
'DiffusionHovercardEngineExtension' => 'applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php',
|
||||
'DiffusionIdentityAssigneeDatasource' => 'applications/diffusion/typeahead/DiffusionIdentityAssigneeDatasource.php',
|
||||
'DiffusionIdentityAssigneeEditField' => 'applications/diffusion/editfield/DiffusionIdentityAssigneeEditField.php',
|
||||
@@ -877,6 +873,8 @@ phutil_register_library_map(array(
|
||||
'DiffusionIdentityViewController' => 'applications/diffusion/controller/DiffusionIdentityViewController.php',
|
||||
'DiffusionInlineCommentController' => 'applications/diffusion/controller/DiffusionInlineCommentController.php',
|
||||
'DiffusionInternalAncestorsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionInternalAncestorsConduitAPIMethod.php',
|
||||
'DiffusionInternalCommitSearchConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionInternalCommitSearchConduitAPIMethod.php',
|
||||
'DiffusionInternalCommitSearchEngine' => 'applications/audit/query/DiffusionInternalCommitSearchEngine.php',
|
||||
'DiffusionInternalGitRawDiffQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionInternalGitRawDiffQueryConduitAPIMethod.php',
|
||||
'DiffusionLastModifiedController' => 'applications/diffusion/controller/DiffusionLastModifiedController.php',
|
||||
'DiffusionLastModifiedQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionLastModifiedQueryConduitAPIMethod.php',
|
||||
@@ -1063,7 +1061,6 @@ phutil_register_library_map(array(
|
||||
'DiffusionSyncLogSearchEngine' => 'applications/diffusion/query/DiffusionSyncLogSearchEngine.php',
|
||||
'DiffusionTagListController' => 'applications/diffusion/controller/DiffusionTagListController.php',
|
||||
'DiffusionTagListView' => 'applications/diffusion/view/DiffusionTagListView.php',
|
||||
'DiffusionTagTableView' => 'applications/diffusion/view/DiffusionTagTableView.php',
|
||||
'DiffusionTaggedRepositoriesFunctionDatasource' => 'applications/diffusion/typeahead/DiffusionTaggedRepositoriesFunctionDatasource.php',
|
||||
'DiffusionTagsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionTagsQueryConduitAPIMethod.php',
|
||||
'DiffusionURIEditConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionURIEditConduitAPIMethod.php',
|
||||
@@ -1310,6 +1307,17 @@ phutil_register_library_map(array(
|
||||
'FlagDeleteConduitAPIMethod' => 'applications/flag/conduit/FlagDeleteConduitAPIMethod.php',
|
||||
'FlagEditConduitAPIMethod' => 'applications/flag/conduit/FlagEditConduitAPIMethod.php',
|
||||
'FlagQueryConduitAPIMethod' => 'applications/flag/conduit/FlagQueryConduitAPIMethod.php',
|
||||
'FuelComponentView' => 'view/fuel/FuelComponentView.php',
|
||||
'FuelGridCellView' => 'view/fuel/FuelGridCellView.php',
|
||||
'FuelGridRowView' => 'view/fuel/FuelGridRowView.php',
|
||||
'FuelGridView' => 'view/fuel/FuelGridView.php',
|
||||
'FuelHandleListItemView' => 'view/fuel/FuelHandleListItemView.php',
|
||||
'FuelHandleListView' => 'view/fuel/FuelHandleListView.php',
|
||||
'FuelMapItemView' => 'view/fuel/FuelMapItemView.php',
|
||||
'FuelMapView' => 'view/fuel/FuelMapView.php',
|
||||
'FuelMenuItemView' => 'view/fuel/FuelMenuItemView.php',
|
||||
'FuelMenuView' => 'view/fuel/FuelMenuView.php',
|
||||
'FuelView' => 'view/fuel/FuelView.php',
|
||||
'FundBacker' => 'applications/fund/storage/FundBacker.php',
|
||||
'FundBackerCart' => 'applications/fund/phortune/FundBackerCart.php',
|
||||
'FundBackerEditor' => 'applications/fund/editor/FundBackerEditor.php',
|
||||
@@ -1422,12 +1430,16 @@ phutil_register_library_map(array(
|
||||
'HarbormasterBuildStep' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStep.php',
|
||||
'HarbormasterBuildStepCoreCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php',
|
||||
'HarbormasterBuildStepCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCustomField.php',
|
||||
'HarbormasterBuildStepEditAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildStepEditAPIMethod.php',
|
||||
'HarbormasterBuildStepEditEngine' => 'applications/harbormaster/editor/HarbormasterBuildStepEditEngine.php',
|
||||
'HarbormasterBuildStepEditor' => 'applications/harbormaster/editor/HarbormasterBuildStepEditor.php',
|
||||
'HarbormasterBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterBuildStepGroup.php',
|
||||
'HarbormasterBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterBuildStepImplementation.php',
|
||||
'HarbormasterBuildStepImplementationTestCase' => 'applications/harbormaster/step/__tests__/HarbormasterBuildStepImplementationTestCase.php',
|
||||
'HarbormasterBuildStepPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildStepPHIDType.php',
|
||||
'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php',
|
||||
'HarbormasterBuildStepSearchAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildStepSearchAPIMethod.php',
|
||||
'HarbormasterBuildStepSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildStepSearchEngine.php',
|
||||
'HarbormasterBuildStepTransaction' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStepTransaction.php',
|
||||
'HarbormasterBuildStepTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildStepTransactionQuery.php',
|
||||
'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php',
|
||||
@@ -1549,6 +1561,7 @@ phutil_register_library_map(array(
|
||||
'HeraldBuildableState' => 'applications/herald/state/HeraldBuildableState.php',
|
||||
'HeraldCallWebhookAction' => 'applications/herald/action/HeraldCallWebhookAction.php',
|
||||
'HeraldCommentAction' => 'applications/herald/action/HeraldCommentAction.php',
|
||||
'HeraldCommentContentField' => 'applications/herald/field/HeraldCommentContentField.php',
|
||||
'HeraldCommitAdapter' => 'applications/diffusion/herald/HeraldCommitAdapter.php',
|
||||
'HeraldCondition' => 'applications/herald/storage/HeraldCondition.php',
|
||||
'HeraldConditionTranscript' => 'applications/herald/storage/transcript/HeraldConditionTranscript.php',
|
||||
@@ -2166,12 +2179,14 @@ phutil_register_library_map(array(
|
||||
'PasteSearchConduitAPIMethod' => 'applications/paste/conduit/PasteSearchConduitAPIMethod.php',
|
||||
'PeopleBrowseUserDirectoryCapability' => 'applications/people/capability/PeopleBrowseUserDirectoryCapability.php',
|
||||
'PeopleCreateUsersCapability' => 'applications/people/capability/PeopleCreateUsersCapability.php',
|
||||
'PeopleDisableSpamUsersCapability' => 'applications/people/capability/PeopleDisableSpamUsersCapability.php',
|
||||
'PeopleDisableUsersCapability' => 'applications/people/capability/PeopleDisableUsersCapability.php',
|
||||
'PeopleHovercardEngineExtension' => 'applications/people/engineextension/PeopleHovercardEngineExtension.php',
|
||||
'PeopleMainMenuBarExtension' => 'applications/people/engineextension/PeopleMainMenuBarExtension.php',
|
||||
'PeopleUserLogGarbageCollector' => 'applications/people/garbagecollector/PeopleUserLogGarbageCollector.php',
|
||||
'Phabricator404Controller' => 'applications/base/controller/Phabricator404Controller.php',
|
||||
'PhabricatorAWSConfigOptions' => 'applications/config/option/PhabricatorAWSConfigOptions.php',
|
||||
'PhabricatorAWSSESFuture' => 'applications/metamta/future/PhabricatorAWSSESFuture.php',
|
||||
'PhabricatorAccessControlTestCase' => 'applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php',
|
||||
'PhabricatorAccessLog' => 'infrastructure/log/PhabricatorAccessLog.php',
|
||||
'PhabricatorAccessLogConfigOptions' => 'applications/config/option/PhabricatorAccessLogConfigOptions.php',
|
||||
@@ -2281,7 +2296,6 @@ phutil_register_library_map(array(
|
||||
'PhabricatorAuditController' => 'applications/audit/controller/PhabricatorAuditController.php',
|
||||
'PhabricatorAuditEditor' => 'applications/audit/editor/PhabricatorAuditEditor.php',
|
||||
'PhabricatorAuditInlineComment' => 'applications/audit/storage/PhabricatorAuditInlineComment.php',
|
||||
'PhabricatorAuditListView' => 'applications/audit/view/PhabricatorAuditListView.php',
|
||||
'PhabricatorAuditMailReceiver' => 'applications/audit/mail/PhabricatorAuditMailReceiver.php',
|
||||
'PhabricatorAuditManagementDeleteWorkflow' => 'applications/audit/management/PhabricatorAuditManagementDeleteWorkflow.php',
|
||||
'PhabricatorAuditManagementWorkflow' => 'applications/audit/management/PhabricatorAuditManagementWorkflow.php',
|
||||
@@ -4167,6 +4181,7 @@ phutil_register_library_map(array(
|
||||
'PhabricatorPeopleRenameController' => 'applications/people/controller/PhabricatorPeopleRenameController.php',
|
||||
'PhabricatorPeopleRevisionsProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleRevisionsProfileMenuItem.php',
|
||||
'PhabricatorPeopleSearchEngine' => 'applications/people/query/PhabricatorPeopleSearchEngine.php',
|
||||
'PhabricatorPeopleSpamController' => 'applications/people/controller/PhabricatorPeopleSpamController.php',
|
||||
'PhabricatorPeopleTasksProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleTasksProfileMenuItem.php',
|
||||
'PhabricatorPeopleTestDataGenerator' => 'applications/people/lipsum/PhabricatorPeopleTestDataGenerator.php',
|
||||
'PhabricatorPeopleTransactionQuery' => 'applications/people/query/PhabricatorPeopleTransactionQuery.php',
|
||||
@@ -4582,7 +4597,6 @@ phutil_register_library_map(array(
|
||||
'PhabricatorRepositoryManagementImportingWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php',
|
||||
'PhabricatorRepositoryManagementListPathsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListPathsWorkflow.php',
|
||||
'PhabricatorRepositoryManagementListWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php',
|
||||
'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementLookupUsersWorkflow.php',
|
||||
'PhabricatorRepositoryManagementMaintenanceWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMaintenanceWorkflow.php',
|
||||
'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkImportedWorkflow.php',
|
||||
'PhabricatorRepositoryManagementMarkReachableWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkReachableWorkflow.php',
|
||||
@@ -4655,8 +4669,6 @@ phutil_register_library_map(array(
|
||||
'PhabricatorRepositoryType' => 'applications/repository/constants/PhabricatorRepositoryType.php',
|
||||
'PhabricatorRepositoryURI' => 'applications/repository/storage/PhabricatorRepositoryURI.php',
|
||||
'PhabricatorRepositoryURIIndex' => 'applications/repository/storage/PhabricatorRepositoryURIIndex.php',
|
||||
'PhabricatorRepositoryURINormalizer' => 'applications/repository/data/PhabricatorRepositoryURINormalizer.php',
|
||||
'PhabricatorRepositoryURINormalizerTestCase' => 'applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php',
|
||||
'PhabricatorRepositoryURIPHIDType' => 'applications/repository/phid/PhabricatorRepositoryURIPHIDType.php',
|
||||
'PhabricatorRepositoryURIQuery' => 'applications/repository/query/PhabricatorRepositoryURIQuery.php',
|
||||
'PhabricatorRepositoryURITestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php',
|
||||
@@ -5059,6 +5071,7 @@ phutil_register_library_map(array(
|
||||
'PhabricatorUserCustomFieldNumericIndex' => 'applications/people/storage/PhabricatorUserCustomFieldNumericIndex.php',
|
||||
'PhabricatorUserCustomFieldStringIndex' => 'applications/people/storage/PhabricatorUserCustomFieldStringIndex.php',
|
||||
'PhabricatorUserDAO' => 'applications/people/storage/PhabricatorUserDAO.php',
|
||||
'PhabricatorUserDisableSpamTransaction' => 'applications/people/xaction/PhabricatorUserDisableSpamTransaction.php',
|
||||
'PhabricatorUserDisableTransaction' => 'applications/people/xaction/PhabricatorUserDisableTransaction.php',
|
||||
'PhabricatorUserEditEngine' => 'applications/people/editor/PhabricatorUserEditEngine.php',
|
||||
'PhabricatorUserEditor' => 'applications/people/editor/PhabricatorUserEditor.php',
|
||||
@@ -5959,9 +5972,11 @@ phutil_register_library_map(array(
|
||||
'celerity_get_resource_uri' => 'applications/celerity/api.php',
|
||||
'hsprintf' => 'infrastructure/markup/render.php',
|
||||
'javelin_tag' => 'infrastructure/javelin/markup.php',
|
||||
'phabricator_absolute_datetime' => 'view/viewutils.php',
|
||||
'phabricator_date' => 'view/viewutils.php',
|
||||
'phabricator_datetime' => 'view/viewutils.php',
|
||||
'phabricator_datetimezone' => 'view/viewutils.php',
|
||||
'phabricator_dual_datetime' => 'view/viewutils.php',
|
||||
'phabricator_form' => 'infrastructure/javelin/markup.php',
|
||||
'phabricator_format_local_time' => 'view/viewutils.php',
|
||||
'phabricator_relative_date' => 'view/viewutils.php',
|
||||
@@ -6867,6 +6882,7 @@ phutil_register_library_map(array(
|
||||
'DiffusionCommitEditEngine' => 'PhabricatorEditEngine',
|
||||
'DiffusionCommitFerretEngine' => 'PhabricatorFerretEngine',
|
||||
'DiffusionCommitFulltextEngine' => 'PhabricatorFulltextEngine',
|
||||
'DiffusionCommitGraphView' => 'DiffusionView',
|
||||
'DiffusionCommitHasPackageEdgeType' => 'PhabricatorEdgeType',
|
||||
'DiffusionCommitHasRevisionEdgeType' => 'PhabricatorEdgeType',
|
||||
'DiffusionCommitHasRevisionRelationship' => 'DiffusionCommitRelationship',
|
||||
@@ -6879,7 +6895,6 @@ phutil_register_library_map(array(
|
||||
'DiffusionCommitHookEngine' => 'Phobject',
|
||||
'DiffusionCommitHookRejectException' => 'Exception',
|
||||
'DiffusionCommitListController' => 'DiffusionController',
|
||||
'DiffusionCommitListView' => 'AphrontView',
|
||||
'DiffusionCommitMergeHeraldField' => 'DiffusionCommitHeraldField',
|
||||
'DiffusionCommitMessageHeraldField' => 'DiffusionCommitHeraldField',
|
||||
'DiffusionCommitPackageAuditHeraldField' => 'DiffusionCommitHeraldField',
|
||||
@@ -6961,12 +6976,8 @@ phutil_register_library_map(array(
|
||||
'DiffusionGitWireProtocolCapabilities' => 'Phobject',
|
||||
'DiffusionGitWireProtocolRef' => 'Phobject',
|
||||
'DiffusionGitWireProtocolRefList' => 'Phobject',
|
||||
'DiffusionGraphController' => 'DiffusionController',
|
||||
'DiffusionHistoryController' => 'DiffusionController',
|
||||
'DiffusionHistoryListView' => 'DiffusionHistoryView',
|
||||
'DiffusionHistoryQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
||||
'DiffusionHistoryTableView' => 'DiffusionHistoryView',
|
||||
'DiffusionHistoryView' => 'DiffusionView',
|
||||
'DiffusionHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
|
||||
'DiffusionIdentityAssigneeDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'DiffusionIdentityAssigneeEditField' => 'PhabricatorTokenizerEditField',
|
||||
@@ -6977,6 +6988,8 @@ phutil_register_library_map(array(
|
||||
'DiffusionIdentityViewController' => 'DiffusionController',
|
||||
'DiffusionInlineCommentController' => 'PhabricatorInlineCommentController',
|
||||
'DiffusionInternalAncestorsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
||||
'DiffusionInternalCommitSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
|
||||
'DiffusionInternalCommitSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'DiffusionInternalGitRawDiffQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
||||
'DiffusionLastModifiedController' => 'DiffusionController',
|
||||
'DiffusionLastModifiedQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
||||
@@ -7162,7 +7175,6 @@ phutil_register_library_map(array(
|
||||
'DiffusionSyncLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'DiffusionTagListController' => 'DiffusionController',
|
||||
'DiffusionTagListView' => 'DiffusionView',
|
||||
'DiffusionTagTableView' => 'DiffusionView',
|
||||
'DiffusionTaggedRepositoriesFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'DiffusionTagsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
||||
'DiffusionURIEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
|
||||
@@ -7455,6 +7467,17 @@ phutil_register_library_map(array(
|
||||
'FlagDeleteConduitAPIMethod' => 'FlagConduitAPIMethod',
|
||||
'FlagEditConduitAPIMethod' => 'FlagConduitAPIMethod',
|
||||
'FlagQueryConduitAPIMethod' => 'FlagConduitAPIMethod',
|
||||
'FuelComponentView' => 'FuelView',
|
||||
'FuelGridCellView' => 'FuelComponentView',
|
||||
'FuelGridRowView' => 'FuelView',
|
||||
'FuelGridView' => 'FuelComponentView',
|
||||
'FuelHandleListItemView' => 'FuelView',
|
||||
'FuelHandleListView' => 'FuelComponentView',
|
||||
'FuelMapItemView' => 'AphrontView',
|
||||
'FuelMapView' => 'FuelComponentView',
|
||||
'FuelMenuItemView' => 'FuelView',
|
||||
'FuelMenuView' => 'FuelComponentView',
|
||||
'FuelView' => 'AphrontView',
|
||||
'FundBacker' => array(
|
||||
'FundDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
@@ -7614,18 +7637,23 @@ phutil_register_library_map(array(
|
||||
'PhabricatorApplicationTransactionInterface',
|
||||
'PhabricatorPolicyInterface',
|
||||
'PhabricatorCustomFieldInterface',
|
||||
'PhabricatorConduitResultInterface',
|
||||
),
|
||||
'HarbormasterBuildStepCoreCustomField' => array(
|
||||
'HarbormasterBuildStepCustomField',
|
||||
'PhabricatorStandardCustomFieldInterface',
|
||||
),
|
||||
'HarbormasterBuildStepCustomField' => 'PhabricatorCustomField',
|
||||
'HarbormasterBuildStepEditAPIMethod' => 'PhabricatorEditEngineAPIMethod',
|
||||
'HarbormasterBuildStepEditEngine' => 'PhabricatorEditEngine',
|
||||
'HarbormasterBuildStepEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'HarbormasterBuildStepGroup' => 'Phobject',
|
||||
'HarbormasterBuildStepImplementation' => 'Phobject',
|
||||
'HarbormasterBuildStepImplementationTestCase' => 'PhabricatorTestCase',
|
||||
'HarbormasterBuildStepPHIDType' => 'PhabricatorPHIDType',
|
||||
'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'HarbormasterBuildStepSearchAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
|
||||
'HarbormasterBuildStepSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'HarbormasterBuildStepTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'HarbormasterBuildStepTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'HarbormasterBuildTarget' => array(
|
||||
@@ -7758,6 +7786,7 @@ phutil_register_library_map(array(
|
||||
'HeraldBuildableState' => 'HeraldState',
|
||||
'HeraldCallWebhookAction' => 'HeraldAction',
|
||||
'HeraldCommentAction' => 'HeraldAction',
|
||||
'HeraldCommentContentField' => 'HeraldField',
|
||||
'HeraldCommitAdapter' => array(
|
||||
'HeraldAdapter',
|
||||
'HarbormasterBuildableAdapterInterface',
|
||||
@@ -8473,12 +8502,14 @@ phutil_register_library_map(array(
|
||||
'PasteSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
|
||||
'PeopleBrowseUserDirectoryCapability' => 'PhabricatorPolicyCapability',
|
||||
'PeopleCreateUsersCapability' => 'PhabricatorPolicyCapability',
|
||||
'PeopleDisableSpamUsersCapability' => 'PhabricatorPolicyCapability',
|
||||
'PeopleDisableUsersCapability' => 'PhabricatorPolicyCapability',
|
||||
'PeopleHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
|
||||
'PeopleMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension',
|
||||
'PeopleUserLogGarbageCollector' => 'PhabricatorGarbageCollector',
|
||||
'Phabricator404Controller' => 'PhabricatorController',
|
||||
'PhabricatorAWSConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorAWSSESFuture' => 'PhutilAWSFuture',
|
||||
'PhabricatorAccessControlTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorAccessLog' => 'Phobject',
|
||||
'PhabricatorAccessLogConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
@@ -8603,7 +8634,6 @@ phutil_register_library_map(array(
|
||||
'PhabricatorAuditController' => 'PhabricatorController',
|
||||
'PhabricatorAuditEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'PhabricatorAuditInlineComment' => 'PhabricatorInlineComment',
|
||||
'PhabricatorAuditListView' => 'AphrontView',
|
||||
'PhabricatorAuditMailReceiver' => 'PhabricatorObjectMailReceiver',
|
||||
'PhabricatorAuditManagementDeleteWorkflow' => 'PhabricatorAuditManagementWorkflow',
|
||||
'PhabricatorAuditManagementWorkflow' => 'PhabricatorManagementWorkflow',
|
||||
@@ -10790,6 +10820,7 @@ phutil_register_library_map(array(
|
||||
'PhabricatorPeopleRenameController' => 'PhabricatorPeopleController',
|
||||
'PhabricatorPeopleRevisionsProfileMenuItem' => 'PhabricatorProfileMenuItem',
|
||||
'PhabricatorPeopleSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'PhabricatorPeopleSpamController' => 'PhabricatorPeopleController',
|
||||
'PhabricatorPeopleTasksProfileMenuItem' => 'PhabricatorProfileMenuItem',
|
||||
'PhabricatorPeopleTestDataGenerator' => 'PhabricatorTestDataGenerator',
|
||||
'PhabricatorPeopleTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
@@ -11315,7 +11346,6 @@ phutil_register_library_map(array(
|
||||
'PhabricatorRepositoryManagementImportingWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementListPathsWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementListWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementMaintenanceWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
'PhabricatorRepositoryManagementMarkReachableWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
|
||||
@@ -11412,8 +11442,6 @@ phutil_register_library_map(array(
|
||||
'PhabricatorConduitResultInterface',
|
||||
),
|
||||
'PhabricatorRepositoryURIIndex' => 'PhabricatorRepositoryDAO',
|
||||
'PhabricatorRepositoryURINormalizer' => 'Phobject',
|
||||
'PhabricatorRepositoryURINormalizerTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorRepositoryURIPHIDType' => 'PhabricatorPHIDType',
|
||||
'PhabricatorRepositoryURIQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase',
|
||||
@@ -11860,6 +11888,7 @@ phutil_register_library_map(array(
|
||||
'PhabricatorUserCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
|
||||
'PhabricatorUserCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage',
|
||||
'PhabricatorUserDAO' => 'PhabricatorLiskDAO',
|
||||
'PhabricatorUserDisableSpamTransaction' => 'PhabricatorUserTransactionType',
|
||||
'PhabricatorUserDisableTransaction' => 'PhabricatorUserTransactionType',
|
||||
'PhabricatorUserEditEngine' => 'PhabricatorEditEngine',
|
||||
'PhabricatorUserEditor' => 'PhabricatorEditor',
|
||||
|
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
final class DiffusionInternalCommitSearchEngine
|
||||
extends PhabricatorApplicationSearchEngine {
|
||||
|
||||
public function getResultTypeDescription() {
|
||||
return pht('Diffusion Raw Commits');
|
||||
}
|
||||
|
||||
public function getApplicationClassName() {
|
||||
return 'PhabricatorDiffusionApplication';
|
||||
}
|
||||
|
||||
public function newQuery() {
|
||||
return new DiffusionCommitQuery();
|
||||
}
|
||||
|
||||
protected function buildQueryFromParameters(array $map) {
|
||||
$query = $this->newQuery();
|
||||
|
||||
if ($map['repositoryPHIDs']) {
|
||||
$query->withRepositoryPHIDs($map['repositoryPHIDs']);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function buildCustomSearchFields() {
|
||||
return array(
|
||||
id(new PhabricatorSearchDatasourceField())
|
||||
->setLabel(pht('Repositories'))
|
||||
->setKey('repositoryPHIDs')
|
||||
->setDatasource(new DiffusionRepositoryFunctionDatasource())
|
||||
->setDescription(pht('Find commits in particular repositories.')),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getURI($path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function renderResultList(
|
||||
array $commits,
|
||||
PhabricatorSavedQuery $query,
|
||||
array $handles) {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getObjectWireFieldsForConduit(
|
||||
$object,
|
||||
array $field_extensions,
|
||||
array $extension_data) {
|
||||
|
||||
$commit = $object;
|
||||
$viewer = $this->requireViewer();
|
||||
|
||||
$repository = $commit->getRepository();
|
||||
$identifier = $commit->getCommitIdentifier();
|
||||
|
||||
id(new DiffusionRepositoryClusterEngine())
|
||||
->setViewer($viewer)
|
||||
->setRepository($repository)
|
||||
->synchronizeWorkingCopyBeforeRead();
|
||||
|
||||
$ref = id(new DiffusionLowLevelCommitQuery())
|
||||
->setRepository($repository)
|
||||
->withIdentifier($identifier)
|
||||
->execute();
|
||||
|
||||
return array(
|
||||
'ref' => $ref->newDictionary(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@@ -221,9 +221,9 @@ final class PhabricatorCommitSearchEngine
|
||||
|
||||
$bucket = $this->getResultBucket($query);
|
||||
|
||||
$template = id(new PhabricatorAuditListView())
|
||||
$template = id(new DiffusionCommitGraphView())
|
||||
->setViewer($viewer)
|
||||
->setShowDrafts(true);
|
||||
->setShowAuditors(true);
|
||||
|
||||
$views = array();
|
||||
if ($bucket) {
|
||||
@@ -235,37 +235,31 @@ final class PhabricatorCommitSearchEngine
|
||||
foreach ($groups as $group) {
|
||||
// Don't show groups in Dashboard Panels
|
||||
if ($group->getObjects() || !$this->isPanelContext()) {
|
||||
$views[] = id(clone $template)
|
||||
$item_list = id(clone $template)
|
||||
->setCommits($group->getObjects())
|
||||
->newObjectItemListView();
|
||||
|
||||
$views[] = $item_list
|
||||
->setHeader($group->getName())
|
||||
->setNoDataString($group->getNoDataString())
|
||||
->setCommits($group->getObjects());
|
||||
->setNoDataString($group->getNoDataString());
|
||||
}
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
$this->addError($ex->getMessage());
|
||||
}
|
||||
} else {
|
||||
$views[] = id(clone $template)
|
||||
->setCommits($commits)
|
||||
->setNoDataString(pht('No commits found.'));
|
||||
}
|
||||
|
||||
if (!$views) {
|
||||
$views[] = id(new PhabricatorAuditListView())
|
||||
->setViewer($viewer)
|
||||
$item_list = id(clone $template)
|
||||
->setCommits($commits)
|
||||
->newObjectItemListView();
|
||||
|
||||
$views[] = $item_list
|
||||
->setNoDataString(pht('No commits found.'));
|
||||
}
|
||||
|
||||
if (count($views) == 1) {
|
||||
$list = head($views)->buildList();
|
||||
} else {
|
||||
$list = $views;
|
||||
}
|
||||
|
||||
$result = new PhabricatorApplicationSearchResultView();
|
||||
$result->setContent($list);
|
||||
|
||||
return $result;
|
||||
return id(new PhabricatorApplicationSearchResultView())
|
||||
->setContent($views);
|
||||
}
|
||||
|
||||
protected function getNewUserBody() {
|
||||
|
@@ -1,179 +0,0 @@
|
||||
<?php
|
||||
|
||||
final class PhabricatorAuditListView extends AphrontView {
|
||||
|
||||
private $commits = array();
|
||||
private $header;
|
||||
private $showDrafts;
|
||||
private $noDataString;
|
||||
private $highlightedAudits;
|
||||
|
||||
public function setNoDataString($no_data_string) {
|
||||
$this->noDataString = $no_data_string;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNoDataString() {
|
||||
return $this->noDataString;
|
||||
}
|
||||
|
||||
public function setHeader($header) {
|
||||
$this->header = $header;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHeader() {
|
||||
return $this->header;
|
||||
}
|
||||
|
||||
public function setShowDrafts($show_drafts) {
|
||||
$this->showDrafts = $show_drafts;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getShowDrafts() {
|
||||
return $this->showDrafts;
|
||||
}
|
||||
|
||||
/**
|
||||
* These commits should have both commit data and audit requests attached.
|
||||
*/
|
||||
public function setCommits(array $commits) {
|
||||
assert_instances_of($commits, 'PhabricatorRepositoryCommit');
|
||||
$this->commits = mpull($commits, null, 'getPHID');
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCommits() {
|
||||
return $this->commits;
|
||||
}
|
||||
|
||||
private function getCommitDescription($phid) {
|
||||
if ($this->commits === null) {
|
||||
return pht('(Unknown Commit)');
|
||||
}
|
||||
|
||||
$commit = idx($this->commits, $phid);
|
||||
if (!$commit) {
|
||||
return pht('(Unknown Commit)');
|
||||
}
|
||||
|
||||
$summary = $commit->getCommitData()->getSummary();
|
||||
if (strlen($summary)) {
|
||||
return $summary;
|
||||
}
|
||||
|
||||
// No summary, so either this is still importing or just has an empty
|
||||
// commit message.
|
||||
|
||||
if (!$commit->isImported()) {
|
||||
return pht('(Importing Commit...)');
|
||||
} else {
|
||||
return pht('(Untitled Commit)');
|
||||
}
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$list = $this->buildList();
|
||||
$list->setFlush(true);
|
||||
return $list->render();
|
||||
}
|
||||
|
||||
public function buildList() {
|
||||
$viewer = $this->getViewer();
|
||||
$rowc = array();
|
||||
|
||||
$phids = array();
|
||||
foreach ($this->getCommits() as $commit) {
|
||||
$phids[] = $commit->getPHID();
|
||||
|
||||
foreach ($commit->getAudits() as $audit) {
|
||||
$phids[] = $audit->getAuditorPHID();
|
||||
}
|
||||
|
||||
$author_phid = $commit->getAuthorPHID();
|
||||
if ($author_phid) {
|
||||
$phids[] = $author_phid;
|
||||
}
|
||||
}
|
||||
|
||||
$handles = $viewer->loadHandles($phids);
|
||||
|
||||
$show_drafts = $this->getShowDrafts();
|
||||
|
||||
$draft_icon = id(new PHUIIconView())
|
||||
->setIcon('fa-comment yellow')
|
||||
->addSigil('has-tooltip')
|
||||
->setMetadata(
|
||||
array(
|
||||
'tip' => pht('Unsubmitted Comments'),
|
||||
));
|
||||
|
||||
$list = new PHUIObjectItemListView();
|
||||
foreach ($this->commits as $commit) {
|
||||
$commit_phid = $commit->getPHID();
|
||||
$commit_handle = $handles[$commit_phid];
|
||||
$committed = null;
|
||||
|
||||
$commit_name = $commit_handle->getName();
|
||||
$commit_link = $commit_handle->getURI();
|
||||
$commit_desc = $this->getCommitDescription($commit_phid);
|
||||
$committed = phabricator_datetime($commit->getEpoch(), $viewer);
|
||||
|
||||
$status = $commit->getAuditStatusObject();
|
||||
|
||||
$status_text = $status->getName();
|
||||
$status_color = $status->getColor();
|
||||
$status_icon = $status->getIcon();
|
||||
|
||||
$author_phid = $commit->getAuthorPHID();
|
||||
if ($author_phid) {
|
||||
$author_name = $handles[$author_phid]->renderLink();
|
||||
} else {
|
||||
$author_name = $commit->getCommitData()->getAuthorName();
|
||||
}
|
||||
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setObjectName($commit_name)
|
||||
->setHeader($commit_desc)
|
||||
->setHref($commit_link)
|
||||
->setDisabled($commit->isUnreachable())
|
||||
->addByline(pht('Author: %s', $author_name))
|
||||
->addIcon('none', $committed);
|
||||
|
||||
if ($show_drafts) {
|
||||
if ($commit->getHasDraft($viewer)) {
|
||||
$item->addAttribute($draft_icon);
|
||||
}
|
||||
}
|
||||
|
||||
$audits = $commit->getAudits();
|
||||
$auditor_phids = mpull($audits, 'getAuditorPHID');
|
||||
if ($auditor_phids) {
|
||||
$auditor_list = $handles->newSublist($auditor_phids)
|
||||
->renderList()
|
||||
->setAsInline(true);
|
||||
} else {
|
||||
$auditor_list = phutil_tag('em', array(), pht('None'));
|
||||
}
|
||||
$item->addAttribute(pht('Auditors: %s', $auditor_list));
|
||||
|
||||
if ($status_color) {
|
||||
$item->setStatusIcon($status_icon.' '.$status_color, $status_text);
|
||||
}
|
||||
|
||||
$list->addItem($item);
|
||||
}
|
||||
|
||||
if ($this->noDataString) {
|
||||
$list->setNoDataString($this->noDataString);
|
||||
}
|
||||
|
||||
if ($this->header) {
|
||||
$list->setHeader($this->header);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
}
|
@@ -181,6 +181,12 @@ final class PhabricatorAuthPasswordEngine
|
||||
$normal_password = phutil_utf8_strtolower($raw_password);
|
||||
if (strlen($normal_password) >= $minimum_similarity) {
|
||||
foreach ($normal_map as $term => $source) {
|
||||
|
||||
// See T2312. This may be required if the term list includes numeric
|
||||
// strings like "12345", which will be cast to integers when used as
|
||||
// array keys.
|
||||
$term = phutil_string_cast($term);
|
||||
|
||||
if (strpos($term, $normal_password) === false &&
|
||||
strpos($normal_password, $term) === false) {
|
||||
continue;
|
||||
|
@@ -100,9 +100,17 @@ final class PhabricatorConduitAPIController
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
$result = null;
|
||||
$error_code = ($ex instanceof ConduitException
|
||||
? 'ERR-CONDUIT-CALL'
|
||||
: 'ERR-CONDUIT-CORE');
|
||||
|
||||
if ($ex instanceof ConduitException) {
|
||||
$error_code = 'ERR-CONDUIT-CALL';
|
||||
} else {
|
||||
$error_code = 'ERR-CONDUIT-CORE';
|
||||
|
||||
// See T13581. When a Conduit method raises an uncaught exception
|
||||
// other than a "ConduitException", log it.
|
||||
phlog($ex);
|
||||
}
|
||||
|
||||
$error_info = $ex->getMessage();
|
||||
}
|
||||
|
||||
|
@@ -142,6 +142,8 @@ abstract class PhabricatorConduitController extends PhabricatorController {
|
||||
$parts[] = '--conduit-token ';
|
||||
$parts[] = phutil_tag('strong', array(), '<conduit-token>');
|
||||
$parts[] = ' ';
|
||||
$parts[] = '--';
|
||||
$parts[] = ' ';
|
||||
|
||||
$parts[] = $method->getAPIMethodName();
|
||||
|
||||
|
@@ -120,9 +120,21 @@ abstract class ConduitAPIMethod
|
||||
public function executeMethod(ConduitAPIRequest $request) {
|
||||
$this->setViewer($request->getUser());
|
||||
|
||||
$client = $this->newConduitCallProxyClient($request);
|
||||
if ($client) {
|
||||
// We're proxying, so just make an intracluster call.
|
||||
return $client->callMethodSynchronous(
|
||||
$this->getAPIMethodName(),
|
||||
$request->getAllParameters());
|
||||
}
|
||||
|
||||
return $this->execute($request);
|
||||
}
|
||||
|
||||
protected function newConduitCallProxyClient(ConduitAPIRequest $request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
abstract public function getAPIMethodName();
|
||||
|
||||
/**
|
||||
|
@@ -51,6 +51,10 @@ final class ConduitAPIRequest extends Phobject {
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function getViewer() {
|
||||
return $this->getUser();
|
||||
}
|
||||
|
||||
public function setOAuthToken(
|
||||
PhabricatorOAuthServerAccessToken $oauth_token) {
|
||||
$this->oauthToken = $oauth_token;
|
||||
|
@@ -8,14 +8,21 @@ final class PhabricatorDaemonsSetupCheck extends PhabricatorSetupCheck {
|
||||
|
||||
protected function executeChecks() {
|
||||
|
||||
$task_daemon = id(new PhabricatorDaemonLogQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
|
||||
->withDaemonClasses(array('PhabricatorTaskmasterDaemon'))
|
||||
->setLimit(1)
|
||||
->execute();
|
||||
try {
|
||||
$task_daemons = id(new PhabricatorDaemonLogQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
|
||||
->withDaemonClasses(array('PhabricatorTaskmasterDaemon'))
|
||||
->setLimit(1)
|
||||
->execute();
|
||||
|
||||
if (!$task_daemon) {
|
||||
$no_daemons = !$task_daemons;
|
||||
} catch (Exception $ex) {
|
||||
// Just skip this warning if the query fails for some reason.
|
||||
$no_daemons = false;
|
||||
}
|
||||
|
||||
if ($no_daemons) {
|
||||
$doc_href = PhabricatorEnv::getDoclink('Managing Daemons with phd');
|
||||
|
||||
$summary = pht(
|
||||
|
@@ -322,6 +322,9 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
|
||||
'directly supported. Prefixes and other strings may be customized with '.
|
||||
'"translation.override".');
|
||||
|
||||
$phd_reason = pht(
|
||||
'Use "bin/phd debug ..." to get a detailed daemon execution log.');
|
||||
|
||||
$ancient_config += array(
|
||||
'phid.external-loaders' =>
|
||||
pht(
|
||||
@@ -539,6 +542,9 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
|
||||
|
||||
'phd.pid-directory' => pht(
|
||||
'Phabricator daemons no longer use PID files.'),
|
||||
|
||||
'phd.trace' => $phd_reason,
|
||||
'phd.verbose' => $phd_reason,
|
||||
);
|
||||
|
||||
return $ancient_config;
|
||||
|
@@ -43,22 +43,6 @@ final class PhabricatorPHDConfigOptions
|
||||
"configuration changes are picked up by the daemons ".
|
||||
"automatically, but pool sizes can not be changed without a ".
|
||||
"restart.")),
|
||||
$this->newOption('phd.verbose', 'bool', false)
|
||||
->setLocked(true)
|
||||
->setBoolOptions(
|
||||
array(
|
||||
pht('Verbose mode'),
|
||||
pht('Normal mode'),
|
||||
))
|
||||
->setSummary(pht("Launch daemons in 'verbose' mode by default."))
|
||||
->setDescription(
|
||||
pht(
|
||||
"Launch daemons in 'verbose' mode by default. This creates a lot ".
|
||||
"of output, but can help debug issues. Daemons launched in debug ".
|
||||
"mode with '%s' are always launched in verbose mode. ".
|
||||
"See also '%s'.",
|
||||
'phd debug',
|
||||
'phd.trace')),
|
||||
$this->newOption('phd.user', 'string', null)
|
||||
->setLocked(true)
|
||||
->setSummary(pht('System user to run daemons as.'))
|
||||
@@ -68,22 +52,6 @@ final class PhabricatorPHDConfigOptions
|
||||
'user will own the working copies of any repositories that '.
|
||||
'Phabricator imports or manages. This option is new and '.
|
||||
'experimental.')),
|
||||
$this->newOption('phd.trace', 'bool', false)
|
||||
->setLocked(true)
|
||||
->setBoolOptions(
|
||||
array(
|
||||
pht('Trace mode'),
|
||||
pht('Normal mode'),
|
||||
))
|
||||
->setSummary(pht("Launch daemons in 'trace' mode by default."))
|
||||
->setDescription(
|
||||
pht(
|
||||
"Launch daemons in 'trace' mode by default. This creates an ".
|
||||
"ENORMOUS amount of output, but can help debug issues. Daemons ".
|
||||
"launched in debug mode with '%s' are always launched in ".
|
||||
"trace mode. See also '%s'.",
|
||||
'phd debug',
|
||||
'phd.verbose')),
|
||||
$this->newOption('phd.garbage-collection', 'wild', array())
|
||||
->setLocked(true)
|
||||
->setLockedMessage(
|
||||
|
@@ -116,11 +116,11 @@ abstract class PhabricatorDaemonManagementWorkflow
|
||||
$trace = PhutilArgumentParser::isTraceModeEnabled();
|
||||
|
||||
$flags = array();
|
||||
if ($trace || PhabricatorEnv::getEnvConfig('phd.trace')) {
|
||||
if ($trace) {
|
||||
$flags[] = '--trace';
|
||||
}
|
||||
|
||||
if ($debug || PhabricatorEnv::getEnvConfig('phd.verbose')) {
|
||||
if ($debug) {
|
||||
$flags[] = '--verbose';
|
||||
}
|
||||
|
||||
|
@@ -510,6 +510,11 @@ final class DifferentialRevisionViewController
|
||||
->setLoadEntireGraph(true)
|
||||
->loadGraph();
|
||||
if (!$stack_graph->isEmpty()) {
|
||||
// See PHI1900. The graph UI element now tries to figure out the correct
|
||||
// height automatically, but currently can't in this case because the
|
||||
// element is not visible when the page loads. Set an explicit height.
|
||||
$stack_graph->setHeight(34);
|
||||
|
||||
$stack_table = $stack_graph->newGraphTable();
|
||||
|
||||
$parent_type = DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST;
|
||||
|
@@ -36,7 +36,8 @@ final class PhabricatorDifferentialRebuildChangesetsWorkflow
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Object "%s" specified by "--revision" must be a Differential '.
|
||||
'revision.'));
|
||||
'revision.',
|
||||
$revision_identifier));
|
||||
}
|
||||
} else {
|
||||
$revision = id(new DifferentialRevisionQuery())
|
||||
|
@@ -1053,14 +1053,30 @@ final class DifferentialChangesetParser extends Phobject {
|
||||
$this->comments = id(new PHUIDiffInlineThreader())
|
||||
->reorderAndThreadCommments($this->comments);
|
||||
|
||||
$old_max_display = 1;
|
||||
foreach ($this->old as $old) {
|
||||
if (isset($old['line'])) {
|
||||
$old_max_display = $old['line'];
|
||||
}
|
||||
}
|
||||
|
||||
$new_max_display = 1;
|
||||
foreach ($this->new as $new) {
|
||||
if (isset($new['line'])) {
|
||||
$new_max_display = $new['line'];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->comments as $comment) {
|
||||
$final = $comment->getLineNumber() +
|
||||
$comment->getLineLength();
|
||||
$final = max(1, $final);
|
||||
$display_line = $comment->getLineNumber() + $comment->getLineLength();
|
||||
$display_line = max(1, $display_line);
|
||||
|
||||
if ($this->isCommentOnRightSideWhenDisplayed($comment)) {
|
||||
$new_comments[$final][] = $comment;
|
||||
$display_line = min($new_max_display, $display_line);
|
||||
$new_comments[$display_line][] = $comment;
|
||||
} else {
|
||||
$old_comments[$final][] = $comment;
|
||||
$display_line = min($old_max_display, $display_line);
|
||||
$old_comments[$display_line][] = $comment;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1327,6 +1343,10 @@ final class DifferentialChangesetParser extends Phobject {
|
||||
$not_covered = 0;
|
||||
|
||||
foreach ($this->new as $k => $new) {
|
||||
if ($new === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$new['line']) {
|
||||
continue;
|
||||
}
|
||||
|
@@ -778,12 +778,19 @@ final class DifferentialRevisionQuery
|
||||
*/
|
||||
protected function shouldGroupQueryResultRows() {
|
||||
|
||||
$join_triggers = array_merge(
|
||||
$this->pathIDs,
|
||||
$this->ccs,
|
||||
$this->reviewers);
|
||||
if (count($this->pathIDs) > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (count($join_triggers) > 1) {
|
||||
if (count($this->ccs) > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (count($this->reviewers) > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (count($this->commitHashes) > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -83,7 +83,9 @@ final class DifferentialChangesetOneUpRenderer
|
||||
$cells = array();
|
||||
if ($is_old) {
|
||||
if ($p['htype']) {
|
||||
if (empty($p['oline'])) {
|
||||
if ($p['htype'] === '\\') {
|
||||
$class = 'comment';
|
||||
} else if (empty($p['oline'])) {
|
||||
$class = 'left old old-full';
|
||||
} else {
|
||||
$class = 'left old';
|
||||
@@ -129,7 +131,9 @@ final class DifferentialChangesetOneUpRenderer
|
||||
$cells[] = $no_coverage;
|
||||
} else {
|
||||
if ($p['htype']) {
|
||||
if (empty($p['oline'])) {
|
||||
if ($p['htype'] === '\\') {
|
||||
$class = 'comment';
|
||||
} else if (empty($p['oline'])) {
|
||||
$class = 'right new new-full';
|
||||
} else {
|
||||
$class = 'right new';
|
||||
|
@@ -505,6 +505,8 @@ abstract class DifferentialChangesetRenderer extends Phobject {
|
||||
$ospec['htype'] = $old[$ii]['type'];
|
||||
if (isset($old_render[$ii])) {
|
||||
$ospec['render'] = $old_render[$ii];
|
||||
} else if ($ospec['htype'] === '\\') {
|
||||
$ospec['render'] = $old[$ii]['text'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,6 +516,8 @@ abstract class DifferentialChangesetRenderer extends Phobject {
|
||||
$nspec['htype'] = $new[$ii]['type'];
|
||||
if (isset($new_render[$ii])) {
|
||||
$nspec['render'] = $new_render[$ii];
|
||||
} else if ($nspec['htype'] === '\\') {
|
||||
$nspec['render'] = $new[$ii]['text'];
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -747,9 +747,10 @@ final class DifferentialDiff
|
||||
$prop->delete();
|
||||
}
|
||||
|
||||
$viewstates = id(new DifferentialViewStateQuery())
|
||||
$viewstate_query = id(new DifferentialViewStateQuery())
|
||||
->setViewer($viewer)
|
||||
->withObjectPHIDs(array($this->getPHID()));
|
||||
$viewstates = new PhabricatorQueryIterator($viewstate_query);
|
||||
foreach ($viewstates as $viewstate) {
|
||||
$viewstate->delete();
|
||||
}
|
||||
|
@@ -1033,9 +1033,10 @@ final class DifferentialRevision extends DifferentialDAO
|
||||
$dummy_path->getTableName(),
|
||||
$this->getID());
|
||||
|
||||
$viewstates = id(new DifferentialViewStateQuery())
|
||||
$viewstate_query = id(new DifferentialViewStateQuery())
|
||||
->setViewer($viewer)
|
||||
->withObjectPHIDs(array($this->getPHID()));
|
||||
$viewstates = new PhabricatorQueryIterator($viewstate_query);
|
||||
foreach ($viewstates as $viewstate) {
|
||||
$viewstate->delete();
|
||||
}
|
||||
|
@@ -6,12 +6,15 @@ final class DifferentialRevisionAbandonTransaction
|
||||
const TRANSACTIONTYPE = 'differential.revision.abandon';
|
||||
const ACTIONKEY = 'abandon';
|
||||
|
||||
protected function getRevisionActionLabel() {
|
||||
protected function getRevisionActionLabel(
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
return pht('Abandon Revision');
|
||||
}
|
||||
|
||||
protected function getRevisionActionDescription(
|
||||
DifferentialRevision $revision) {
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
return pht('This revision will be abandoned and closed.');
|
||||
}
|
||||
|
||||
|
@@ -6,12 +6,15 @@ final class DifferentialRevisionAcceptTransaction
|
||||
const TRANSACTIONTYPE = 'differential.revision.accept';
|
||||
const ACTIONKEY = 'accept';
|
||||
|
||||
protected function getRevisionActionLabel() {
|
||||
protected function getRevisionActionLabel(
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
return pht('Accept Revision');
|
||||
}
|
||||
|
||||
protected function getRevisionActionDescription(
|
||||
DifferentialRevision $revision) {
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
return pht('These changes will be approved.');
|
||||
}
|
||||
|
||||
|
@@ -17,7 +17,9 @@ abstract class DifferentialRevisionActionTransaction
|
||||
}
|
||||
|
||||
abstract protected function validateAction($object, PhabricatorUser $viewer);
|
||||
abstract protected function getRevisionActionLabel();
|
||||
abstract protected function getRevisionActionLabel(
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer);
|
||||
|
||||
protected function validateOptionValue($object, $actor, array $value) {
|
||||
return null;
|
||||
@@ -53,15 +55,23 @@ abstract class DifferentialRevisionActionTransaction
|
||||
}
|
||||
|
||||
protected function getRevisionActionDescription(
|
||||
DifferentialRevision $revision) {
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getRevisionActionSubmitButtonText(
|
||||
DifferentialRevision $revision) {
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getRevisionActionMetadata(
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
return array();
|
||||
}
|
||||
|
||||
public static function loadAllActions() {
|
||||
return id(new PhutilClassMapQuery())
|
||||
->setAncestorClass(__CLASS__)
|
||||
@@ -105,17 +115,19 @@ abstract class DifferentialRevisionActionTransaction
|
||||
->setValue(true);
|
||||
|
||||
if ($this->isActionAvailable($revision, $viewer)) {
|
||||
$label = $this->getRevisionActionLabel();
|
||||
$label = $this->getRevisionActionLabel($revision, $viewer);
|
||||
if ($label !== null) {
|
||||
$field->setCommentActionLabel($label);
|
||||
|
||||
$description = $this->getRevisionActionDescription($revision);
|
||||
$description = $this->getRevisionActionDescription($revision, $viewer);
|
||||
$field->setActionDescription($description);
|
||||
|
||||
$group_key = $this->getRevisionActionGroupKey();
|
||||
$field->setCommentActionGroupKey($group_key);
|
||||
|
||||
$button_text = $this->getRevisionActionSubmitButtonText($revision);
|
||||
$button_text = $this->getRevisionActionSubmitButtonText(
|
||||
$revision,
|
||||
$viewer);
|
||||
$field->setActionSubmitButtonText($button_text);
|
||||
|
||||
// Currently, every revision action conflicts with every other
|
||||
@@ -144,6 +156,11 @@ abstract class DifferentialRevisionActionTransaction
|
||||
$field->setOptions($options);
|
||||
$field->setValue($value);
|
||||
}
|
||||
|
||||
$metadata = $this->getRevisionActionMetadata($revision, $viewer);
|
||||
foreach ($metadata as $metadata_key => $metadata_value) {
|
||||
$field->setMetadataValue($metadata_key, $metadata_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -6,12 +6,15 @@ final class DifferentialRevisionCloseTransaction
|
||||
const TRANSACTIONTYPE = 'differential.revision.close';
|
||||
const ACTIONKEY = 'close';
|
||||
|
||||
protected function getRevisionActionLabel() {
|
||||
protected function getRevisionActionLabel(
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
return pht('Close Revision');
|
||||
}
|
||||
|
||||
protected function getRevisionActionDescription(
|
||||
DifferentialRevision $revision) {
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
return pht('This revision will be closed.');
|
||||
}
|
||||
|
||||
|
@@ -6,12 +6,15 @@ final class DifferentialRevisionCommandeerTransaction
|
||||
const TRANSACTIONTYPE = 'differential.revision.commandeer';
|
||||
const ACTIONKEY = 'commandeer';
|
||||
|
||||
protected function getRevisionActionLabel() {
|
||||
protected function getRevisionActionLabel(
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
return pht('Commandeer Revision');
|
||||
}
|
||||
|
||||
protected function getRevisionActionDescription(
|
||||
DifferentialRevision $revision) {
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
return pht('You will take control of this revision and become its author.');
|
||||
}
|
||||
|
||||
|
@@ -6,12 +6,15 @@ final class DifferentialRevisionPlanChangesTransaction
|
||||
const TRANSACTIONTYPE = 'differential.revision.plan';
|
||||
const ACTIONKEY = 'plan-changes';
|
||||
|
||||
protected function getRevisionActionLabel() {
|
||||
protected function getRevisionActionLabel(
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
return pht('Plan Changes');
|
||||
}
|
||||
|
||||
protected function getRevisionActionDescription(
|
||||
DifferentialRevision $revision) {
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
return pht(
|
||||
'This revision will be removed from review queues until it is revised.');
|
||||
}
|
||||
|
@@ -6,12 +6,15 @@ final class DifferentialRevisionReclaimTransaction
|
||||
const TRANSACTIONTYPE = 'differential.revision.reclaim';
|
||||
const ACTIONKEY = 'reclaim';
|
||||
|
||||
protected function getRevisionActionLabel() {
|
||||
protected function getRevisionActionLabel(
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
return pht('Reclaim Revision');
|
||||
}
|
||||
|
||||
protected function getRevisionActionDescription(
|
||||
DifferentialRevision $revision) {
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
return pht('This revision will be reclaimed and reopened.');
|
||||
}
|
||||
|
||||
|
@@ -6,12 +6,15 @@ final class DifferentialRevisionRejectTransaction
|
||||
const TRANSACTIONTYPE = 'differential.revision.reject';
|
||||
const ACTIONKEY = 'reject';
|
||||
|
||||
protected function getRevisionActionLabel() {
|
||||
protected function getRevisionActionLabel(
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
return pht('Request Changes');
|
||||
}
|
||||
|
||||
protected function getRevisionActionDescription(
|
||||
DifferentialRevision $revision) {
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
return pht('This revision will be returned to the author for updates.');
|
||||
}
|
||||
|
||||
|
@@ -6,12 +6,15 @@ final class DifferentialRevisionReopenTransaction
|
||||
const TRANSACTIONTYPE = 'differential.revision.reopen';
|
||||
const ACTIONKEY = 'reopen';
|
||||
|
||||
protected function getRevisionActionLabel() {
|
||||
protected function getRevisionActionLabel(
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
return pht('Reopen Revision');
|
||||
}
|
||||
|
||||
protected function getRevisionActionDescription(
|
||||
DifferentialRevision $revision) {
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
return pht('This revision will be reopened for review.');
|
||||
}
|
||||
|
||||
|
@@ -84,7 +84,8 @@ final class DifferentialRevisionRepositoryTransaction
|
||||
$errors[] = $this->newInvalidError(
|
||||
pht(
|
||||
'Repository "%s" is not a valid repository, or you do not have '.
|
||||
'permission to view it.'),
|
||||
'permission to view it.',
|
||||
$new_value),
|
||||
$xaction);
|
||||
}
|
||||
}
|
||||
|
@@ -6,21 +6,63 @@ final class DifferentialRevisionRequestReviewTransaction
|
||||
const TRANSACTIONTYPE = 'differential.revision.request';
|
||||
const ACTIONKEY = 'request-review';
|
||||
|
||||
protected function getRevisionActionLabel() {
|
||||
const SOURCE_HARBORMASTER = 'harbormaster';
|
||||
const SOURCE_AUTHOR = 'author';
|
||||
const SOURCE_VIEWER = 'viewer';
|
||||
|
||||
protected function getRevisionActionLabel(
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
|
||||
// See PHI1810. Allow non-authors to "Request Review" on draft revisions
|
||||
// to promote them out of the draft state. This smoothes over the workflow
|
||||
// where an author asks for review of an urgent change but has not used
|
||||
// "Request Review" to skip builds.
|
||||
|
||||
if ($revision->isDraft()) {
|
||||
if (!$this->isViewerRevisionAuthor($revision, $viewer)) {
|
||||
return pht('Begin Review Now');
|
||||
}
|
||||
}
|
||||
|
||||
return pht('Request Review');
|
||||
}
|
||||
|
||||
protected function getRevisionActionDescription(
|
||||
DifferentialRevision $revision) {
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
if ($revision->isDraft()) {
|
||||
return pht('This revision will be submitted to reviewers for feedback.');
|
||||
if (!$this->isViewerRevisionAuthor($revision, $viewer)) {
|
||||
return pht(
|
||||
'This revision will be moved out of the draft state so you can '.
|
||||
'review it immediately.');
|
||||
} else {
|
||||
return pht(
|
||||
'This revision will be submitted to reviewers for feedback.');
|
||||
}
|
||||
} else {
|
||||
return pht('This revision will be returned to reviewers for feedback.');
|
||||
}
|
||||
}
|
||||
|
||||
protected function getRevisionActionMetadata(
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
$map = array();
|
||||
|
||||
if ($revision->isDraft()) {
|
||||
$action_source = $this->getActorSourceType(
|
||||
$revision,
|
||||
$viewer);
|
||||
$map['promotion.source'] = $action_source;
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
protected function getRevisionActionSubmitButtonText(
|
||||
DifferentialRevision $revision) {
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
|
||||
// See PHI975. When the action stack will promote the revision out of
|
||||
// draft, change the button text from "Submit Quietly".
|
||||
@@ -72,29 +114,43 @@ final class DifferentialRevisionRequestReviewTransaction
|
||||
'revisions.'));
|
||||
}
|
||||
|
||||
// When revisions automatically promote out of "Draft" after builds finish,
|
||||
// the viewer may be acting as the Harbormaster application.
|
||||
if (!$viewer->isOmnipotent()) {
|
||||
if (!$this->isViewerRevisionAuthor($object, $viewer)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'You can not request review of this revision because you are not '.
|
||||
'the author of the revision.'));
|
||||
}
|
||||
}
|
||||
$this->getActorSourceType($object, $viewer);
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
return pht(
|
||||
'%s requested review of this revision.',
|
||||
$this->renderAuthor());
|
||||
$source = $this->getDraftPromotionSource();
|
||||
|
||||
switch ($source) {
|
||||
case self::SOURCE_HARBORMASTER:
|
||||
case self::SOURCE_VIEWER:
|
||||
case self::SOURCE_AUTHOR:
|
||||
return pht(
|
||||
'%s published this revision for review.',
|
||||
$this->renderAuthor());
|
||||
default:
|
||||
return pht(
|
||||
'%s requested review of this revision.',
|
||||
$this->renderAuthor());
|
||||
}
|
||||
}
|
||||
|
||||
public function getTitleForFeed() {
|
||||
return pht(
|
||||
'%s requested review of %s.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderObject());
|
||||
$source = $this->getDraftPromotionSource();
|
||||
|
||||
switch ($source) {
|
||||
case self::SOURCE_HARBORMASTER:
|
||||
case self::SOURCE_VIEWER:
|
||||
case self::SOURCE_AUTHOR:
|
||||
return pht(
|
||||
'%s published %s for review.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderObject());
|
||||
default:
|
||||
return pht(
|
||||
'%s requested review of %s.',
|
||||
$this->renderAuthor(),
|
||||
$this->renderObject());
|
||||
}
|
||||
}
|
||||
|
||||
public function getTransactionTypeForConduit($xaction) {
|
||||
@@ -105,4 +161,36 @@ final class DifferentialRevisionRequestReviewTransaction
|
||||
return array();
|
||||
}
|
||||
|
||||
private function getDraftPromotionSource() {
|
||||
return $this->getMetadataValue('promotion.source');
|
||||
}
|
||||
|
||||
private function getActorSourceType(
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
|
||||
$is_harbormaster = $viewer->isOmnipotent();
|
||||
$is_author = $this->isViewerRevisionAuthor($revision, $viewer);
|
||||
$is_draft = $revision->isDraft();
|
||||
|
||||
if ($is_harbormaster) {
|
||||
// When revisions automatically promote out of "Draft" after builds
|
||||
// finish, the viewer may be acting as the Harbormaster application.
|
||||
$source = self::SOURCE_HARBORMASTER;
|
||||
} else if ($is_author) {
|
||||
$source = self::SOURCE_AUTHOR;
|
||||
} else if ($is_draft) {
|
||||
// Non-authors are allowed to "Request Review" on draft revisions, to
|
||||
// force them into review immediately.
|
||||
$source = self::SOURCE_VIEWER;
|
||||
} else {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'You can not request review of this revision because you are not '.
|
||||
'the author of the revision and it is not currently a draft.'));
|
||||
}
|
||||
|
||||
return $source;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -6,12 +6,15 @@ final class DifferentialRevisionResignTransaction
|
||||
const TRANSACTIONTYPE = 'differential.revision.resign';
|
||||
const ACTIONKEY = 'resign';
|
||||
|
||||
protected function getRevisionActionLabel() {
|
||||
protected function getRevisionActionLabel(
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
return pht('Resign as Reviewer');
|
||||
}
|
||||
|
||||
protected function getRevisionActionDescription(
|
||||
DifferentialRevision $revision) {
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorUser $viewer) {
|
||||
return pht('You will resign as a reviewer for this change.');
|
||||
}
|
||||
|
||||
|
@@ -52,7 +52,6 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication {
|
||||
'change/(?P<dblob>.*)' => 'DiffusionChangeController',
|
||||
'clone/' => 'DiffusionCloneController',
|
||||
'history/(?P<dblob>.*)' => 'DiffusionHistoryController',
|
||||
'graph/(?P<dblob>.*)' => 'DiffusionGraphController',
|
||||
'browse/(?P<dblob>.*)' => 'DiffusionBrowseController',
|
||||
'document/(?P<dblob>.*)'
|
||||
=> 'DiffusionDocumentController',
|
||||
|
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
final class DiffusionInternalCommitSearchConduitAPIMethod
|
||||
extends PhabricatorSearchEngineAPIMethod {
|
||||
|
||||
public function getAPIMethodName() {
|
||||
return 'internal.commit.search';
|
||||
}
|
||||
|
||||
public function newSearchEngine() {
|
||||
return new DiffusionInternalCommitSearchEngine();
|
||||
}
|
||||
|
||||
public function getMethodSummary() {
|
||||
return pht('Read raw information about commits.');
|
||||
}
|
||||
|
||||
protected function newConduitCallProxyClient(ConduitAPIRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
|
||||
$constraints = $request->getValue('constraints');
|
||||
if (is_array($constraints)) {
|
||||
$repository_phids = idx($constraints, 'repositoryPHIDs');
|
||||
} else {
|
||||
$repository_phids = array();
|
||||
}
|
||||
|
||||
$repository_phid = null;
|
||||
if (is_array($repository_phids)) {
|
||||
if (phutil_is_natural_list($repository_phids)) {
|
||||
if (count($repository_phids) === 1) {
|
||||
$value = head($repository_phids);
|
||||
if (is_string($value)) {
|
||||
$repository_phid = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($repository_phid === null) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'This internal method must be invoked with a "repositoryPHIDs" '.
|
||||
'constraint with exactly one value.'));
|
||||
}
|
||||
|
||||
$repository = id(new PhabricatorRepositoryQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($repository_phid))
|
||||
->executeOne();
|
||||
if (!$repository) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return $repository->newConduitClientForRequest($request);
|
||||
}
|
||||
|
||||
}
|
@@ -38,7 +38,6 @@ final class DiffusionQueryCommitsConduitAPIMethod
|
||||
|
||||
protected function execute(ConduitAPIRequest $request) {
|
||||
$need_messages = $request->getValue('needMessages');
|
||||
$bypass_cache = $request->getValue('bypassCache');
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$query = id(new DiffusionCommitQuery())
|
||||
@@ -53,12 +52,6 @@ final class DiffusionQueryCommitsConduitAPIMethod
|
||||
->executeOne();
|
||||
if ($repository) {
|
||||
$query->withRepository($repository);
|
||||
if ($bypass_cache) {
|
||||
id(new DiffusionRepositoryClusterEngine())
|
||||
->setViewer($viewer)
|
||||
->setRepository($repository)
|
||||
->synchronizeWorkingCopyBeforeRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,48 +89,22 @@ final class DiffusionQueryCommitsConduitAPIMethod
|
||||
'repositoryPHID' => $commit->getRepository()->getPHID(),
|
||||
'identifier' => $commit->getCommitIdentifier(),
|
||||
'epoch' => $commit->getEpoch(),
|
||||
'authorEpoch' => $commit_data->getCommitDetail('authorEpoch'),
|
||||
'authorEpoch' => $commit_data->getAuthorEpoch(),
|
||||
'uri' => $uri,
|
||||
'isImporting' => !$commit->isImported(),
|
||||
'summary' => $commit->getSummary(),
|
||||
'authorPHID' => $commit->getAuthorPHID(),
|
||||
'committerPHID' => $commit_data->getCommitDetail('committerPHID'),
|
||||
'author' => $commit_data->getAuthorName(),
|
||||
'authorName' => $commit_data->getCommitDetail('authorName'),
|
||||
'authorEmail' => $commit_data->getCommitDetail('authorEmail'),
|
||||
'committer' => $commit_data->getCommitDetail('committer'),
|
||||
'committerName' => $commit_data->getCommitDetail('committerName'),
|
||||
'committerEmail' => $commit_data->getCommitDetail('committerEmail'),
|
||||
'author' => $commit_data->getAuthorString(),
|
||||
'authorName' => $commit_data->getAuthorDisplayName(),
|
||||
'authorEmail' => $commit_data->getAuthorEmail(),
|
||||
'committer' => $commit_data->getCommitterString(),
|
||||
'committerName' => $commit_data->getCommitterDisplayName(),
|
||||
'committerEmail' => $commit_data->getCommitterEmail(),
|
||||
'hashes' => array(),
|
||||
);
|
||||
|
||||
if ($bypass_cache) {
|
||||
$lowlevel_commitref = id(new DiffusionLowLevelCommitQuery())
|
||||
->setRepository($commit->getRepository())
|
||||
->withIdentifier($commit->getCommitIdentifier())
|
||||
->execute();
|
||||
|
||||
$dict['authorEpoch'] = $lowlevel_commitref->getAuthorEpoch();
|
||||
$dict['author'] = $lowlevel_commitref->getAuthor();
|
||||
$dict['authorName'] = $lowlevel_commitref->getAuthorName();
|
||||
$dict['authorEmail'] = $lowlevel_commitref->getAuthorEmail();
|
||||
$dict['committer'] = $lowlevel_commitref->getCommitter();
|
||||
$dict['committerName'] = $lowlevel_commitref->getCommitterName();
|
||||
$dict['committerEmail'] = $lowlevel_commitref->getCommitterEmail();
|
||||
|
||||
if ($need_messages) {
|
||||
$dict['message'] = $lowlevel_commitref->getMessage();
|
||||
}
|
||||
|
||||
foreach ($lowlevel_commitref->getHashes() as $hash) {
|
||||
$dict['hashes'][] = array(
|
||||
'type' => $hash->getHashType(),
|
||||
'value' => $hash->getHashValue(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($need_messages && !$bypass_cache) {
|
||||
if ($need_messages) {
|
||||
$dict['message'] = $commit_data->getCommitMessage();
|
||||
}
|
||||
|
||||
|
@@ -85,6 +85,35 @@ abstract class DiffusionQueryConduitAPIMethod
|
||||
* should occur after @{method:getResult}, like formatting a timestamp.
|
||||
*/
|
||||
final protected function execute(ConduitAPIRequest $request) {
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
|
||||
// We pass this flag on to prevent proxying of any other Conduit calls
|
||||
// which we need to make in order to respond to this one. Although we
|
||||
// could safely proxy them, we take a big performance hit in the common
|
||||
// case, and doing more proxying wouldn't exercise any additional code so
|
||||
// we wouldn't gain a testability/predictability benefit.
|
||||
$is_cluster_request = $request->getIsClusterRequest();
|
||||
$drequest->setIsClusterRequest($is_cluster_request);
|
||||
|
||||
$viewer = $request->getViewer();
|
||||
$repository = $drequest->getRepository();
|
||||
|
||||
// TODO: Allow web UI queries opt out of this if they don't care about
|
||||
// fetching the most up-to-date data? Synchronization can be slow, and a
|
||||
// lot of web reads are probably fine if they're a few seconds out of
|
||||
// date.
|
||||
id(new DiffusionRepositoryClusterEngine())
|
||||
->setViewer($viewer)
|
||||
->setRepository($repository)
|
||||
->synchronizeWorkingCopyBeforeRead();
|
||||
|
||||
return $this->getResult($request);
|
||||
}
|
||||
|
||||
|
||||
protected function newConduitCallProxyClient(ConduitAPIRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
|
||||
$identifier = $request->getValue('repository');
|
||||
if ($identifier === null) {
|
||||
$identifier = $request->getValue('callsign');
|
||||
@@ -92,7 +121,7 @@ abstract class DiffusionQueryConduitAPIMethod
|
||||
|
||||
$drequest = DiffusionRequest::newFromDictionary(
|
||||
array(
|
||||
'user' => $request->getUser(),
|
||||
'user' => $viewer,
|
||||
'repository' => $identifier,
|
||||
'branch' => $request->getValue('branch'),
|
||||
'path' => $request->getValue('path'),
|
||||
@@ -106,46 +135,16 @@ abstract class DiffusionQueryConduitAPIMethod
|
||||
$identifier));
|
||||
}
|
||||
|
||||
// Figure out whether we're going to handle this request on this device,
|
||||
// or proxy it to another node in the cluster.
|
||||
|
||||
// If this is a cluster request and we need to proxy, we'll explode here
|
||||
// to prevent infinite recursion.
|
||||
|
||||
$is_cluster_request = $request->getIsClusterRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$repository = $drequest->getRepository();
|
||||
$client = $repository->newConduitClient(
|
||||
$viewer,
|
||||
$is_cluster_request);
|
||||
|
||||
$client = $repository->newConduitClientForRequest($request);
|
||||
if ($client) {
|
||||
// We're proxying, so just make an intracluster call.
|
||||
return $client->callMethodSynchronous(
|
||||
$this->getAPIMethodName(),
|
||||
$request->getAllParameters());
|
||||
} else {
|
||||
|
||||
// We pass this flag on to prevent proxying of any other Conduit calls
|
||||
// which we need to make in order to respond to this one. Although we
|
||||
// could safely proxy them, we take a big performance hit in the common
|
||||
// case, and doing more proxying wouldn't exercise any additional code so
|
||||
// we wouldn't gain a testability/predictability benefit.
|
||||
$drequest->setIsClusterRequest($is_cluster_request);
|
||||
|
||||
$this->setDiffusionRequest($drequest);
|
||||
|
||||
// TODO: Allow web UI queries opt out of this if they don't care about
|
||||
// fetching the most up-to-date data? Synchronization can be slow, and a
|
||||
// lot of web reads are probably fine if they're a few seconds out of
|
||||
// date.
|
||||
id(new DiffusionRepositoryClusterEngine())
|
||||
->setViewer($viewer)
|
||||
->setRepository($repository)
|
||||
->synchronizeWorkingCopyBeforeRead();
|
||||
|
||||
return $this->getResult($request);
|
||||
return $client;
|
||||
}
|
||||
|
||||
$this->setDiffusionRequest($drequest);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getResult(ConduitAPIRequest $request) {
|
||||
|
@@ -292,7 +292,6 @@ final class DiffusionBrowseController extends DiffusionController {
|
||||
|
||||
$empty_result = null;
|
||||
$browse_panel = null;
|
||||
$branch_panel = null;
|
||||
if (!$results->isValidResults()) {
|
||||
$empty_result = new DiffusionEmptyResultView();
|
||||
$empty_result->setDiffusionRequest($drequest);
|
||||
@@ -328,12 +327,6 @@ final class DiffusionBrowseController extends DiffusionController {
|
||||
->setTable($browse_table)
|
||||
->addClass('diffusion-mobile-view')
|
||||
->setPager($pager);
|
||||
|
||||
$path = $drequest->getPath();
|
||||
$is_branch = (!strlen($path) && $repository->supportsBranchComparison());
|
||||
if ($is_branch) {
|
||||
$branch_panel = $this->buildBranchTable();
|
||||
}
|
||||
}
|
||||
|
||||
$open_revisions = $this->buildOpenRevisions();
|
||||
@@ -359,7 +352,6 @@ final class DiffusionBrowseController extends DiffusionController {
|
||||
->setFooter(
|
||||
array(
|
||||
$bar,
|
||||
$branch_panel,
|
||||
$empty_result,
|
||||
$browse_panel,
|
||||
$open_revisions,
|
||||
@@ -1074,59 +1066,4 @@ final class DiffusionBrowseController extends DiffusionController {
|
||||
return $file;
|
||||
}
|
||||
|
||||
private function buildBranchTable() {
|
||||
$viewer = $this->getViewer();
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
$repository = $drequest->getRepository();
|
||||
|
||||
$branch = $drequest->getBranch();
|
||||
$default_branch = $repository->getDefaultBranch();
|
||||
|
||||
if ($branch === $default_branch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$pager = id(new PHUIPagerView())
|
||||
->setPageSize(10);
|
||||
|
||||
try {
|
||||
$results = $this->callConduitWithDiffusionRequest(
|
||||
'diffusion.historyquery',
|
||||
array(
|
||||
'commit' => $branch,
|
||||
'against' => $default_branch,
|
||||
'path' => $drequest->getPath(),
|
||||
'offset' => $pager->getOffset(),
|
||||
'limit' => $pager->getPageSize() + 1,
|
||||
));
|
||||
} catch (Exception $ex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$history = DiffusionPathChange::newFromConduit($results['pathChanges']);
|
||||
$history = $pager->sliceResults($history);
|
||||
|
||||
if (!$history) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$history_table = id(new DiffusionHistoryTableView())
|
||||
->setViewer($viewer)
|
||||
->setDiffusionRequest($drequest)
|
||||
->setHistory($history)
|
||||
->setParents($results['parents'])
|
||||
->setFilterParents(true)
|
||||
->setIsHead(true)
|
||||
->setIsTail(!$pager->getHasMorePages());
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('%s vs %s', $branch, $default_branch));
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->addClass('diffusion-mobile-view')
|
||||
->setTable($history_table);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -90,10 +90,9 @@ final class DiffusionCommitController extends DiffusionController {
|
||||
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
|
||||
->appendChild($warning_message);
|
||||
|
||||
$list = id(new DiffusionCommitListView())
|
||||
$list = id(new DiffusionCommitGraphView())
|
||||
->setViewer($viewer)
|
||||
->setCommits($commits)
|
||||
->setNoDataString(pht('No recent commits.'));
|
||||
->setCommits($commits);
|
||||
|
||||
$crumbs->addTextCrumb(pht('Ambiguous Commit'));
|
||||
|
||||
@@ -183,7 +182,6 @@ final class DiffusionCommitController extends DiffusionController {
|
||||
}
|
||||
|
||||
$curtain = $this->buildCurtain($commit, $repository);
|
||||
$subheader = $this->buildSubheaderView($commit, $commit_data);
|
||||
$details = $this->buildPropertyListView(
|
||||
$commit,
|
||||
$commit_data,
|
||||
@@ -483,7 +481,6 @@ final class DiffusionCommitController extends DiffusionController {
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setSubheader($subheader)
|
||||
->setCurtain($curtain)
|
||||
->setMainColumn(
|
||||
array(
|
||||
@@ -625,33 +622,50 @@ final class DiffusionCommitController extends DiffusionController {
|
||||
}
|
||||
}
|
||||
|
||||
$author_epoch = $data->getCommitDetail('authorEpoch');
|
||||
$provenance_list = new PHUIStatusListView();
|
||||
|
||||
$committed_info = id(new PHUIStatusItemView())
|
||||
->setNote(phabricator_datetime($commit->getEpoch(), $viewer))
|
||||
->setTarget($commit->renderAnyCommitter($viewer, $handles));
|
||||
$author_view = $commit->newCommitAuthorView($viewer);
|
||||
if ($author_view) {
|
||||
$author_date = $data->getAuthorEpoch();
|
||||
$author_date = phabricator_datetime($author_date, $viewer);
|
||||
|
||||
$committed_list = new PHUIStatusListView();
|
||||
$committed_list->addItem($committed_info);
|
||||
$view->addProperty(
|
||||
pht('Committed'),
|
||||
$committed_list);
|
||||
$provenance_list->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setTarget($author_view)
|
||||
->setNote(pht('Authored on %s', $author_date)));
|
||||
}
|
||||
|
||||
if (!$commit->isAuthorSameAsCommitter()) {
|
||||
$committer_view = $commit->newCommitCommitterView($viewer);
|
||||
if ($committer_view) {
|
||||
$committer_date = $commit->getEpoch();
|
||||
$committer_date = phabricator_datetime($committer_date, $viewer);
|
||||
|
||||
$provenance_list->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setTarget($committer_view)
|
||||
->setNote(pht('Committed on %s', $committer_date)));
|
||||
}
|
||||
}
|
||||
|
||||
if ($push_logs) {
|
||||
$pushed_list = new PHUIStatusListView();
|
||||
|
||||
foreach ($push_logs as $push_log) {
|
||||
$pushed_item = id(new PHUIStatusItemView())
|
||||
->setTarget($handles[$push_log->getPusherPHID()]->renderLink())
|
||||
->setNote(phabricator_datetime($push_log->getEpoch(), $viewer));
|
||||
$pushed_list->addItem($pushed_item);
|
||||
}
|
||||
$pusher_date = $push_log->getEpoch();
|
||||
$pusher_date = phabricator_datetime($pusher_date, $viewer);
|
||||
|
||||
$view->addProperty(
|
||||
pht('Pushed'),
|
||||
$pushed_list);
|
||||
$pusher_view = $handles[$push_log->getPusherPHID()]->renderLink();
|
||||
|
||||
$provenance_list->addItem(
|
||||
id(new PHUIStatusItemView())
|
||||
->setTarget($pusher_view)
|
||||
->setNote(pht('Pushed on %s', $pusher_date)));
|
||||
}
|
||||
}
|
||||
|
||||
$view->addProperty(pht('Provenance'), $provenance_list);
|
||||
|
||||
$reviewer_phid = $data->getCommitDetail('reviewerPHID');
|
||||
if ($reviewer_phid) {
|
||||
$view->addProperty(
|
||||
@@ -743,52 +757,6 @@ final class DiffusionCommitController extends DiffusionController {
|
||||
return $view;
|
||||
}
|
||||
|
||||
private function buildSubheaderView(
|
||||
PhabricatorRepositoryCommit $commit,
|
||||
PhabricatorRepositoryCommitData $data) {
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
$repository = $drequest->getRepository();
|
||||
|
||||
if ($repository->isSVN()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$author_phid = $commit->getAuthorDisplayPHID();
|
||||
$author_name = $data->getAuthorName();
|
||||
$author_epoch = $data->getCommitDetail('authorEpoch');
|
||||
$date = null;
|
||||
if ($author_epoch !== null) {
|
||||
$date = phabricator_datetime($author_epoch, $viewer);
|
||||
}
|
||||
|
||||
if ($author_phid) {
|
||||
$handles = $viewer->loadHandles(array($author_phid));
|
||||
$image_uri = $handles[$author_phid]->getImageURI();
|
||||
$image_href = $handles[$author_phid]->getURI();
|
||||
$author = $handles[$author_phid]->renderLink();
|
||||
} else if (strlen($author_name)) {
|
||||
$author = $author_name;
|
||||
$image_uri = null;
|
||||
$image_href = null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
$author = phutil_tag('strong', array(), $author);
|
||||
if ($date) {
|
||||
$content = pht('Authored by %s on %s.', $author, $date);
|
||||
} else {
|
||||
$content = pht('Authored by %s.', $author);
|
||||
}
|
||||
|
||||
return id(new PHUIHeadThingView())
|
||||
->setImage($image_uri)
|
||||
->setImageHref($image_href)
|
||||
->setContent($content);
|
||||
}
|
||||
|
||||
private function buildComments(PhabricatorRepositoryCommit $commit) {
|
||||
$timeline = $this->buildTransactionTimeline(
|
||||
$commit,
|
||||
@@ -843,15 +811,15 @@ final class DiffusionCommitController extends DiffusionController {
|
||||
new PhutilNumber($limit)));
|
||||
}
|
||||
|
||||
$history_table = id(new DiffusionHistoryTableView())
|
||||
->setUser($viewer)
|
||||
$commit_list = id(new DiffusionCommitGraphView())
|
||||
->setViewer($viewer)
|
||||
->setDiffusionRequest($drequest)
|
||||
->setHistory($merges);
|
||||
|
||||
$panel = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Merged Changes'))
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setTable($history_table);
|
||||
->setObjectList($commit_list->newObjectItemListView());
|
||||
if ($caption) {
|
||||
$panel->setInfoView($caption);
|
||||
}
|
||||
|
@@ -285,7 +285,6 @@ final class DiffusionCompareController extends DiffusionController {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $this->getViewer();
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
$repository = $drequest->getRepository();
|
||||
|
||||
if (!$history) {
|
||||
return $this->renderStatusMessage(
|
||||
@@ -296,8 +295,8 @@ final class DiffusionCompareController extends DiffusionController {
|
||||
phutil_tag('strong', array(), $against_ref)));
|
||||
}
|
||||
|
||||
$history_table = id(new DiffusionHistoryTableView())
|
||||
->setUser($viewer)
|
||||
$history_view = id(new DiffusionCommitGraphView())
|
||||
->setViewer($viewer)
|
||||
->setDiffusionRequest($drequest)
|
||||
->setHistory($history)
|
||||
->setParents($results['parents'])
|
||||
@@ -305,15 +304,6 @@ final class DiffusionCompareController extends DiffusionController {
|
||||
->setIsHead(!$pager->getOffset())
|
||||
->setIsTail(!$pager->getHasMorePages());
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Commits'));
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setTable($history_table)
|
||||
->addClass('diffusion-mobile-view')
|
||||
->setPager($pager);
|
||||
|
||||
return $history_view;
|
||||
}
|
||||
}
|
||||
|
@@ -210,9 +210,6 @@ abstract class DiffusionController extends PhabricatorController {
|
||||
case 'history':
|
||||
$view_name = pht('History');
|
||||
break;
|
||||
case 'graph':
|
||||
$view_name = pht('Graph');
|
||||
break;
|
||||
case 'browse':
|
||||
$view_name = pht('Browse');
|
||||
break;
|
||||
@@ -553,17 +550,6 @@ abstract class DiffusionController extends PhabricatorController {
|
||||
)))
|
||||
->setSelected($key == 'history'));
|
||||
|
||||
$view->addMenuItem(
|
||||
id(new PHUIListItemView())
|
||||
->setKey('graph')
|
||||
->setName(pht('Graph'))
|
||||
->setIcon('fa-code-fork')
|
||||
->setHref($drequest->generateURI(
|
||||
array(
|
||||
'action' => 'graph',
|
||||
)))
|
||||
->setSelected($key == 'graph'));
|
||||
|
||||
return $view;
|
||||
|
||||
}
|
||||
|
@@ -1,110 +0,0 @@
|
||||
<?php
|
||||
|
||||
final class DiffusionGraphController extends DiffusionController {
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$response = $this->loadDiffusionContext();
|
||||
if ($response) {
|
||||
return $response;
|
||||
}
|
||||
require_celerity_resource('diffusion-css');
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
$repository = $drequest->getRepository();
|
||||
|
||||
$pager = id(new PHUIPagerView())
|
||||
->readFromRequest($request);
|
||||
|
||||
$params = array(
|
||||
'commit' => $drequest->getCommit(),
|
||||
'path' => $drequest->getPath(),
|
||||
'offset' => $pager->getOffset(),
|
||||
'limit' => $pager->getPageSize() + 1,
|
||||
);
|
||||
|
||||
$history_results = $this->callConduitWithDiffusionRequest(
|
||||
'diffusion.historyquery',
|
||||
$params);
|
||||
$history = DiffusionPathChange::newFromConduit(
|
||||
$history_results['pathChanges']);
|
||||
|
||||
$history = $pager->sliceResults($history);
|
||||
|
||||
$graph = id(new DiffusionHistoryTableView())
|
||||
->setViewer($viewer)
|
||||
->setDiffusionRequest($drequest)
|
||||
->setHistory($history);
|
||||
|
||||
$show_graph = !strlen($drequest->getPath());
|
||||
if ($show_graph) {
|
||||
$graph->setParents($history_results['parents']);
|
||||
$graph->setIsHead(!$pager->getOffset());
|
||||
$graph->setIsTail(!$pager->getHasMorePages());
|
||||
}
|
||||
|
||||
$header = $this->buildHeader($drequest);
|
||||
|
||||
$crumbs = $this->buildCrumbs(
|
||||
array(
|
||||
'branch' => true,
|
||||
'path' => true,
|
||||
'view' => 'graph',
|
||||
));
|
||||
$crumbs->setBorder(true);
|
||||
|
||||
$title = array(
|
||||
pht('Graph'),
|
||||
$repository->getDisplayName(),
|
||||
);
|
||||
|
||||
$graph_view = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('History Graph'))
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setTable($graph)
|
||||
->addClass('diffusion-mobile-view')
|
||||
->setPager($pager);
|
||||
|
||||
$tabs = $this->buildTabsView('graph');
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setTabs($tabs)
|
||||
->setFooter($graph_view);
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle($title)
|
||||
->setCrumbs($crumbs)
|
||||
->appendChild($view);
|
||||
}
|
||||
|
||||
private function buildHeader(DiffusionRequest $drequest) {
|
||||
$viewer = $this->getViewer();
|
||||
$repository = $drequest->getRepository();
|
||||
|
||||
$no_path = !strlen($drequest->getPath());
|
||||
if ($no_path) {
|
||||
$header_text = pht('Graph');
|
||||
} else {
|
||||
$header_text = $this->renderPathLinks($drequest, $mode = 'history');
|
||||
}
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setUser($viewer)
|
||||
->setHeader($header_text)
|
||||
->setHeaderIcon('fa-code-fork');
|
||||
|
||||
if (!$repository->isSVN()) {
|
||||
$branch_tag = $this->renderBranchTag($drequest);
|
||||
$header->addTag($branch_tag);
|
||||
}
|
||||
|
||||
return $header;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -35,11 +35,29 @@ final class DiffusionHistoryController extends DiffusionController {
|
||||
|
||||
$history = $pager->sliceResults($history);
|
||||
|
||||
$history_list = id(new DiffusionHistoryListView())
|
||||
$history_list = id(new DiffusionCommitGraphView())
|
||||
->setViewer($viewer)
|
||||
->setDiffusionRequest($drequest)
|
||||
->setHistory($history);
|
||||
|
||||
// NOTE: If we have a path (like "src/"), many nodes in the graph are
|
||||
// likely to be missing (since the path wasn't touched by those commits).
|
||||
|
||||
// If we draw the graph, commits will often appear to be unrelated because
|
||||
// intermediate nodes are omitted. Just drop the graph.
|
||||
|
||||
// The ideal behavior would be to load the entire graph and then connect
|
||||
// ancestors appropriately, but this would currrently be prohibitively
|
||||
// expensive in the general case.
|
||||
|
||||
$show_graph = !strlen($drequest->getPath());
|
||||
if ($show_graph) {
|
||||
$history_list
|
||||
->setParents($history_results['parents'])
|
||||
->setIsHead(!$pager->getOffset())
|
||||
->setIsTail(!$pager->getHasMorePages());
|
||||
}
|
||||
|
||||
$header = $this->buildHeader($drequest);
|
||||
|
||||
$crumbs = $this->buildCrumbs(
|
||||
|
@@ -53,14 +53,6 @@ final class DiffusionLastModifiedController extends DiffusionController {
|
||||
}
|
||||
}
|
||||
|
||||
$phids = array();
|
||||
foreach ($commits as $commit) {
|
||||
$phids[] = $commit->getCommitterDisplayPHID();
|
||||
$phids[] = $commit->getAuthorDisplayPHID();
|
||||
}
|
||||
$phids = array_filter($phids);
|
||||
$handles = $this->loadViewerHandles($phids);
|
||||
|
||||
$branch = $drequest->loadBranch();
|
||||
if ($branch && $commits) {
|
||||
$lint_query = id(new DiffusionLintCountQuery())
|
||||
@@ -83,7 +75,6 @@ final class DiffusionLastModifiedController extends DiffusionController {
|
||||
|
||||
$output[$path] = $this->renderColumns(
|
||||
$prequest,
|
||||
$handles,
|
||||
$commit,
|
||||
idx($lint, $path));
|
||||
}
|
||||
@@ -93,11 +84,9 @@ final class DiffusionLastModifiedController extends DiffusionController {
|
||||
|
||||
private function renderColumns(
|
||||
DiffusionRequest $drequest,
|
||||
array $handles,
|
||||
PhabricatorRepositoryCommit $commit = null,
|
||||
$lint = null) {
|
||||
assert_instances_of($handles, 'PhabricatorObjectHandle');
|
||||
$viewer = $this->getRequest()->getUser();
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
if ($commit) {
|
||||
$epoch = $commit->getEpoch();
|
||||
@@ -110,13 +99,6 @@ final class DiffusionLastModifiedController extends DiffusionController {
|
||||
$date = '';
|
||||
}
|
||||
|
||||
$author = $commit->renderAuthor($viewer, $handles);
|
||||
$committer = $commit->renderCommitter($viewer, $handles);
|
||||
|
||||
if ($author != $committer) {
|
||||
$author = hsprintf('%s/%s', $author, $committer);
|
||||
}
|
||||
|
||||
$data = $commit->getCommitData();
|
||||
$details = DiffusionView::linkDetail(
|
||||
$drequest->getRepository(),
|
||||
@@ -124,11 +106,9 @@ final class DiffusionLastModifiedController extends DiffusionController {
|
||||
$data->getSummary());
|
||||
$details = AphrontTableView::renderSingleDisplayLine($details);
|
||||
|
||||
|
||||
$return = array(
|
||||
'commit' => $modified,
|
||||
'date' => $date,
|
||||
'author' => $author,
|
||||
'details' => $details,
|
||||
);
|
||||
|
||||
|
@@ -299,16 +299,10 @@ final class DiffusionRepositoryController extends DiffusionController {
|
||||
$handles,
|
||||
$browse_pager);
|
||||
|
||||
$content[] = $this->buildHistoryTable(
|
||||
$history_results,
|
||||
$history,
|
||||
$history_exception);
|
||||
|
||||
if ($readme) {
|
||||
$content[] = $readme;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
$branch_button = $this->buildBranchList($drequest);
|
||||
$this->branchButton = $branch_button;
|
||||
@@ -428,51 +422,6 @@ final class DiffusionRepositoryController extends DiffusionController {
|
||||
return null;
|
||||
}
|
||||
|
||||
private function buildHistoryTable(
|
||||
$history_results,
|
||||
$history,
|
||||
$history_exception) {
|
||||
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
$repository = $drequest->getRepository();
|
||||
|
||||
if ($history_exception) {
|
||||
if ($repository->isImporting()) {
|
||||
return $this->renderStatusMessage(
|
||||
pht('Still Importing...'),
|
||||
pht(
|
||||
'This repository is still importing. History is not yet '.
|
||||
'available.'));
|
||||
} else {
|
||||
return $this->renderStatusMessage(
|
||||
pht('Unable to Retrieve History'),
|
||||
$history_exception->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$history_table = id(new DiffusionHistoryTableView())
|
||||
->setUser($viewer)
|
||||
->setDiffusionRequest($drequest)
|
||||
->setHistory($history)
|
||||
->setIsHead(true);
|
||||
|
||||
if ($history_results) {
|
||||
$history_table->setParents($history_results['parents']);
|
||||
}
|
||||
|
||||
$panel = id(new PHUIObjectBoxView())
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->addClass('diffusion-mobile-view');
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Recent Commits'));
|
||||
$panel->setHeader($header);
|
||||
$panel->setTable($history_table);
|
||||
|
||||
return $panel;
|
||||
}
|
||||
|
||||
private function buildBranchList(DiffusionRequest $drequest) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
|
@@ -922,18 +922,26 @@ final class DiffusionServeController extends DiffusionController {
|
||||
// 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.
|
||||
// in the output that we're looking at a `--depth` clone.
|
||||
|
||||
// For evidence this isn't completely crazy, see:
|
||||
// https://github.com/schacon/grack/pull/7
|
||||
// A valid x-git-upload-pack-result response during packfile negotiation
|
||||
// should end with a flush packet ("0000"). As long as that packet
|
||||
// terminates the response body in the response, we'll assume the response
|
||||
// is correct and complete.
|
||||
|
||||
// See https://git-scm.com/docs/pack-protocol#_packfile_negotiation
|
||||
|
||||
$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;
|
||||
if (strlen($stdout) >= 4) {
|
||||
$has_flush_packet = (substr($stdout, -4) === "0000");
|
||||
} else {
|
||||
$has_flush_packet = false;
|
||||
}
|
||||
|
||||
return ($has_pack && $has_flush_packet);
|
||||
}
|
||||
|
||||
private function getCommonEnvironment(PhabricatorUser $viewer) {
|
||||
|
@@ -34,4 +34,21 @@ final class DiffusionCommitHash extends Phobject {
|
||||
}
|
||||
return $hash_objects;
|
||||
}
|
||||
|
||||
public static function newFromDictionary(array $map) {
|
||||
$hash_type = idx($map, 'type');
|
||||
$hash_value = idx($map, 'value');
|
||||
|
||||
return id(new self())
|
||||
->setHashType($hash_type)
|
||||
->setHashValue($hash_value);
|
||||
}
|
||||
|
||||
public function newDictionary() {
|
||||
return array(
|
||||
'type' => $this->hashType,
|
||||
'value' => $this->hashValue,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -10,28 +10,48 @@ final class DiffusionCommitRef extends Phobject {
|
||||
private $committerEmail;
|
||||
private $hashes = array();
|
||||
|
||||
public static function newFromConduitResult(array $result) {
|
||||
$ref = id(new DiffusionCommitRef())
|
||||
->setAuthorEpoch(idx($result, 'authorEpoch'))
|
||||
->setCommitterEmail(idx($result, 'committerEmail'))
|
||||
->setCommitterName(idx($result, 'committerName'))
|
||||
->setAuthorEmail(idx($result, 'authorEmail'))
|
||||
->setAuthorName(idx($result, 'authorName'))
|
||||
->setMessage(idx($result, 'message'));
|
||||
public function newDictionary() {
|
||||
$hashes = $this->getHashes();
|
||||
$hashes = mpull($hashes, 'newDictionary');
|
||||
$hashes = array_values($hashes);
|
||||
|
||||
$hashes = array();
|
||||
foreach (idx($result, 'hashes', array()) as $hash_result) {
|
||||
$hashes[] = id(new DiffusionCommitHash())
|
||||
->setHashType(idx($hash_result, 'type'))
|
||||
->setHashValue(idx($hash_result, 'value'));
|
||||
return array(
|
||||
'authorEpoch' => $this->authorEpoch,
|
||||
'authorName' => $this->authorName,
|
||||
'authorEmail' => $this->authorEmail,
|
||||
'committerName' => $this->committerName,
|
||||
'committerEmail' => $this->committerEmail,
|
||||
'message' => $this->message,
|
||||
'hashes' => $hashes,
|
||||
);
|
||||
}
|
||||
|
||||
public static function newFromDictionary(array $map) {
|
||||
$hashes = idx($map, 'hashes', array());
|
||||
foreach ($hashes as $key => $hash_map) {
|
||||
$hashes[$key] = DiffusionCommitHash::newFromDictionary($hash_map);
|
||||
}
|
||||
$hashes = array_values($hashes);
|
||||
|
||||
$ref->setHashes($hashes);
|
||||
$author_epoch = idx($map, 'authorEpoch');
|
||||
$author_name = idx($map, 'authorName');
|
||||
$author_email = idx($map, 'authorEmail');
|
||||
$committer_name = idx($map, 'committerName');
|
||||
$committer_email = idx($map, 'committerEmail');
|
||||
$message = idx($map, 'message');
|
||||
|
||||
return $ref;
|
||||
return id(new self())
|
||||
->setAuthorEpoch($author_epoch)
|
||||
->setAuthorName($author_name)
|
||||
->setAuthorEmail($author_email)
|
||||
->setCommitterName($committer_name)
|
||||
->setCommitterEmail($committer_email)
|
||||
->setMessage($message)
|
||||
->setHashes($hashes);
|
||||
}
|
||||
|
||||
public function setHashes(array $hashes) {
|
||||
assert_instances_of($hashes, 'DiffusionCommitHash');
|
||||
$this->hashes = $hashes;
|
||||
return $this;
|
||||
}
|
||||
|
@@ -104,7 +104,7 @@ final class DiffusionPathChange extends Phobject {
|
||||
|
||||
public function getAuthorName() {
|
||||
if ($this->getCommitData()) {
|
||||
return $this->getCommitData()->getAuthorName();
|
||||
return $this->getCommitData()->getAuthorString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@@ -68,10 +68,15 @@ final class DiffusionMercurialCommandEngine
|
||||
// http://selenic.com/pipermail/mercurial-devel/2011-February/028541.html
|
||||
//
|
||||
// After Jan 2015, it may also fail to write to a revision branch cache.
|
||||
//
|
||||
// Separately, it may fail to write to a different branch cache, and may
|
||||
// encounter issues reading the branch cache.
|
||||
|
||||
$ignore = array(
|
||||
'ignoring untrusted configuration option',
|
||||
"couldn't write revision branch cache:",
|
||||
"couldn't write branch cache:",
|
||||
'invalid branchheads cache',
|
||||
);
|
||||
|
||||
foreach ($ignore as $key => $pattern) {
|
||||
|
@@ -73,17 +73,11 @@ abstract class DiffusionQuery extends PhabricatorQuery {
|
||||
|
||||
$params = $params + $core_params;
|
||||
|
||||
$client = $repository->newConduitClient(
|
||||
$future = $repository->newConduitFuture(
|
||||
$user,
|
||||
$method,
|
||||
$params,
|
||||
$drequest->getIsClusterRequest());
|
||||
if (!$client) {
|
||||
$result = id(new ConduitCall($method, $params))
|
||||
->setUser($user)
|
||||
->execute();
|
||||
$future = new ImmediateFuture($result);
|
||||
} else {
|
||||
$future = $client->callMethod($method, $params);
|
||||
}
|
||||
|
||||
if (!$return_future) {
|
||||
return $future->resolve();
|
||||
|
@@ -76,7 +76,6 @@ final class DiffusionBrowseTableView extends DiffusionView {
|
||||
$dict = array(
|
||||
'lint' => celerity_generate_unique_node_id(),
|
||||
'date' => celerity_generate_unique_node_id(),
|
||||
'author' => celerity_generate_unique_node_id(),
|
||||
'details' => celerity_generate_unique_node_id(),
|
||||
);
|
||||
|
||||
|
642
src/applications/diffusion/view/DiffusionCommitGraphView.php
Normal file
642
src/applications/diffusion/view/DiffusionCommitGraphView.php
Normal file
@@ -0,0 +1,642 @@
|
||||
<?php
|
||||
|
||||
final class DiffusionCommitGraphView
|
||||
extends DiffusionView {
|
||||
|
||||
private $history;
|
||||
private $commits;
|
||||
private $isHead;
|
||||
private $isTail;
|
||||
private $parents;
|
||||
private $filterParents;
|
||||
|
||||
private $commitMap;
|
||||
private $buildableMap;
|
||||
private $revisionMap;
|
||||
|
||||
private $showAuditors;
|
||||
|
||||
public function setHistory(array $history) {
|
||||
assert_instances_of($history, 'DiffusionPathChange');
|
||||
$this->history = $history;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHistory() {
|
||||
return $this->history;
|
||||
}
|
||||
|
||||
public function setCommits(array $commits) {
|
||||
assert_instances_of($commits, 'PhabricatorRepositoryCommit');
|
||||
$this->commits = $commits;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCommits() {
|
||||
return $this->commits;
|
||||
}
|
||||
|
||||
public function setShowAuditors($show_auditors) {
|
||||
$this->showAuditors = $show_auditors;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getShowAuditors() {
|
||||
return $this->showAuditors;
|
||||
}
|
||||
|
||||
public function setParents(array $parents) {
|
||||
$this->parents = $parents;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getParents() {
|
||||
return $this->parents;
|
||||
}
|
||||
|
||||
public function setIsHead($is_head) {
|
||||
$this->isHead = $is_head;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIsHead() {
|
||||
return $this->isHead;
|
||||
}
|
||||
|
||||
public function setIsTail($is_tail) {
|
||||
$this->isTail = $is_tail;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIsTail() {
|
||||
return $this->isTail;
|
||||
}
|
||||
|
||||
public function setFilterParents($filter_parents) {
|
||||
$this->filterParents = $filter_parents;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFilterParents() {
|
||||
return $this->filterParents;
|
||||
}
|
||||
|
||||
private function getRepository() {
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
|
||||
if (!$drequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $drequest->getRepository();
|
||||
}
|
||||
|
||||
public function newObjectItemListView() {
|
||||
$list_view = id(new PHUIObjectItemListView());
|
||||
|
||||
$item_views = $this->newObjectItemViews();
|
||||
foreach ($item_views as $item_view) {
|
||||
$list_view->addItem($item_view);
|
||||
}
|
||||
|
||||
return $list_view;
|
||||
}
|
||||
|
||||
private function newObjectItemViews() {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
require_celerity_resource('diffusion-css');
|
||||
|
||||
$show_builds = $this->shouldShowBuilds();
|
||||
$show_revisions = $this->shouldShowRevisions();
|
||||
$show_auditors = $this->shouldShowAuditors();
|
||||
|
||||
$phids = array();
|
||||
|
||||
if ($show_revisions) {
|
||||
$revision_map = $this->getRevisionMap();
|
||||
foreach ($revision_map as $revisions) {
|
||||
foreach ($revisions as $revision) {
|
||||
$phids[] = $revision->getPHID();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$commits = $this->getCommitMap();
|
||||
|
||||
foreach ($commits as $commit) {
|
||||
$author_phid = $commit->getAuthorDisplayPHID();
|
||||
if ($author_phid !== null) {
|
||||
$phids[] = $author_phid;
|
||||
}
|
||||
}
|
||||
|
||||
if ($show_auditors) {
|
||||
foreach ($commits as $commit) {
|
||||
$audits = $commit->getAudits();
|
||||
foreach ($audits as $auditor) {
|
||||
$phids[] = $auditor->getAuditorPHID();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$handles = $viewer->loadHandles($phids);
|
||||
|
||||
$views = array();
|
||||
|
||||
$items = $this->newHistoryItems();
|
||||
foreach ($items as $hash => $item) {
|
||||
$content = array();
|
||||
|
||||
$commit = $item['commit'];
|
||||
|
||||
$commit_description = $this->getCommitDescription($commit);
|
||||
$commit_link = $this->getCommitURI($hash);
|
||||
|
||||
$short_hash = $this->getCommitObjectName($hash);
|
||||
$is_disabled = $this->getCommitIsDisabled($commit);
|
||||
|
||||
$item_view = id(new PHUIObjectItemView())
|
||||
->setViewer($viewer)
|
||||
->setHeader($commit_description)
|
||||
->setObjectName($short_hash)
|
||||
->setHref($commit_link)
|
||||
->setDisabled($is_disabled);
|
||||
|
||||
$this->addBrowseAction($item_view, $hash);
|
||||
|
||||
if ($show_builds) {
|
||||
$this->addBuildAction($item_view, $hash);
|
||||
}
|
||||
|
||||
$this->addAuditAction($item_view, $hash);
|
||||
|
||||
if ($show_auditors) {
|
||||
$auditor_list = $item_view->newMapView();
|
||||
if ($commit) {
|
||||
$auditors = $this->newAuditorList($commit, $handles);
|
||||
$auditor_list->newItem()
|
||||
->setName(pht('Auditors'))
|
||||
->setValue($auditors);
|
||||
}
|
||||
}
|
||||
|
||||
$property_list = $item_view->newMapView();
|
||||
|
||||
if ($commit) {
|
||||
$author_view = $this->getCommitAuthorView($commit);
|
||||
if ($author_view) {
|
||||
$property_list->newItem()
|
||||
->setName(pht('Author'))
|
||||
->setValue($author_view);
|
||||
}
|
||||
}
|
||||
|
||||
if ($show_revisions) {
|
||||
if ($commit) {
|
||||
$revisions = $this->getRevisions($commit);
|
||||
if ($revisions) {
|
||||
$list_view = $handles->newSublist(mpull($revisions, 'getPHID'))
|
||||
->newListView();
|
||||
|
||||
$property_list->newItem()
|
||||
->setName(pht('Revisions'))
|
||||
->setValue($list_view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$views[$hash] = $item_view;
|
||||
}
|
||||
|
||||
return $views;
|
||||
}
|
||||
|
||||
private function newObjectItemRows() {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$items = $this->newHistoryItems();
|
||||
$views = $this->newObjectItemViews();
|
||||
|
||||
$last_date = null;
|
||||
$rows = array();
|
||||
foreach ($items as $hash => $item) {
|
||||
$item_epoch = $item['epoch'];
|
||||
$item_date = phabricator_date($item_epoch, $viewer);
|
||||
if ($item_date !== $last_date) {
|
||||
$last_date = $item_date;
|
||||
$header = $item_date;
|
||||
} else {
|
||||
$header = null;
|
||||
}
|
||||
|
||||
$item_view = $views[$hash];
|
||||
|
||||
$list_view = id(new PHUIObjectItemListView())
|
||||
->setViewer($viewer)
|
||||
->setFlush(true)
|
||||
->addItem($item_view);
|
||||
|
||||
if ($header !== null) {
|
||||
$list_view->setHeader($header);
|
||||
}
|
||||
|
||||
$rows[] = $list_view;
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$rows = $this->newObjectItemRows();
|
||||
|
||||
$graph = $this->newGraphView();
|
||||
foreach ($rows as $idx => $row) {
|
||||
$cells = array();
|
||||
|
||||
if ($graph) {
|
||||
$cells[] = phutil_tag(
|
||||
'td',
|
||||
array(
|
||||
'class' => 'diffusion-commit-graph-path-cell',
|
||||
),
|
||||
$graph[$idx]);
|
||||
}
|
||||
|
||||
$cells[] = phutil_tag(
|
||||
'td',
|
||||
array(
|
||||
'class' => 'diffusion-commit-graph-content-cell',
|
||||
),
|
||||
$row);
|
||||
|
||||
$rows[$idx] = phutil_tag('tr', array(), $cells);
|
||||
}
|
||||
|
||||
$table = phutil_tag(
|
||||
'table',
|
||||
array(
|
||||
'class' => 'diffusion-commit-graph-table',
|
||||
),
|
||||
$rows);
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
private function newGraphView() {
|
||||
if (!$this->getParents()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parents = $this->getParents();
|
||||
|
||||
// If we're filtering parents, remove relationships which point to
|
||||
// commits that are not part of the visible graph. Otherwise, we get
|
||||
// a big tree of nonsense when viewing release branches like "stable"
|
||||
// versus "master".
|
||||
if ($this->getFilterParents()) {
|
||||
foreach ($parents as $key => $nodes) {
|
||||
foreach ($nodes as $nkey => $node) {
|
||||
if (empty($parents[$node])) {
|
||||
unset($parents[$key][$nkey]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return id(new PHUIDiffGraphView())
|
||||
->setIsHead($this->getIsHead())
|
||||
->setIsTail($this->getIsTail())
|
||||
->renderGraph($parents);
|
||||
}
|
||||
|
||||
private function shouldShowBuilds() {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$show_builds = PhabricatorApplication::isClassInstalledForViewer(
|
||||
'PhabricatorHarbormasterApplication',
|
||||
$this->getUser());
|
||||
|
||||
return $show_builds;
|
||||
}
|
||||
|
||||
private function shouldShowRevisions() {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$show_revisions = PhabricatorApplication::isClassInstalledForViewer(
|
||||
'PhabricatorDifferentialApplication',
|
||||
$viewer);
|
||||
|
||||
return $show_revisions;
|
||||
}
|
||||
|
||||
private function shouldShowAuditors() {
|
||||
return $this->getShowAuditors();
|
||||
}
|
||||
|
||||
private function newHistoryItems() {
|
||||
$items = array();
|
||||
|
||||
$history = $this->getHistory();
|
||||
if ($history !== null) {
|
||||
foreach ($history as $history_item) {
|
||||
$commit_hash = $history_item->getCommitIdentifier();
|
||||
|
||||
$items[$commit_hash] = array(
|
||||
'epoch' => $history_item->getEpoch(),
|
||||
'hash' => $commit_hash,
|
||||
'commit' => $this->getCommit($commit_hash),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$commits = $this->getCommitMap();
|
||||
foreach ($commits as $commit) {
|
||||
$commit_hash = $commit->getCommitIdentifier();
|
||||
|
||||
$items[$commit_hash] = array(
|
||||
'epoch' => $commit->getEpoch(),
|
||||
'hash' => $commit_hash,
|
||||
'commit' => $commit,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
private function getCommitDescription($commit) {
|
||||
if (!$commit) {
|
||||
return phutil_tag('em', array(), pht("Discovering\xE2\x80\xA6"));
|
||||
}
|
||||
|
||||
// We can show details once the message and change have been imported.
|
||||
$partial_import = PhabricatorRepositoryCommit::IMPORTED_MESSAGE |
|
||||
PhabricatorRepositoryCommit::IMPORTED_CHANGE;
|
||||
if (!$commit->isPartiallyImported($partial_import)) {
|
||||
return phutil_tag('em', array(), pht("Importing\xE2\x80\xA6"));
|
||||
}
|
||||
|
||||
return $commit->getCommitData()->getSummary();
|
||||
}
|
||||
|
||||
private function getCommitURI($hash) {
|
||||
$repository = $this->getRepository();
|
||||
|
||||
if ($repository) {
|
||||
return $repository->getCommitURI($hash);
|
||||
}
|
||||
|
||||
$commit = $this->getCommit($hash);
|
||||
if ($commit) {
|
||||
return $commit->getURI();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getCommitObjectName($hash) {
|
||||
$repository = $this->getRepository();
|
||||
|
||||
if ($repository) {
|
||||
return $repository->formatCommitName(
|
||||
$hash,
|
||||
$is_local = true);
|
||||
}
|
||||
|
||||
$commit = $this->getCommit($hash);
|
||||
if ($commit) {
|
||||
return $commit->getDisplayName();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getCommitIsDisabled($commit) {
|
||||
if (!$commit) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($commit->isUnreachable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getCommitAuthorView($commit) {
|
||||
if (!$commit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$author_phid = $commit->getAuthorDisplayPHID();
|
||||
if ($author_phid) {
|
||||
return $viewer->loadHandles(array($author_phid))
|
||||
->newListView();
|
||||
}
|
||||
|
||||
return $commit->newCommitAuthorView($viewer);
|
||||
}
|
||||
|
||||
private function getCommit($hash) {
|
||||
$commit_map = $this->getCommitMap();
|
||||
return idx($commit_map, $hash);
|
||||
}
|
||||
|
||||
private function getCommitMap() {
|
||||
if ($this->commitMap === null) {
|
||||
$commit_list = $this->newCommitList();
|
||||
$this->commitMap = mpull($commit_list, null, 'getCommitIdentifier');
|
||||
}
|
||||
|
||||
return $this->commitMap;
|
||||
}
|
||||
|
||||
private function addBrowseAction(PHUIObjectItemView $item, $hash) {
|
||||
$repository = $this->getRepository();
|
||||
|
||||
if (!$repository) {
|
||||
return;
|
||||
}
|
||||
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
$path = $drequest->getPath();
|
||||
|
||||
$uri = $drequest->generateURI(
|
||||
array(
|
||||
'action' => 'browse',
|
||||
'path' => $path,
|
||||
'commit' => $hash,
|
||||
));
|
||||
|
||||
$menu_item = $item->newMenuItem()
|
||||
->setName(pht('Browse Repository'))
|
||||
->setURI($uri);
|
||||
|
||||
$menu_item->newIcon()
|
||||
->setIcon('fa-folder-open-o')
|
||||
->setColor('bluegrey');
|
||||
}
|
||||
|
||||
private function addBuildAction(PHUIObjectItemView $item, $hash) {
|
||||
$is_disabled = true;
|
||||
|
||||
$buildable = null;
|
||||
|
||||
$commit = $this->getCommit($hash);
|
||||
if ($commit) {
|
||||
$buildable = $this->getBuildable($commit);
|
||||
}
|
||||
|
||||
if ($buildable) {
|
||||
$icon = $buildable->getStatusIcon();
|
||||
$color = $buildable->getStatusColor();
|
||||
$name = $buildable->getStatusDisplayName();
|
||||
$uri = $buildable->getURI();
|
||||
} else {
|
||||
$icon = 'fa-times';
|
||||
$color = 'grey';
|
||||
$name = pht('No Builds');
|
||||
$uri = null;
|
||||
}
|
||||
|
||||
$menu_item = $item->newMenuItem()
|
||||
->setName($name)
|
||||
->setURI($uri)
|
||||
->setDisabled(($uri === null));
|
||||
|
||||
$menu_item->newIcon()
|
||||
->setIcon($icon)
|
||||
->setColor($color);
|
||||
}
|
||||
|
||||
private function addAuditAction(PHUIObjectItemView $item_view, $hash) {
|
||||
$commit = $this->getCommit($hash);
|
||||
|
||||
if ($commit) {
|
||||
$status = $commit->getAuditStatusObject();
|
||||
|
||||
$text = $status->getName();
|
||||
$icon = $status->getIcon();
|
||||
|
||||
$is_disabled = $status->isNoAudit();
|
||||
if ($is_disabled) {
|
||||
$uri = null;
|
||||
$color = 'grey';
|
||||
} else {
|
||||
$color = $status->getColor();
|
||||
$uri = $commit->getURI();
|
||||
}
|
||||
} else {
|
||||
$text = pht('No Audit');
|
||||
$color = 'grey';
|
||||
$icon = 'fa-times';
|
||||
$uri = null;
|
||||
|
||||
$is_disabled = true;
|
||||
}
|
||||
|
||||
$menu_item = $item_view->newMenuItem()
|
||||
->setName($text)
|
||||
->setURI($uri)
|
||||
->setBackgroundColor($color)
|
||||
->setDisabled($is_disabled);
|
||||
|
||||
$menu_item->newIcon()
|
||||
->setIcon($icon)
|
||||
->setColor($color);
|
||||
}
|
||||
|
||||
private function getBuildable(PhabricatorRepositoryCommit $commit) {
|
||||
$buildable_map = $this->getBuildableMap();
|
||||
return idx($buildable_map, $commit->getPHID());
|
||||
}
|
||||
|
||||
private function getBuildableMap() {
|
||||
if ($this->buildableMap === null) {
|
||||
$commits = $this->getCommitMap();
|
||||
$buildables = $this->loadBuildables($commits);
|
||||
$this->buildableMap = $buildables;
|
||||
}
|
||||
|
||||
return $this->buildableMap;
|
||||
}
|
||||
|
||||
private function getRevisions(PhabricatorRepositoryCommit $commit) {
|
||||
$revision_map = $this->getRevisionMap();
|
||||
return idx($revision_map, $commit->getPHID(), array());
|
||||
}
|
||||
|
||||
private function getRevisionMap() {
|
||||
if ($this->revisionMap === null) {
|
||||
$this->revisionMap = $this->newRevisionMap();
|
||||
}
|
||||
|
||||
return $this->revisionMap;
|
||||
}
|
||||
|
||||
private function newRevisionMap() {
|
||||
$viewer = $this->getViewer();
|
||||
$commits = $this->getCommitMap();
|
||||
|
||||
return DiffusionCommitRevisionQuery::loadRevisionMapForCommits(
|
||||
$viewer,
|
||||
$commits);
|
||||
}
|
||||
|
||||
private function newCommitList() {
|
||||
$commits = $this->getCommits();
|
||||
if ($commits !== null) {
|
||||
return $commits;
|
||||
}
|
||||
|
||||
$repository = $this->getRepository();
|
||||
if (!$repository) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$history = $this->getHistory();
|
||||
if ($history === null) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$identifiers = array();
|
||||
foreach ($history as $item) {
|
||||
$identifiers[] = $item->getCommitIdentifier();
|
||||
}
|
||||
|
||||
if (!$identifiers) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$commits = id(new DiffusionCommitQuery())
|
||||
->setViewer($viewer)
|
||||
->withRepository($repository)
|
||||
->withIdentifiers($identifiers)
|
||||
->needCommitData(true)
|
||||
->needIdentities(true)
|
||||
->execute();
|
||||
|
||||
return $commits;
|
||||
}
|
||||
|
||||
private function newAuditorList(
|
||||
PhabricatorRepositoryCommit $commit,
|
||||
$handles) {
|
||||
|
||||
$auditors = $commit->getAudits();
|
||||
if (!$auditors) {
|
||||
return phutil_tag('em', array(), pht('None'));
|
||||
}
|
||||
|
||||
$auditor_phids = mpull($auditors, 'getAuditorPHID');
|
||||
$auditor_list = $handles->newSublist($auditor_phids)
|
||||
->newListView();
|
||||
|
||||
return $auditor_list;
|
||||
}
|
||||
|
||||
}
|
@@ -1,177 +0,0 @@
|
||||
<?php
|
||||
|
||||
final class DiffusionCommitListView extends AphrontView {
|
||||
|
||||
private $commits = array();
|
||||
private $noDataString;
|
||||
|
||||
public function setNoDataString($no_data_string) {
|
||||
$this->noDataString = $no_data_string;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setHeader($header) {
|
||||
$this->header = $header;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCommits(array $commits) {
|
||||
assert_instances_of($commits, 'PhabricatorRepositoryCommit');
|
||||
$this->commits = mpull($commits, null, 'getPHID');
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCommits() {
|
||||
return $this->commits;
|
||||
}
|
||||
|
||||
public function setHandles(array $handles) {
|
||||
assert_instances_of($handles, 'PhabricatorObjectHandle');
|
||||
$this->handles = $handles;
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function getRequiredHandlePHIDs() {
|
||||
$phids = array();
|
||||
foreach ($this->history as $item) {
|
||||
$data = $item->getCommitData();
|
||||
if ($data) {
|
||||
if ($data->getCommitDetail('authorPHID')) {
|
||||
$phids[$data->getCommitDetail('authorPHID')] = true;
|
||||
}
|
||||
if ($data->getCommitDetail('committerPHID')) {
|
||||
$phids[$data->getCommitDetail('committerPHID')] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return array_keys($phids);
|
||||
}
|
||||
|
||||
private function getCommitDescription($phid) {
|
||||
if ($this->commits === null) {
|
||||
return pht('(Unknown Commit)');
|
||||
}
|
||||
|
||||
$commit = idx($this->commits, $phid);
|
||||
if (!$commit) {
|
||||
return pht('(Unknown Commit)');
|
||||
}
|
||||
|
||||
$summary = $commit->getCommitData()->getSummary();
|
||||
if (strlen($summary)) {
|
||||
return $summary;
|
||||
}
|
||||
|
||||
// No summary, so either this is still importing or just has an empty
|
||||
// commit message.
|
||||
|
||||
if (!$commit->isImported()) {
|
||||
return pht('(Importing Commit...)');
|
||||
} else {
|
||||
return pht('(Untitled Commit)');
|
||||
}
|
||||
}
|
||||
|
||||
public function render() {
|
||||
require_celerity_resource('diffusion-css');
|
||||
return $this->buildList();
|
||||
}
|
||||
|
||||
public function buildList() {
|
||||
$viewer = $this->getViewer();
|
||||
$rowc = array();
|
||||
|
||||
$phids = array();
|
||||
foreach ($this->getCommits() as $commit) {
|
||||
$phids[] = $commit->getPHID();
|
||||
|
||||
$author_phid = $commit->getAuthorPHID();
|
||||
if ($author_phid) {
|
||||
$phids[] = $author_phid;
|
||||
}
|
||||
}
|
||||
|
||||
$handles = $viewer->loadHandles($phids);
|
||||
|
||||
$cur_date = 0;
|
||||
$view = array();
|
||||
foreach ($this->commits as $commit) {
|
||||
$new_date = phabricator_date($commit->getEpoch(), $viewer);
|
||||
if ($cur_date !== $new_date) {
|
||||
$date = ucfirst(
|
||||
phabricator_relative_date($commit->getEpoch(), $viewer));
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader($date);
|
||||
$list = id(new PHUIObjectItemListView())
|
||||
->setFlush(true)
|
||||
->addClass('diffusion-history-list');
|
||||
|
||||
$view[] = id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setObjectList($list);
|
||||
}
|
||||
|
||||
$commit_phid = $commit->getPHID();
|
||||
$commit_handle = $handles[$commit_phid];
|
||||
$committed = null;
|
||||
|
||||
$commit_name = $commit_handle->getName();
|
||||
$commit_link = $commit_handle->getURI();
|
||||
$commit_desc = $this->getCommitDescription($commit_phid);
|
||||
$committed = phabricator_datetime($commit->getEpoch(), $viewer);
|
||||
|
||||
$engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
|
||||
$engine->setConfig('viewer', $viewer);
|
||||
$commit_data = $commit->getCommitData();
|
||||
$message = $commit_data->getCommitMessage();
|
||||
$message = $engine->markupText($message);
|
||||
$message = phutil_tag_div(
|
||||
'diffusion-history-message phabricator-remarkup', $message);
|
||||
|
||||
$author_phid = $commit->getAuthorPHID();
|
||||
if ($author_phid) {
|
||||
$author_name = $handles[$author_phid]->renderLink();
|
||||
$author_image_uri = $handles[$author_phid]->getImageURI();
|
||||
} else {
|
||||
$author_name = $commit->getCommitData()->getAuthorName();
|
||||
$author_image_uri =
|
||||
celerity_get_resource_uri('/rsrc/image/people/user0.png');
|
||||
}
|
||||
|
||||
$commit_tag = id(new PHUITagView())
|
||||
->setName($commit_name)
|
||||
->setType(PHUITagView::TYPE_SHADE)
|
||||
->setColor(PHUITagView::COLOR_INDIGO)
|
||||
->setBorder(PHUITagView::BORDER_NONE)
|
||||
->setSlimShady(true);
|
||||
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setHeader($commit_desc)
|
||||
->setHref($commit_link)
|
||||
->setDisabled($commit->isUnreachable())
|
||||
->setDescription($message)
|
||||
->setImageURI($author_image_uri)
|
||||
->addByline(pht('Author: %s', $author_name))
|
||||
->addIcon('none', $committed)
|
||||
->addAttribute($commit_tag);
|
||||
|
||||
$list->addItem($item);
|
||||
$cur_date = $new_date;
|
||||
}
|
||||
|
||||
if (!$view) {
|
||||
$list = id(new PHUIObjectItemListView())
|
||||
->setFlush(true)
|
||||
->setNoDataString($this->noDataString);
|
||||
|
||||
$view = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Recent Commits'))
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setObjectList($list);
|
||||
}
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
}
|
@@ -1,172 +0,0 @@
|
||||
<?php
|
||||
|
||||
final class DiffusionHistoryListView extends DiffusionHistoryView {
|
||||
|
||||
public function render() {
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
$viewer = $this->getUser();
|
||||
$repository = $drequest->getRepository();
|
||||
|
||||
require_celerity_resource('diffusion-css');
|
||||
Javelin::initBehavior('phabricator-tooltips');
|
||||
|
||||
$buildables = $this->loadBuildables(
|
||||
mpull($this->getHistory(), 'getCommit'));
|
||||
|
||||
$show_revisions = PhabricatorApplication::isClassInstalledForViewer(
|
||||
'PhabricatorDifferentialApplication',
|
||||
$viewer);
|
||||
|
||||
$handles = $viewer->loadHandles($this->getRequiredHandlePHIDs());
|
||||
|
||||
$show_builds = PhabricatorApplication::isClassInstalledForViewer(
|
||||
'PhabricatorHarbormasterApplication',
|
||||
$this->getUser());
|
||||
|
||||
$cur_date = null;
|
||||
$view = array();
|
||||
foreach ($this->getHistory() as $history) {
|
||||
$epoch = $history->getEpoch();
|
||||
$new_date = phabricator_date($history->getEpoch(), $viewer);
|
||||
if ($cur_date !== $new_date) {
|
||||
$date = ucfirst(
|
||||
phabricator_relative_date($history->getEpoch(), $viewer));
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader($date);
|
||||
$list = id(new PHUIObjectItemListView())
|
||||
->setFlush(true)
|
||||
->addClass('diffusion-history-list');
|
||||
|
||||
$view[] = id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->addClass('diffusion-mobile-view')
|
||||
->setObjectList($list);
|
||||
}
|
||||
|
||||
if ($epoch) {
|
||||
$committed = $viewer->formatShortDateTime($epoch);
|
||||
} else {
|
||||
$committed = null;
|
||||
}
|
||||
|
||||
$data = $history->getCommitData();
|
||||
$author_phid = $committer = $committer_phid = null;
|
||||
if ($data) {
|
||||
$author_phid = $data->getCommitDetail('authorPHID');
|
||||
$committer_phid = $data->getCommitDetail('committerPHID');
|
||||
$committer = $data->getCommitDetail('committer');
|
||||
}
|
||||
|
||||
if ($author_phid && isset($handles[$author_phid])) {
|
||||
$author_name = $handles[$author_phid]->renderLink();
|
||||
$author_image = $handles[$author_phid]->getImageURI();
|
||||
} else {
|
||||
$author_name = self::renderName($history->getAuthorName());
|
||||
$author_image =
|
||||
celerity_get_resource_uri('/rsrc/image/people/user0.png');
|
||||
}
|
||||
|
||||
$different_committer = false;
|
||||
if ($committer_phid) {
|
||||
$different_committer = ($committer_phid != $author_phid);
|
||||
} else if ($committer != '') {
|
||||
$different_committer = ($committer != $history->getAuthorName());
|
||||
}
|
||||
if ($different_committer) {
|
||||
if ($committer_phid && isset($handles[$committer_phid])) {
|
||||
$committer = $handles[$committer_phid]->renderLink();
|
||||
} else {
|
||||
$committer = self::renderName($committer);
|
||||
}
|
||||
$author_name = hsprintf('%s / %s', $author_name, $committer);
|
||||
}
|
||||
|
||||
// We can show details once the message and change have been imported.
|
||||
$partial_import = PhabricatorRepositoryCommit::IMPORTED_MESSAGE |
|
||||
PhabricatorRepositoryCommit::IMPORTED_CHANGE;
|
||||
|
||||
$commit = $history->getCommit();
|
||||
if ($commit && $commit->isPartiallyImported($partial_import) && $data) {
|
||||
$commit_desc = $history->getSummary();
|
||||
} else {
|
||||
$commit_desc = phutil_tag('em', array(), pht("Importing\xE2\x80\xA6"));
|
||||
}
|
||||
|
||||
$browse_button = $this->linkBrowse(
|
||||
$history->getPath(),
|
||||
array(
|
||||
'commit' => $history->getCommitIdentifier(),
|
||||
'branch' => $drequest->getBranch(),
|
||||
'type' => $history->getFileType(),
|
||||
),
|
||||
true);
|
||||
|
||||
$diff_tag = null;
|
||||
if ($show_revisions && $commit) {
|
||||
$revisions = $this->getRevisionsForCommit($commit);
|
||||
if ($revisions) {
|
||||
$revision = head($revisions);
|
||||
$diff_tag = id(new PHUITagView())
|
||||
->setName($revision->getMonogram())
|
||||
->setType(PHUITagView::TYPE_SHADE)
|
||||
->setColor(PHUITagView::COLOR_BLUE)
|
||||
->setHref($revision->getURI())
|
||||
->setBorder(PHUITagView::BORDER_NONE)
|
||||
->setSlimShady(true);
|
||||
}
|
||||
}
|
||||
|
||||
$build_view = null;
|
||||
if ($show_builds) {
|
||||
$buildable = idx($buildables, $commit->getPHID());
|
||||
if ($buildable !== null) {
|
||||
$build_view = $this->renderBuildable($buildable, 'button');
|
||||
}
|
||||
}
|
||||
|
||||
$message = null;
|
||||
$commit_link = $repository->getCommitURI(
|
||||
$history->getCommitIdentifier());
|
||||
|
||||
$commit_name = $repository->formatCommitName(
|
||||
$history->getCommitIdentifier(), $local = true);
|
||||
|
||||
$committed = phabricator_datetime($commit->getEpoch(), $viewer);
|
||||
$author_name = phutil_tag(
|
||||
'strong',
|
||||
array(
|
||||
'class' => 'diffusion-history-author-name',
|
||||
),
|
||||
$author_name);
|
||||
$authored = pht('%s on %s.', $author_name, $committed);
|
||||
|
||||
$commit_tag = id(new PHUITagView())
|
||||
->setName($commit_name)
|
||||
->setType(PHUITagView::TYPE_SHADE)
|
||||
->setColor(PHUITagView::COLOR_INDIGO)
|
||||
->setBorder(PHUITagView::BORDER_NONE)
|
||||
->setSlimShady(true);
|
||||
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setHeader($commit_desc)
|
||||
->setHref($commit_link)
|
||||
->setDisabled($commit->isUnreachable())
|
||||
->setDescription($message)
|
||||
->setImageURI($author_image)
|
||||
->addAttribute(array($commit_tag, ' ', $diff_tag)) // For Copy Pasta
|
||||
->addAttribute($authored)
|
||||
->setSideColumn(array(
|
||||
$build_view,
|
||||
$browse_button,
|
||||
));
|
||||
|
||||
$list->addItem($item);
|
||||
$cur_date = $new_date;
|
||||
}
|
||||
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
}
|
@@ -1,208 +0,0 @@
|
||||
<?php
|
||||
|
||||
final class DiffusionHistoryTableView extends DiffusionHistoryView {
|
||||
|
||||
public function render() {
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
|
||||
$viewer = $this->getUser();
|
||||
|
||||
$buildables = $this->loadBuildables(
|
||||
mpull($this->getHistory(), 'getCommit'));
|
||||
$has_any_build = false;
|
||||
|
||||
$show_revisions = PhabricatorApplication::isClassInstalledForViewer(
|
||||
'PhabricatorDifferentialApplication',
|
||||
$viewer);
|
||||
|
||||
$handles = $viewer->loadHandles($this->getRequiredHandlePHIDs());
|
||||
|
||||
$graph = null;
|
||||
if ($this->getParents()) {
|
||||
$parents = $this->getParents();
|
||||
|
||||
// If we're filtering parents, remove relationships which point to
|
||||
// commits that are not part of the visible graph. Otherwise, we get
|
||||
// a big tree of nonsense when viewing release branches like "stable"
|
||||
// versus "master".
|
||||
if ($this->getFilterParents()) {
|
||||
foreach ($parents as $key => $nodes) {
|
||||
foreach ($nodes as $nkey => $node) {
|
||||
if (empty($parents[$node])) {
|
||||
unset($parents[$key][$nkey]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$graph = id(new PHUIDiffGraphView())
|
||||
->setIsHead($this->getIsHead())
|
||||
->setIsTail($this->getIsTail())
|
||||
->renderGraph($parents);
|
||||
}
|
||||
|
||||
$show_builds = PhabricatorApplication::isClassInstalledForViewer(
|
||||
'PhabricatorHarbormasterApplication',
|
||||
$this->getUser());
|
||||
|
||||
$rows = array();
|
||||
$ii = 0;
|
||||
foreach ($this->getHistory() as $history) {
|
||||
$epoch = $history->getEpoch();
|
||||
|
||||
if ($epoch) {
|
||||
$committed = $viewer->formatShortDateTime($epoch);
|
||||
} else {
|
||||
$committed = null;
|
||||
}
|
||||
|
||||
$data = $history->getCommitData();
|
||||
$author_phid = $committer = $committer_phid = null;
|
||||
if ($data) {
|
||||
$author_phid = $data->getCommitDetail('authorPHID');
|
||||
$committer_phid = $data->getCommitDetail('committerPHID');
|
||||
$committer = $data->getCommitDetail('committer');
|
||||
}
|
||||
|
||||
if ($author_phid && isset($handles[$author_phid])) {
|
||||
$author = $handles[$author_phid]->renderLink();
|
||||
} else {
|
||||
$author = self::renderName($history->getAuthorName());
|
||||
}
|
||||
|
||||
$different_committer = false;
|
||||
if ($committer_phid) {
|
||||
$different_committer = ($committer_phid != $author_phid);
|
||||
} else if ($committer != '') {
|
||||
$different_committer = ($committer != $history->getAuthorName());
|
||||
}
|
||||
if ($different_committer) {
|
||||
if ($committer_phid && isset($handles[$committer_phid])) {
|
||||
$committer = $handles[$committer_phid]->renderLink();
|
||||
} else {
|
||||
$committer = self::renderName($committer);
|
||||
}
|
||||
$author = hsprintf('%s/%s', $author, $committer);
|
||||
}
|
||||
|
||||
// We can show details once the message and change have been imported.
|
||||
$partial_import = PhabricatorRepositoryCommit::IMPORTED_MESSAGE |
|
||||
PhabricatorRepositoryCommit::IMPORTED_CHANGE;
|
||||
|
||||
$commit = $history->getCommit();
|
||||
if ($commit && $commit->isPartiallyImported($partial_import) && $data) {
|
||||
$summary = AphrontTableView::renderSingleDisplayLine(
|
||||
$history->getSummary());
|
||||
} else {
|
||||
$summary = phutil_tag('em', array(), pht("Importing\xE2\x80\xA6"));
|
||||
}
|
||||
|
||||
$build = null;
|
||||
if ($show_builds) {
|
||||
$buildable = idx($buildables, $commit->getPHID());
|
||||
if ($buildable !== null) {
|
||||
$build = $this->renderBuildable($buildable);
|
||||
$has_any_build = true;
|
||||
}
|
||||
}
|
||||
|
||||
$browse = $this->linkBrowse(
|
||||
$history->getPath(),
|
||||
array(
|
||||
'commit' => $history->getCommitIdentifier(),
|
||||
'branch' => $drequest->getBranch(),
|
||||
'type' => $history->getFileType(),
|
||||
));
|
||||
|
||||
$status = $commit->getAuditStatusObject();
|
||||
$icon = $status->getIcon();
|
||||
$color = $status->getColor();
|
||||
$name = $status->getName();
|
||||
|
||||
$audit_view = id(new PHUIIconView())
|
||||
->setIcon($icon, $color)
|
||||
->addSigil('has-tooltip')
|
||||
->setMetadata(
|
||||
array(
|
||||
'tip' => $name,
|
||||
));
|
||||
|
||||
$revision_link = null;
|
||||
if ($commit) {
|
||||
$revisions = $this->getRevisionsForCommit($commit);
|
||||
if ($revisions) {
|
||||
$revision = head($revisions);
|
||||
$revision_link = phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $revision->getURI(),
|
||||
),
|
||||
$revision->getMonogram());
|
||||
}
|
||||
}
|
||||
|
||||
$rows[] = array(
|
||||
$graph ? $graph[$ii++] : null,
|
||||
$browse,
|
||||
self::linkCommit(
|
||||
$drequest->getRepository(),
|
||||
$history->getCommitIdentifier()),
|
||||
$build,
|
||||
$audit_view,
|
||||
$revision_link,
|
||||
$author,
|
||||
$summary,
|
||||
$committed,
|
||||
);
|
||||
}
|
||||
|
||||
$view = new AphrontTableView($rows);
|
||||
$view->setHeaders(
|
||||
array(
|
||||
null,
|
||||
null,
|
||||
pht('Commit'),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
pht('Author'),
|
||||
pht('Details'),
|
||||
pht('Committed'),
|
||||
));
|
||||
$view->setColumnClasses(
|
||||
array(
|
||||
'threads',
|
||||
'nudgeright',
|
||||
'',
|
||||
'icon',
|
||||
'icon',
|
||||
'',
|
||||
'',
|
||||
'wide',
|
||||
'right',
|
||||
));
|
||||
$view->setColumnVisibility(
|
||||
array(
|
||||
$graph ? true : false,
|
||||
true,
|
||||
true,
|
||||
$has_any_build,
|
||||
true,
|
||||
$show_revisions,
|
||||
));
|
||||
$view->setDeviceVisibility(
|
||||
array(
|
||||
$graph ? true : false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
));
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
}
|
@@ -1,117 +0,0 @@
|
||||
<?php
|
||||
|
||||
abstract class DiffusionHistoryView extends DiffusionView {
|
||||
|
||||
private $history;
|
||||
private $revisions = array();
|
||||
private $handles = array();
|
||||
private $isHead;
|
||||
private $isTail;
|
||||
private $parents;
|
||||
private $filterParents;
|
||||
private $revisionMap;
|
||||
|
||||
public function setHistory(array $history) {
|
||||
assert_instances_of($history, 'DiffusionPathChange');
|
||||
$this->history = $history;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHistory() {
|
||||
return $this->history;
|
||||
}
|
||||
|
||||
public function setHandles(array $handles) {
|
||||
assert_instances_of($handles, 'PhabricatorObjectHandle');
|
||||
$this->handles = $handles;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRequiredHandlePHIDs() {
|
||||
$phids = array();
|
||||
foreach ($this->history as $item) {
|
||||
$data = $item->getCommitData();
|
||||
if ($data) {
|
||||
if ($data->getCommitDetail('authorPHID')) {
|
||||
$phids[$data->getCommitDetail('authorPHID')] = true;
|
||||
}
|
||||
if ($data->getCommitDetail('committerPHID')) {
|
||||
$phids[$data->getCommitDetail('committerPHID')] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return array_keys($phids);
|
||||
}
|
||||
|
||||
public function setParents(array $parents) {
|
||||
$this->parents = $parents;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getParents() {
|
||||
return $this->parents;
|
||||
}
|
||||
|
||||
public function setIsHead($is_head) {
|
||||
$this->isHead = $is_head;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIsHead() {
|
||||
return $this->isHead;
|
||||
}
|
||||
|
||||
public function setIsTail($is_tail) {
|
||||
$this->isTail = $is_tail;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIsTail() {
|
||||
return $this->isTail;
|
||||
}
|
||||
|
||||
public function setFilterParents($filter_parents) {
|
||||
$this->filterParents = $filter_parents;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFilterParents() {
|
||||
return $this->filterParents;
|
||||
}
|
||||
|
||||
public function render() {}
|
||||
|
||||
final protected function getRevisionsForCommit(
|
||||
PhabricatorRepositoryCommit $commit) {
|
||||
|
||||
if ($this->revisionMap === null) {
|
||||
$this->revisionMap = $this->newRevisionMap();
|
||||
}
|
||||
|
||||
return idx($this->revisionMap, $commit->getPHID(), array());
|
||||
}
|
||||
|
||||
private function newRevisionMap() {
|
||||
$history = $this->history;
|
||||
|
||||
$commits = array();
|
||||
foreach ($history as $item) {
|
||||
$commit = $item->getCommit();
|
||||
if ($commit) {
|
||||
|
||||
// NOTE: The "commit" objects in the history list may be undiscovered,
|
||||
// and thus not yet have PHIDs. Only load data for commits with PHIDs.
|
||||
if (!$commit->getPHID()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$commits[] = $commit;
|
||||
}
|
||||
}
|
||||
|
||||
return DiffusionCommitRevisionQuery::loadRevisionMapForCommits(
|
||||
$this->getViewer(),
|
||||
$commits);
|
||||
}
|
||||
|
||||
}
|
@@ -150,7 +150,7 @@ final class DiffusionTagListView extends DiffusionView {
|
||||
if ($commit->getAuthorPHID()) {
|
||||
$author = $this->handles[$commit->getAuthorPHID()]->renderLink();
|
||||
} else if ($commit->getCommitData()) {
|
||||
$author = self::renderName($commit->getCommitData()->getAuthorName());
|
||||
$author = self::renderName($commit->getCommitData()->getAuthorString());
|
||||
} else {
|
||||
$author = self::renderName($tag->getAuthor());
|
||||
}
|
||||
|
@@ -1,140 +0,0 @@
|
||||
<?php
|
||||
|
||||
final class DiffusionTagTableView extends DiffusionView {
|
||||
|
||||
private $tags;
|
||||
private $commits = array();
|
||||
private $handles = array();
|
||||
|
||||
public function setTags($tags) {
|
||||
$this->tags = $tags;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCommits(array $commits) {
|
||||
$this->commits = mpull($commits, null, 'getCommitIdentifier');
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setHandles(array $handles) {
|
||||
$this->handles = $handles;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRequiredHandlePHIDs() {
|
||||
return array_filter(mpull($this->commits, 'getAuthorPHID'));
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
$repository = $drequest->getRepository();
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$buildables = $this->loadBuildables($this->commits);
|
||||
$has_builds = false;
|
||||
|
||||
$rows = array();
|
||||
foreach ($this->tags as $tag) {
|
||||
$commit = idx($this->commits, $tag->getCommitIdentifier());
|
||||
|
||||
$tag_link = phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $drequest->generateURI(
|
||||
array(
|
||||
'action' => 'browse',
|
||||
'commit' => $tag->getName(),
|
||||
)),
|
||||
),
|
||||
$tag->getName());
|
||||
|
||||
$commit_link = phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $drequest->generateURI(
|
||||
array(
|
||||
'action' => 'commit',
|
||||
'commit' => $tag->getCommitIdentifier(),
|
||||
)),
|
||||
),
|
||||
$repository->formatCommitName(
|
||||
$tag->getCommitIdentifier()));
|
||||
|
||||
$author = null;
|
||||
if ($commit && $commit->getAuthorPHID()) {
|
||||
$author = $this->handles[$commit->getAuthorPHID()]->renderLink();
|
||||
} else if ($commit && $commit->getCommitData()) {
|
||||
$author = self::renderName($commit->getCommitData()->getAuthorName());
|
||||
} else {
|
||||
$author = self::renderName($tag->getAuthor());
|
||||
}
|
||||
|
||||
$description = null;
|
||||
if ($tag->getType() == 'git/tag') {
|
||||
// In Git, a tag may be a "real" tag, or just a reference to a commit.
|
||||
// If it's a real tag, use the message on the tag, since this may be
|
||||
// unique data which isn't otherwise available.
|
||||
$description = $tag->getDescription();
|
||||
} else {
|
||||
if ($commit) {
|
||||
$description = $commit->getSummary();
|
||||
} else {
|
||||
$description = $tag->getDescription();
|
||||
}
|
||||
}
|
||||
|
||||
$build = null;
|
||||
if ($commit) {
|
||||
$buildable = idx($buildables, $commit->getPHID());
|
||||
if ($buildable) {
|
||||
$build = $this->renderBuildable($buildable);
|
||||
$has_builds = true;
|
||||
}
|
||||
}
|
||||
|
||||
$history = $this->linkTagHistory($tag->getName());
|
||||
|
||||
$rows[] = array(
|
||||
$history,
|
||||
$tag_link,
|
||||
$commit_link,
|
||||
$build,
|
||||
$author,
|
||||
$description,
|
||||
$viewer->formatShortDateTime($tag->getEpoch()),
|
||||
);
|
||||
}
|
||||
|
||||
$table = id(new AphrontTableView($rows))
|
||||
->setHeaders(
|
||||
array(
|
||||
null,
|
||||
pht('Tag'),
|
||||
pht('Commit'),
|
||||
null,
|
||||
pht('Author'),
|
||||
pht('Description'),
|
||||
pht('Created'),
|
||||
))
|
||||
->setColumnClasses(
|
||||
array(
|
||||
'nudgeright',
|
||||
'pri',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'wide',
|
||||
'right',
|
||||
))
|
||||
->setColumnVisibility(
|
||||
array(
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
$has_builds,
|
||||
));
|
||||
|
||||
return $table->render();
|
||||
}
|
||||
|
||||
}
|
@@ -55,6 +55,12 @@ final class DrydockRepositoryOperation extends DrydockDAO
|
||||
'key_repository' => array(
|
||||
'columns' => array('repositoryPHID', 'operationState'),
|
||||
),
|
||||
'key_state' => array(
|
||||
'columns' => array('operationState'),
|
||||
),
|
||||
'key_author' => array(
|
||||
'columns' => array('authorPHID', 'operationState'),
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
@@ -43,6 +43,7 @@ final class PhabricatorChartFunctionArgumentParser
|
||||
pht(
|
||||
'Chart function "%s" emitted an argument specification ("%s") with '.
|
||||
'no type. Each argument specification must have a valid type.',
|
||||
$this->getFunctionArgumentSignature(),
|
||||
$name));
|
||||
}
|
||||
|
||||
|
@@ -6,7 +6,7 @@ final class PhabricatorFactDaemon extends PhabricatorDaemon {
|
||||
|
||||
protected function run() {
|
||||
$this->setEngines(PhabricatorFactEngine::loadAllEngines());
|
||||
while (!$this->shouldExit()) {
|
||||
do {
|
||||
PhabricatorCaches::destroyRequestCache();
|
||||
|
||||
$iterators = $this->getAllApplicationIterators();
|
||||
@@ -14,9 +14,14 @@ final class PhabricatorFactDaemon extends PhabricatorDaemon {
|
||||
$this->processIteratorWithCursor($iterator_name, $iterator);
|
||||
}
|
||||
|
||||
$this->log(pht('Zzz...'));
|
||||
$this->sleep(15);
|
||||
}
|
||||
$sleep_duration = 60;
|
||||
|
||||
if ($this->shouldHibernate($sleep_duration)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->sleep($sleep_duration);
|
||||
} while (!$this->shouldExit());
|
||||
}
|
||||
|
||||
public static function getAllApplicationIterators() {
|
||||
|
@@ -85,15 +85,8 @@ final class PhabricatorJupyterDocumentEngine
|
||||
if ($utype === $vtype) {
|
||||
switch ($utype) {
|
||||
case 'markdown':
|
||||
$usource = idx($ucell, 'source');
|
||||
if (is_array($usource)) {
|
||||
$usource = implode('', $usource);
|
||||
}
|
||||
|
||||
$vsource = idx($vcell, 'source');
|
||||
if (is_array($vsource)) {
|
||||
$vsource = implode('', $vsource);
|
||||
}
|
||||
$usource = $this->readString($ucell, 'source');
|
||||
$vsource = $this->readString($vcell, 'source');
|
||||
|
||||
$diff = id(new PhutilProseDifferenceEngine())
|
||||
->getDiff($usource, $vsource);
|
||||
@@ -117,8 +110,6 @@ final class PhabricatorJupyterDocumentEngine
|
||||
$vsource = idx($vcell, 'raw');
|
||||
$udisplay = idx($ucell, 'display');
|
||||
$vdisplay = idx($vcell, 'display');
|
||||
$ulabel = idx($ucell, 'label');
|
||||
$vlabel = idx($vcell, 'label');
|
||||
|
||||
$intraline_segments = ArcanistDiffUtils::generateIntralineDiff(
|
||||
$usource,
|
||||
@@ -142,15 +133,15 @@ final class PhabricatorJupyterDocumentEngine
|
||||
$vdisplay,
|
||||
$v_segments);
|
||||
|
||||
$u_content = $this->newCodeLineCell($ucell, $usource);
|
||||
$v_content = $this->newCodeLineCell($vcell, $vsource);
|
||||
list($u_label, $u_content) = $this->newCodeLineCell($ucell, $usource);
|
||||
list($v_label, $v_content) = $this->newCodeLineCell($vcell, $vsource);
|
||||
|
||||
$classes = array(
|
||||
'jupyter-cell-flush',
|
||||
);
|
||||
|
||||
$u_content = $this->newJupyterCell($ulabel, $u_content, $classes);
|
||||
$v_content = $this->newJupyterCell($vlabel, $v_content, $classes);
|
||||
$u_content = $this->newJupyterCell($u_label, $u_content, $classes);
|
||||
$v_content = $this->newJupyterCell($v_label, $v_content, $classes);
|
||||
|
||||
$u_content = $this->newCellContainer($u_content);
|
||||
$v_content = $this->newCellContainer($v_content);
|
||||
@@ -259,10 +250,7 @@ final class PhabricatorJupyterDocumentEngine
|
||||
$hash_input = $cell['raw'];
|
||||
break;
|
||||
case 'markdown':
|
||||
$hash_input = $cell['source'];
|
||||
if (is_array($hash_input)) {
|
||||
$hash_input = implode('', $cell['source']);
|
||||
}
|
||||
$hash_input = $this->readString($cell, 'source');
|
||||
break;
|
||||
default:
|
||||
$hash_input = serialize($cell);
|
||||
@@ -334,7 +322,6 @@ final class PhabricatorJupyterDocumentEngine
|
||||
'be rendered as a Jupyter notebook.'));
|
||||
}
|
||||
|
||||
|
||||
$nbformat = idx($data, 'nbformat');
|
||||
if (!strlen($nbformat)) {
|
||||
throw new Exception(
|
||||
@@ -376,10 +363,7 @@ final class PhabricatorJupyterDocumentEngine
|
||||
foreach ($cells as $cell) {
|
||||
$cell_type = idx($cell, 'cell_type');
|
||||
if ($cell_type === 'markdown') {
|
||||
$source = $cell['source'];
|
||||
if (is_array($source)) {
|
||||
$source = implode('', $source);
|
||||
}
|
||||
$source = $this->readString($cell, 'source');
|
||||
|
||||
// Attempt to split contiguous blocks of markdown into smaller
|
||||
// pieces.
|
||||
@@ -404,11 +388,7 @@ final class PhabricatorJupyterDocumentEngine
|
||||
|
||||
$label = $this->newCellLabel($cell);
|
||||
|
||||
$lines = idx($cell, 'source');
|
||||
if (!is_array($lines)) {
|
||||
$lines = array();
|
||||
}
|
||||
|
||||
$lines = $this->readStringList($cell, 'source');
|
||||
$content = $this->highlightLines($lines);
|
||||
|
||||
$count = count($lines);
|
||||
@@ -526,10 +506,7 @@ final class PhabricatorJupyterDocumentEngine
|
||||
}
|
||||
|
||||
private function newMarkdownCell(array $cell) {
|
||||
$content = idx($cell, 'source');
|
||||
if (!is_array($content)) {
|
||||
$content = array();
|
||||
}
|
||||
$content = $this->readStringList($cell, 'source');
|
||||
|
||||
// TODO: This should ideally highlight as Markdown, but the "md"
|
||||
// highlighter in Pygments is painfully slow and not terribly useful.
|
||||
@@ -549,11 +526,7 @@ final class PhabricatorJupyterDocumentEngine
|
||||
private function newCodeCell(array $cell) {
|
||||
$label = $this->newCellLabel($cell);
|
||||
|
||||
$content = idx($cell, 'source');
|
||||
if (!is_array($content)) {
|
||||
$content = array();
|
||||
}
|
||||
|
||||
$content = $this->readStringList($cell, 'source');
|
||||
$content = $this->highlightLines($content);
|
||||
|
||||
$outputs = array();
|
||||
@@ -660,11 +633,7 @@ final class PhabricatorJupyterDocumentEngine
|
||||
continue;
|
||||
}
|
||||
|
||||
$raw_data = $data[$image_format];
|
||||
if (!is_array($raw_data)) {
|
||||
$raw_data = array($raw_data);
|
||||
}
|
||||
$raw_data = implode('', $raw_data);
|
||||
$raw_data = $this->readString($data, $image_format);
|
||||
|
||||
$content = phutil_tag(
|
||||
'img',
|
||||
@@ -695,11 +664,7 @@ final class PhabricatorJupyterDocumentEngine
|
||||
break;
|
||||
case 'stream':
|
||||
default:
|
||||
$content = idx($output, 'text');
|
||||
if (!is_array($content)) {
|
||||
$content = array();
|
||||
}
|
||||
$content = implode('', $content);
|
||||
$content = $this->readString($output, 'text');
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -761,4 +726,23 @@ final class PhabricatorJupyterDocumentEngine
|
||||
return true;
|
||||
}
|
||||
|
||||
private function readString(array $src, $key) {
|
||||
$list = $this->readStringList($src, $key);
|
||||
return implode('', $list);
|
||||
}
|
||||
|
||||
private function readStringList(array $src, $key) {
|
||||
$list = idx($src, $key);
|
||||
|
||||
if (is_array($list)) {
|
||||
$list = $list;
|
||||
} else if (is_string($list)) {
|
||||
$list = array($list);
|
||||
} else {
|
||||
$list = array();
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -12,4 +12,16 @@ final class PhabricatorFileHasObjectEdgeType extends PhabricatorEdgeType {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getConduitKey() {
|
||||
return 'file.attached-objects';
|
||||
}
|
||||
|
||||
public function getConduitName() {
|
||||
return pht('File Has Object');
|
||||
}
|
||||
|
||||
public function getConduitDescription() {
|
||||
return pht('The source file is attached to the destination object.');
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
final class HarbormasterBuildStepEditAPIMethod
|
||||
extends PhabricatorEditEngineAPIMethod {
|
||||
|
||||
public function getAPIMethodName() {
|
||||
return 'harbormaster.step.edit';
|
||||
}
|
||||
|
||||
public function newEditEngine() {
|
||||
return new HarbormasterBuildStepEditEngine();
|
||||
}
|
||||
|
||||
public function getMethodSummary() {
|
||||
return pht(
|
||||
'Apply transactions to create a new build step or edit an existing '.
|
||||
'one.');
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
final class HarbormasterBuildStepSearchAPIMethod
|
||||
extends PhabricatorSearchEngineAPIMethod {
|
||||
|
||||
public function getAPIMethodName() {
|
||||
return 'harbormaster.step.search';
|
||||
}
|
||||
|
||||
public function newSearchEngine() {
|
||||
return new HarbormasterBuildStepSearchEngine();
|
||||
}
|
||||
|
||||
public function getMethodSummary() {
|
||||
return pht('Retrieve information about Harbormaster build steps.');
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
final class HarbormasterBuildStepEditEngine
|
||||
extends PhabricatorEditEngine {
|
||||
|
||||
const ENGINECONST = 'harbormaster.buildstep';
|
||||
|
||||
private $buildPlan;
|
||||
|
||||
public function setBuildPlan(HarbormasterBuildPlan $build_plan) {
|
||||
$this->buildPlan = $build_plan;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBuildPlan() {
|
||||
if ($this->buildPlan === null) {
|
||||
throw new PhutilInvalidStateException('setBuildPlan');
|
||||
}
|
||||
|
||||
return $this->buildPlan;
|
||||
}
|
||||
|
||||
public function isEngineConfigurable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getEngineName() {
|
||||
return pht('Harbormaster Build Steps');
|
||||
}
|
||||
|
||||
public function getSummaryHeader() {
|
||||
return pht('Edit Harbormaster Build Step Configurations');
|
||||
}
|
||||
|
||||
public function getSummaryText() {
|
||||
return pht('This engine is used to edit Harbormaster build steps.');
|
||||
}
|
||||
|
||||
public function getEngineApplicationClass() {
|
||||
return 'PhabricatorHarbormasterApplication';
|
||||
}
|
||||
|
||||
protected function newEditableObject() {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
|
||||
$plan = HarbormasterBuildPlan::initializeNewBuildPlan($viewer);
|
||||
$this->setBuildPlan($plan);
|
||||
|
||||
$plan = $this->getBuildPlan();
|
||||
|
||||
$step = HarbormasterBuildStep::initializeNewStep($viewer);
|
||||
|
||||
$step->setBuildPlanPHID($plan->getPHID());
|
||||
$step->attachBuildPlan($plan);
|
||||
|
||||
return $step;
|
||||
}
|
||||
|
||||
protected function newObjectQuery() {
|
||||
return new HarbormasterBuildStepQuery();
|
||||
}
|
||||
|
||||
protected function getObjectCreateTitleText($object) {
|
||||
return pht('Create Build Step');
|
||||
}
|
||||
|
||||
protected function getObjectCreateButtonText($object) {
|
||||
return pht('Create Build Step');
|
||||
}
|
||||
|
||||
protected function getObjectEditTitleText($object) {
|
||||
return pht('Edit Build Step: %s', $object->getName());
|
||||
}
|
||||
|
||||
protected function getObjectEditShortText($object) {
|
||||
return pht('Edit Build Step');
|
||||
}
|
||||
|
||||
protected function getObjectCreateShortText() {
|
||||
return pht('Create Build Step');
|
||||
}
|
||||
|
||||
protected function getObjectName() {
|
||||
return pht('Build Step');
|
||||
}
|
||||
|
||||
protected function getEditorURI() {
|
||||
return '/harbormaster/step/edit/';
|
||||
}
|
||||
|
||||
protected function getObjectCreateCancelURI($object) {
|
||||
return '/harbormaster/step/';
|
||||
}
|
||||
|
||||
protected function getObjectViewURI($object) {
|
||||
$id = $object->getID();
|
||||
return "/harbormaster/step/{$id}/";
|
||||
}
|
||||
|
||||
protected function buildCustomEditFields($object) {
|
||||
$fields = array();
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
@@ -54,6 +54,7 @@ final class HarbormasterManagementPublishWorkflow
|
||||
pht(
|
||||
'Object "%s" is not a HarbormasterBuildable (it is a "%s"). '.
|
||||
'Name one or more buildables to publish, like "B123".',
|
||||
$name,
|
||||
get_class($result)));
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
final class HarbormasterBuildStepSearchEngine
|
||||
extends PhabricatorApplicationSearchEngine {
|
||||
|
||||
public function getResultTypeDescription() {
|
||||
return pht('Harbormaster Build Steps');
|
||||
}
|
||||
|
||||
public function getApplicationClassName() {
|
||||
return 'PhabricatorHarbormasterApplication';
|
||||
}
|
||||
|
||||
public function newQuery() {
|
||||
return new HarbormasterBuildStepQuery();
|
||||
}
|
||||
|
||||
protected function buildCustomSearchFields() {
|
||||
return array();
|
||||
}
|
||||
|
||||
protected function buildQueryFromParameters(array $map) {
|
||||
$query = $this->newQuery();
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function getURI($path) {
|
||||
return '/harbormaster/step/'.$path;
|
||||
}
|
||||
|
||||
protected function getBuiltinQueryNames() {
|
||||
return array(
|
||||
'all' => pht('All Steps'),
|
||||
);
|
||||
}
|
||||
|
||||
public function buildSavedQueryFromBuiltin($query_key) {
|
||||
$query = $this->newSavedQuery();
|
||||
$query->setQueryKey($query_key);
|
||||
|
||||
switch ($query_key) {
|
||||
case 'all':
|
||||
return $query;
|
||||
}
|
||||
|
||||
return parent::buildSavedQueryFromBuiltin($query_key);
|
||||
}
|
||||
|
||||
protected function renderResultList(
|
||||
array $plans,
|
||||
PhabricatorSavedQuery $query,
|
||||
array $handles) {
|
||||
assert_instances_of($plans, 'HarbormasterBuildStep');
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@@ -14,7 +14,7 @@ final class HarbormasterWaitForPreviousBuildStepImplementation
|
||||
}
|
||||
|
||||
public function getBuildStepGroupKey() {
|
||||
return HarbormasterPrototypeBuildStepGroup::GROUPKEY;
|
||||
return HarbormasterControlBuildStepGroup::GROUPKEY;
|
||||
}
|
||||
|
||||
public function execute(
|
||||
|
@@ -177,6 +177,8 @@ final class HarbormasterBuildLog
|
||||
pht(
|
||||
'Attempt to load log bytes (%d - %d) failed: failed to '.
|
||||
'load a single contiguous range. Actual ranges: %s.',
|
||||
$offset,
|
||||
$end,
|
||||
implode('; ', $display_ranges)));
|
||||
}
|
||||
|
||||
|
@@ -4,7 +4,8 @@ final class HarbormasterBuildStep extends HarbormasterDAO
|
||||
implements
|
||||
PhabricatorApplicationTransactionInterface,
|
||||
PhabricatorPolicyInterface,
|
||||
PhabricatorCustomFieldInterface {
|
||||
PhabricatorCustomFieldInterface,
|
||||
PhabricatorConduitResultInterface {
|
||||
|
||||
protected $name;
|
||||
protected $description;
|
||||
@@ -169,5 +170,45 @@ final class HarbormasterBuildStep extends HarbormasterDAO
|
||||
return $this;
|
||||
}
|
||||
|
||||
/* -( PhabricatorConduitResultInterface )---------------------------------- */
|
||||
|
||||
|
||||
public function getFieldSpecificationsForConduit() {
|
||||
return array(
|
||||
id(new PhabricatorConduitSearchFieldSpecification())
|
||||
->setKey('name')
|
||||
->setType('string')
|
||||
->setDescription(pht('The name of the build step.')),
|
||||
id(new PhabricatorConduitSearchFieldSpecification())
|
||||
->setKey('description')
|
||||
->setType('remarkup')
|
||||
->setDescription(pht('The build step description.')),
|
||||
id(new PhabricatorConduitSearchFieldSpecification())
|
||||
->setKey('buildPlanPHID')
|
||||
->setType('phid')
|
||||
->setDescription(
|
||||
pht(
|
||||
'The PHID of the build plan this build step belongs to.')),
|
||||
);
|
||||
}
|
||||
|
||||
public function getFieldValuesForConduit() {
|
||||
// T6203: This can be removed once the field becomes non-nullable.
|
||||
$name = $this->getName();
|
||||
$name = phutil_string_cast($name);
|
||||
|
||||
return array(
|
||||
'name' => $name,
|
||||
'description' => array(
|
||||
'raw' => $this->getDescription(),
|
||||
),
|
||||
'buildPlanPHID' => $this->getBuildPlanPHID(),
|
||||
);
|
||||
}
|
||||
|
||||
public function getConduitSearchAttachments() {
|
||||
return array();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
43
src/applications/herald/field/HeraldCommentContentField.php
Normal file
43
src/applications/herald/field/HeraldCommentContentField.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
final class HeraldCommentContentField extends HeraldField {
|
||||
|
||||
const FIELDCONST = 'comment.content';
|
||||
|
||||
public function getHeraldFieldName() {
|
||||
return pht('Comment content');
|
||||
}
|
||||
|
||||
public function getFieldGroupKey() {
|
||||
return HeraldTransactionsFieldGroup::FIELDGROUPKEY;
|
||||
}
|
||||
|
||||
public function getHeraldFieldValue($object) {
|
||||
$adapter = $this->getAdapter();
|
||||
|
||||
$xactions = $adapter->getAppliedTransactions();
|
||||
|
||||
$result = array();
|
||||
foreach ($xactions as $xaction) {
|
||||
if (!$xaction->hasComment()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$comment = $xaction->getComment();
|
||||
$content = $comment->getContent();
|
||||
|
||||
$result[] = $content;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function supportsObject($object) {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getHeraldFieldStandardType() {
|
||||
return self::STANDARD_TEXT_LIST;
|
||||
}
|
||||
|
||||
}
|
@@ -28,6 +28,17 @@ final class HeraldWebhookCallManagementWorkflow
|
||||
'name' => 'secure',
|
||||
'help' => pht('Set the "secure" flag on the request.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'count',
|
||||
'param' => 'N',
|
||||
'help' => pht('Make a total of __N__ copies of the call.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'background',
|
||||
'help' => pht(
|
||||
'Instead of making calls in the foreground, add the tasks '.
|
||||
'to the daemon queue.'),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -41,6 +52,17 @@ final class HeraldWebhookCallManagementWorkflow
|
||||
'Specify a webhook to call with "--id".'));
|
||||
}
|
||||
|
||||
$count = $args->getArg('count');
|
||||
if ($count === null) {
|
||||
$count = 1;
|
||||
}
|
||||
|
||||
if ($count <= 0) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Specified "--count" must be larger than 0.'));
|
||||
}
|
||||
|
||||
$hook = id(new HeraldWebhookQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
@@ -69,6 +91,8 @@ final class HeraldWebhookCallManagementWorkflow
|
||||
$object = head($objects);
|
||||
}
|
||||
|
||||
$is_background = $args->getArg('background');
|
||||
|
||||
$xaction_query =
|
||||
PhabricatorApplicationTransactionQuery::newQueryForObject($object);
|
||||
|
||||
@@ -80,25 +104,49 @@ final class HeraldWebhookCallManagementWorkflow
|
||||
|
||||
$application_phid = id(new PhabricatorHeraldApplication())->getPHID();
|
||||
|
||||
$request = HeraldWebhookRequest::initializeNewWebhookRequest($hook)
|
||||
->setObjectPHID($object->getPHID())
|
||||
->setIsTestAction(true)
|
||||
->setIsSilentAction((bool)$args->getArg('silent'))
|
||||
->setIsSecureAction((bool)$args->getArg('secure'))
|
||||
->setTriggerPHIDs(array($application_phid))
|
||||
->setTransactionPHIDs(mpull($xactions, 'getPHID'))
|
||||
->save();
|
||||
if ($is_background) {
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'Queueing webhook calls...'));
|
||||
$progress_bar = id(new PhutilConsoleProgressBar())
|
||||
->setTotal($count);
|
||||
} else {
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'Calling webhook...'));
|
||||
PhabricatorWorker::setRunAllTasksInProcess(true);
|
||||
}
|
||||
|
||||
PhabricatorWorker::setRunAllTasksInProcess(true);
|
||||
$request->queueCall();
|
||||
for ($ii = 0; $ii < $count; $ii++) {
|
||||
$request = HeraldWebhookRequest::initializeNewWebhookRequest($hook)
|
||||
->setObjectPHID($object->getPHID())
|
||||
->setIsTestAction(true)
|
||||
->setIsSilentAction((bool)$args->getArg('silent'))
|
||||
->setIsSecureAction((bool)$args->getArg('secure'))
|
||||
->setTriggerPHIDs(array($application_phid))
|
||||
->setTransactionPHIDs(mpull($xactions, 'getPHID'))
|
||||
->save();
|
||||
|
||||
$request->reload();
|
||||
$request->queueCall();
|
||||
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'Success, got HTTP %s from webhook.',
|
||||
$request->getErrorCode()));
|
||||
if ($is_background) {
|
||||
$progress_bar->update(1);
|
||||
} else {
|
||||
$request->reload();
|
||||
|
||||
echo tsprintf(
|
||||
"%s\n",
|
||||
pht(
|
||||
'Success, got HTTP %s from webhook.',
|
||||
$request->getErrorCode()));
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_background) {
|
||||
$progress_bar->done();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@@ -199,8 +199,7 @@ final class ManiphestTaskPriority extends ManiphestConstants {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Configuration is not valid. Maniphest priority configurations '.
|
||||
'must be dictionaries.',
|
||||
$config));
|
||||
'must be dictionaries.'));
|
||||
}
|
||||
|
||||
$all_keywords = array();
|
||||
|
@@ -65,6 +65,7 @@ abstract class PhabricatorMailAdapter
|
||||
pht(
|
||||
'Adapter ("%s") is configured for medium "%s", but this is not '.
|
||||
'a supported delivery medium. Supported media are: %s.',
|
||||
get_class($this),
|
||||
$medium,
|
||||
implode(', ', $native_map)));
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@ final class PhabricatorMailAmazonSESAdapter
|
||||
array(
|
||||
'access-key' => 'string',
|
||||
'secret-key' => 'string',
|
||||
'region' => 'string',
|
||||
'endpoint' => 'string',
|
||||
));
|
||||
}
|
||||
@@ -25,6 +26,7 @@ final class PhabricatorMailAmazonSESAdapter
|
||||
return array(
|
||||
'access-key' => null,
|
||||
'secret-key' => null,
|
||||
'region' => null,
|
||||
'endpoint' => null,
|
||||
);
|
||||
}
|
||||
@@ -45,23 +47,33 @@ final class PhabricatorMailAmazonSESAdapter
|
||||
$mailer->Send();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @phutil-external-symbol class SimpleEmailService
|
||||
*/
|
||||
public function executeSend($body) {
|
||||
$key = $this->getOption('access-key');
|
||||
|
||||
$secret = $this->getOption('secret-key');
|
||||
$secret = new PhutilOpaqueEnvelope($secret);
|
||||
|
||||
$region = $this->getOption('region');
|
||||
$endpoint = $this->getOption('endpoint');
|
||||
|
||||
$root = phutil_get_library_root('phabricator');
|
||||
$root = dirname($root);
|
||||
require_once $root.'/externals/amazon-ses/ses.php';
|
||||
$data = array(
|
||||
'Action' => 'SendRawEmail',
|
||||
'RawMessage.Data' => base64_encode($body),
|
||||
);
|
||||
|
||||
$service = new SimpleEmailService($key, $secret, $endpoint);
|
||||
$service->enableUseExceptions(true);
|
||||
return $service->sendRawEmail($body);
|
||||
$data = phutil_build_http_querystring($data);
|
||||
|
||||
$future = id(new PhabricatorAWSSESFuture())
|
||||
->setAccessKey($key)
|
||||
->setSecretKey($secret)
|
||||
->setRegion($region)
|
||||
->setEndpoint($endpoint)
|
||||
->setHTTPMethod('POST')
|
||||
->setData($data);
|
||||
|
||||
$future->resolve();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ final class PhabricatorMailAdapterTestCase
|
||||
array(
|
||||
'access-key' => 'test',
|
||||
'secret-key' => 'test',
|
||||
'region' => 'test',
|
||||
'endpoint' => 'test',
|
||||
),
|
||||
),
|
||||
|
21
src/applications/metamta/future/PhabricatorAWSSESFuture.php
Normal file
21
src/applications/metamta/future/PhabricatorAWSSESFuture.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
final class PhabricatorAWSSESFuture extends PhutilAWSFuture {
|
||||
|
||||
private $parameters;
|
||||
|
||||
public function getServiceName() {
|
||||
return 'ses';
|
||||
}
|
||||
|
||||
protected function didReceiveResult($result) {
|
||||
list($status, $body, $headers) = $result;
|
||||
|
||||
if (!$status->isError()) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
return parent::didReceiveResult($result);
|
||||
}
|
||||
|
||||
}
|
@@ -64,24 +64,6 @@ final class PhabricatorMetaMTAMailQuery
|
||||
$this->actorPHIDs);
|
||||
}
|
||||
|
||||
if ($this->recipientPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'recipient.dst IN (%Ls)',
|
||||
$this->recipientPHIDs);
|
||||
}
|
||||
|
||||
if ($this->actorPHIDs === null && $this->recipientPHIDs === null) {
|
||||
$viewer = $this->getViewer();
|
||||
if (!$viewer->isOmnipotent()) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'edge.dst = %s OR actorPHID = %s',
|
||||
$viewer->getPHID(),
|
||||
$viewer->getPHID());
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->createdMin !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
@@ -102,26 +84,29 @@ final class PhabricatorMetaMTAMailQuery
|
||||
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$joins = parent::buildJoinClauseParts($conn);
|
||||
|
||||
if ($this->actorPHIDs === null && $this->recipientPHIDs === null) {
|
||||
if ($this->shouldJoinRecipients()) {
|
||||
$joins[] = qsprintf(
|
||||
$conn,
|
||||
'LEFT JOIN %T edge ON mail.phid = edge.src AND edge.type = %d',
|
||||
'JOIN %T recipient
|
||||
ON mail.phid = recipient.src
|
||||
AND recipient.type = %d
|
||||
AND recipient.dst IN (%Ls)',
|
||||
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
|
||||
PhabricatorMetaMTAMailHasRecipientEdgeType::EDGECONST);
|
||||
}
|
||||
|
||||
if ($this->recipientPHIDs !== null) {
|
||||
$joins[] = qsprintf(
|
||||
$conn,
|
||||
'LEFT JOIN %T recipient '.
|
||||
'ON mail.phid = recipient.src AND recipient.type = %d',
|
||||
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
|
||||
PhabricatorMetaMTAMailHasRecipientEdgeType::EDGECONST);
|
||||
PhabricatorMetaMTAMailHasRecipientEdgeType::EDGECONST,
|
||||
$this->recipientPHIDs);
|
||||
}
|
||||
|
||||
return $joins;
|
||||
}
|
||||
|
||||
private function shouldJoinRecipients() {
|
||||
if ($this->recipientPHIDs === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getPrimaryTableAlias() {
|
||||
return 'mail';
|
||||
}
|
||||
@@ -134,4 +119,14 @@ final class PhabricatorMetaMTAMailQuery
|
||||
return 'PhabricatorMetaMTAApplication';
|
||||
}
|
||||
|
||||
protected function shouldGroupQueryResultRows() {
|
||||
if ($this->shouldJoinRecipients()) {
|
||||
if (count($this->recipientPHIDs) > 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return parent::shouldGroupQueryResultRows();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -98,7 +98,9 @@ final class PhabricatorNotificationServersConfigType
|
||||
'Notification server configuration describes an invalid host '.
|
||||
'("%s", at index "%s"). This is an "admin" service but it has a '.
|
||||
'"path" property. This property is only valid for "client" '.
|
||||
'services.'));
|
||||
'services.',
|
||||
$host,
|
||||
$index));
|
||||
}
|
||||
|
||||
// We can't guarantee that you didn't just give the same host two
|
||||
|
@@ -85,7 +85,8 @@ final class NuanceGitHubRawEventTestCase
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Expected test file "%s" to contain exactly two sections, '.
|
||||
'but it has more than two sections.'));
|
||||
'but it has more than two sections.',
|
||||
$file));
|
||||
}
|
||||
|
||||
list($input, $expect) = $parts;
|
||||
|
@@ -82,12 +82,15 @@ final class PhabricatorOwnersDetailController
|
||||
))
|
||||
->needCommitData(true)
|
||||
->needAuditRequests(true)
|
||||
->needIdentities(true)
|
||||
->setLimit(10)
|
||||
->execute();
|
||||
$view = id(new PhabricatorAuditListView())
|
||||
->setUser($viewer)
|
||||
->setNoDataString(pht('This package has no open problem commits.'))
|
||||
->setCommits($attention_commits);
|
||||
$view = id(new DiffusionCommitGraphView())
|
||||
->setViewer($viewer)
|
||||
->setCommits($attention_commits)
|
||||
->newObjectItemListView();
|
||||
|
||||
$view->setNoDataString(pht('This package has no open problem commits.'));
|
||||
|
||||
$commit_views[] = array(
|
||||
'view' => $view,
|
||||
@@ -105,13 +108,16 @@ final class PhabricatorOwnersDetailController
|
||||
->withPackagePHIDs(array($package->getPHID()))
|
||||
->needCommitData(true)
|
||||
->needAuditRequests(true)
|
||||
->needIdentities(true)
|
||||
->setLimit(25)
|
||||
->execute();
|
||||
|
||||
$view = id(new PhabricatorAuditListView())
|
||||
->setUser($viewer)
|
||||
$view = id(new DiffusionCommitGraphView())
|
||||
->setViewer($viewer)
|
||||
->setCommits($all_commits)
|
||||
->setNoDataString(pht('No commits in this package.'));
|
||||
->newObjectItemListView();
|
||||
|
||||
$view->setNoDataString(pht('No commits in this package.'));
|
||||
|
||||
$commit_views[] = array(
|
||||
'view' => $view,
|
||||
|
@@ -58,6 +58,7 @@ final class PhabricatorPeopleApplication extends PhabricatorApplication {
|
||||
=> 'PhabricatorPeopleDisableController',
|
||||
'(?P<via>disable)/(?P<id>[1-9]\d*)/'
|
||||
=> 'PhabricatorPeopleDisableController',
|
||||
'spam/(?P<id>[1-9]\d*)/' => 'PhabricatorPeopleSpamController',
|
||||
'empower/(?P<id>[1-9]\d*)/' => 'PhabricatorPeopleEmpowerController',
|
||||
'delete/(?P<id>[1-9]\d*)/' => 'PhabricatorPeopleDeleteController',
|
||||
'rename/(?P<id>[1-9]\d*)/' => 'PhabricatorPeopleRenameController',
|
||||
@@ -99,6 +100,9 @@ final class PhabricatorPeopleApplication extends PhabricatorApplication {
|
||||
PeopleDisableUsersCapability::CAPABILITY => array(
|
||||
'default' => PhabricatorPolicies::POLICY_ADMIN,
|
||||
),
|
||||
PeopleDisableSpamUsersCapability::CAPABILITY => array(
|
||||
'default' => PhabricatorPolicies::POLICY_ADMIN,
|
||||
),
|
||||
PeopleBrowseUserDirectoryCapability::CAPABILITY => array(),
|
||||
);
|
||||
}
|
||||
|
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
final class PeopleDisableSpamUsersCapability
|
||||
extends PhabricatorPolicyCapability {
|
||||
|
||||
const CAPABILITY = 'people.disable_spam.users';
|
||||
|
||||
public function getCapabilityName() {
|
||||
return pht('Can Disable Spammers');
|
||||
}
|
||||
|
||||
public function describeCapabilityRejection() {
|
||||
return pht('You do not have permission to handle spam users.');
|
||||
}
|
||||
|
||||
}
|
@@ -58,13 +58,13 @@ final class PhabricatorPeopleProfileCommitsController
|
||||
->setViewer($viewer)
|
||||
->withAuthorPHIDs(array($user->getPHID()))
|
||||
->needCommitData(true)
|
||||
->needIdentities(true)
|
||||
->setLimit(100)
|
||||
->execute();
|
||||
|
||||
$list = id(new DiffusionCommitListView())
|
||||
$list = id(new DiffusionCommitGraphView())
|
||||
->setViewer($viewer)
|
||||
->setCommits($commits)
|
||||
->setNoDataString(pht('No recent commits.'));
|
||||
->setCommits($commits);
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
@@ -93,6 +93,10 @@ final class PhabricatorPeopleProfileManageController
|
||||
PeopleDisableUsersCapability::CAPABILITY);
|
||||
$can_disable = ($has_disable && !$is_self);
|
||||
|
||||
$has_disable_spam_capability = $this->hasApplicationCapability(
|
||||
PeopleDisableSpamUsersCapability::CAPABILITY);
|
||||
$can_disable_spam = ($has_disable_spam_capability && !$is_self);
|
||||
|
||||
$id = $user->getID();
|
||||
|
||||
$welcome_engine = id(new PhabricatorPeopleWelcomeMailEngine())
|
||||
@@ -193,6 +197,14 @@ final class PhabricatorPeopleProfileManageController
|
||||
->setWorkflow(true)
|
||||
->setHref($this->getApplicationURI('disable/'.$id.'/')));
|
||||
|
||||
$curtain->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setIcon('fa-shield')
|
||||
->setName(pht('Disable as Spam'))
|
||||
->setDisabled(!$can_disable_spam)
|
||||
->setWorkflow(true)
|
||||
->setHref($this->getApplicationURI('spam/'.$id.'/')));
|
||||
|
||||
$curtain->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setIcon('fa-times')
|
||||
|
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
final class PhabricatorPeopleSpamController
|
||||
extends PhabricatorPeopleController {
|
||||
|
||||
public function shouldRequireAdmin() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$user = id(new PhabricatorPeopleQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->executeOne();
|
||||
if (!$user) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$this->requireApplicationCapability(
|
||||
PeopleDisableUsersCapability::CAPABILITY);
|
||||
|
||||
$actor = $viewer;
|
||||
$done_uri = $this->getApplicationURI("manage/{$id}/");
|
||||
|
||||
if ($viewer->getPHID() == $user->getPHID()) {
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Something Stays Your Hand'))
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'Try as you might, you find you can not disable your own account.'))
|
||||
->addCancelButton($done_uri, pht('Curses!'));
|
||||
}
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
// Disable the account.
|
||||
if (!$user->getIsDisabled()) {
|
||||
$xactions = array();
|
||||
$xactions[] = id(new PhabricatorUserTransaction())
|
||||
->setTransactionType(PhabricatorUserDisableTransaction::TRANSACTIONTYPE)
|
||||
->setNewValue(true);
|
||||
|
||||
id(new PhabricatorUserTransactionEditor())
|
||||
->setActor($actor)
|
||||
->setActingAsPHID($viewer->getPHID())
|
||||
->setContentSourceFromRequest($request)
|
||||
->setContinueOnMissingFields(true)
|
||||
->setContinueOnNoEffect(true)
|
||||
->applyTransactions($user, $xactions);
|
||||
}
|
||||
|
||||
// Set profile info to spam and blank to everything else.
|
||||
{
|
||||
$xactions = array();
|
||||
$xactions[] = id(new PhabricatorUserTransaction())
|
||||
->setTransactionType(PhabricatorUserDisableSpamTransaction::TRANSACTIONTYPE);
|
||||
|
||||
id(new PhabricatorUserTransactionEditor())
|
||||
->setActor($actor)
|
||||
->setActingAsPHID($viewer->getPHID())
|
||||
->setContentSourceFromRequest($request)
|
||||
->setContinueOnMissingFields(true)
|
||||
->setContinueOnNoEffect(true)
|
||||
->applyTransactions($user, $xactions);
|
||||
}
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($done_uri);
|
||||
}
|
||||
|
||||
$title = pht('Disable as Spam?');
|
||||
$short_title = pht('Disable as Spam');
|
||||
|
||||
$body = pht(
|
||||
'Is %s\'s profile spam?<br />All the profile info will be erased and '.
|
||||
'they will no longer be able to access Phabricator.',
|
||||
phutil_tag('strong', array(), $user->getUsername()));
|
||||
|
||||
$submit = pht('Disable as Spam');
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle($title)
|
||||
->setShortTitle($short_title)
|
||||
->appendParagraph($body)
|
||||
->addCancelButton($done_uri)
|
||||
->addSubmitButton($submit);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
final class PhabricatorUserDisableSpamTransaction
|
||||
extends PhabricatorUserTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'user.disable_spam';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
$user = $object;
|
||||
return (string)$user->getRealName();
|
||||
}
|
||||
|
||||
public function generateNewValue($object, $value) {
|
||||
// Deliberately not using 'spam'.
|
||||
// This way we can use this button even for accounts that
|
||||
// have been already manually renamed to 'spam'.
|
||||
// Otherwise when the name clash with the existing name
|
||||
// none of the changes happens.
|
||||
return 'disabled_spam';
|
||||
}
|
||||
|
||||
public function applyInternalEffects($object, $value) {
|
||||
$user = $object;
|
||||
$user->setRealName('spam');
|
||||
$profile = $user->loadUserProfile();
|
||||
$profile->setBlurb('');
|
||||
$profile->setTitle('');
|
||||
$profile->setIcon('');
|
||||
|
||||
$file = PhabricatorFile::loadBuiltin($user, 'profile.png');
|
||||
$user->setProfileImagePHID($file->getPHID());
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
return pht(
|
||||
'%s set this user as spam.',
|
||||
$this->renderAuthor());
|
||||
}
|
||||
|
||||
public function shouldHideForFeed() {
|
||||
// Don't publish feed stories about handling spam.
|
||||
return true;
|
||||
}
|
||||
|
||||
public function validateTransactions($object, array $xactions) {
|
||||
$errors = array();
|
||||
|
||||
foreach ($xactions as $xaction) {
|
||||
|
||||
// You must have the "Can Disable Spam Users" permission to disable a user as spam.
|
||||
$this->requireApplicationCapability(
|
||||
PeopleDisableSpamUsersCapability::CAPABILITY);
|
||||
|
||||
if ($this->getActingAsPHID() === $object->getPHID()) {
|
||||
$errors[] = $this->newInvalidError(
|
||||
pht('You can not disable your own account as spam.'));
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
public function getRequiredCapabilities(
|
||||
$object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -104,6 +104,10 @@ final class PhabricatorHandleList
|
||||
->setHandleList($this);
|
||||
}
|
||||
|
||||
public function newListView() {
|
||||
return id(new FuelHandleListView())
|
||||
->addHandleList($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a @{class:PHUIHandleView} which can render a specific handle.
|
||||
|
@@ -61,6 +61,8 @@ final class PholioMockViewController extends PholioController {
|
||||
new PholioTransactionQuery());
|
||||
$timeline->setMock($mock);
|
||||
|
||||
$timeline->setQuoteRef($mock->getMonogram());
|
||||
|
||||
$curtain = $this->buildCurtainView($mock);
|
||||
$details = $this->buildDescriptionView($mock);
|
||||
|
||||
@@ -80,6 +82,7 @@ final class PholioMockViewController extends PholioController {
|
||||
->appendChild($mock_view);
|
||||
|
||||
$add_comment = $this->buildAddCommentView($mock, $comment_form_id);
|
||||
$add_comment->setTransactionTimeline($timeline);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb($mock->getMonogram(), $mock->getURI());
|
||||
|
@@ -71,7 +71,6 @@ final class PhragmentRevertController extends PhragmentController {
|
||||
->appendParagraph(pht(
|
||||
'Reverting this fragment to version %d will create a new version of '.
|
||||
'the fragment. It will not delete any version history.',
|
||||
$version->getSequence(),
|
||||
$version->getSequence()));
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
}
|
||||
|
@@ -401,7 +401,7 @@ final class PhrictionDocumentController
|
||||
|
||||
$view->addProperty(
|
||||
pht('Last Edited'),
|
||||
phabricator_datetime($content->getDateCreated(), $viewer));
|
||||
phabricator_dual_datetime($content->getDateCreated(), $viewer));
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
@@ -266,8 +266,7 @@ final class PhabricatorPolicy
|
||||
return pht(
|
||||
'Members of a particular project can take this action. (You '.
|
||||
'can not see this object, so the name of this project is '.
|
||||
'restricted.)',
|
||||
$handle->getFullName());
|
||||
'restricted.)');
|
||||
} else if ($type == PhabricatorPeopleUserPHIDType::TYPECONST) {
|
||||
return pht(
|
||||
'%s can take this action.',
|
||||
|
@@ -224,6 +224,7 @@ final class PhabricatorProjectIconSet
|
||||
'Icon key "%s" is not a valid icon key. Icon keys must be 1-32 '.
|
||||
'characters long and contain only lowercase letters. For example, '.
|
||||
'"%s" and "%s" are reasonable keys.',
|
||||
$value['key'],
|
||||
'tag',
|
||||
'group'));
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user