Page MenuHome GnuPG

No OneTemporary

diff --git a/src/applications/phortune/controller/PhortuneSubscriptionViewController.php b/src/applications/phortune/controller/PhortuneSubscriptionViewController.php
index 4fbfac3bd..aab6b819e 100644
--- a/src/applications/phortune/controller/PhortuneSubscriptionViewController.php
+++ b/src/applications/phortune/controller/PhortuneSubscriptionViewController.php
@@ -1,203 +1,203 @@
<?php
final class PhortuneSubscriptionViewController extends PhortuneController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$subscription = id(new PhortuneSubscriptionQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->needTriggers(true)
->executeOne();
if (!$subscription) {
return new Aphront404Response();
}
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$subscription,
PhabricatorPolicyCapability::CAN_EDIT);
$is_merchant = (bool)$request->getURIData('merchantID');
$merchant = $subscription->getMerchant();
$account = $subscription->getAccount();
$account_id = $account->getID();
$subscription_id = $subscription->getID();
- $title = pht('Subscription: %s', $subscription->getSubscriptionName());
+ $title = $subscription->getSubscriptionFullName();
$header = id(new PHUIHeaderView())
- ->setHeader($subscription->getSubscriptionName());
+ ->setHeader($title);
$actions = id(new PhabricatorActionListView())
->setUser($viewer)
->setObjectURI($request->getRequestURI());
$edit_uri = $this->getApplicationURI(
"{$account_id}/subscription/edit/{$subscription_id}/");
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('fa-pencil')
->setName(pht('Edit Subscription'))
->setHref($edit_uri)
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$crumbs = $this->buildApplicationCrumbs();
if ($is_merchant) {
$this->addMerchantCrumb($crumbs, $merchant);
} else {
$this->addAccountCrumb($crumbs, $account);
}
- $crumbs->addTextCrumb(pht('Subscription %d', $subscription->getID()));
+ $crumbs->addTextCrumb($subscription->getSubscriptionCrumbName());
$properties = id(new PHUIPropertyListView())
->setUser($viewer)
->setActionList($actions);
$next_invoice = $subscription->getTrigger()->getNextEventPrediction();
$properties->addProperty(
pht('Next Invoice'),
phabricator_datetime($next_invoice, $viewer));
$default_method = $subscription->getDefaultPaymentMethodPHID();
if ($default_method) {
$handles = $this->loadViewerHandles(array($default_method));
$autopay_method = $handles[$default_method]->renderLink();
} else {
$autopay_method = phutil_tag(
'em',
array(),
pht('No Autopay Method Configured'));
}
$properties->addProperty(
pht('Autopay With'),
$autopay_method);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
$due_box = $this->buildDueInvoices($subscription, $is_merchant);
$invoice_box = $this->buildPastInvoices($subscription, $is_merchant);
return $this->buildApplicationPage(
array(
$crumbs,
$object_box,
$due_box,
$invoice_box,
),
array(
'title' => $title,
));
}
private function buildDueInvoices(
PhortuneSubscription $subscription,
$is_merchant) {
$viewer = $this->getViewer();
$invoices = id(new PhortuneCartQuery())
->setViewer($viewer)
->withSubscriptionPHIDs(array($subscription->getPHID()))
->needPurchases(true)
->withInvoices(true)
->execute();
$phids = array();
foreach ($invoices as $invoice) {
$phids[] = $invoice->getPHID();
$phids[] = $invoice->getMerchantPHID();
foreach ($invoice->getPurchases() as $purchase) {
$phids[] = $purchase->getPHID();
}
}
$handles = $this->loadViewerHandles($phids);
$invoice_table = id(new PhortuneOrderTableView())
->setUser($viewer)
->setCarts($invoices)
->setIsInvoices(true)
->setIsMerchantView($is_merchant)
->setHandles($handles);
$invoice_header = id(new PHUIHeaderView())
->setHeader(pht('Invoices Due'));
return id(new PHUIObjectBoxView())
->setHeader($invoice_header)
->appendChild($invoice_table);
}
private function buildPastInvoices(
PhortuneSubscription $subscription,
$is_merchant) {
$viewer = $this->getViewer();
$invoices = id(new PhortuneCartQuery())
->setViewer($viewer)
->withSubscriptionPHIDs(array($subscription->getPHID()))
->needPurchases(true)
->withStatuses(
array(
PhortuneCart::STATUS_PURCHASING,
PhortuneCart::STATUS_CHARGED,
PhortuneCart::STATUS_HOLD,
PhortuneCart::STATUS_REVIEW,
PhortuneCart::STATUS_PURCHASED,
))
->setLimit(50)
->execute();
$phids = array();
foreach ($invoices as $invoice) {
$phids[] = $invoice->getPHID();
foreach ($invoice->getPurchases() as $purchase) {
$phids[] = $purchase->getPHID();
}
}
$handles = $this->loadViewerHandles($phids);
$invoice_table = id(new PhortuneOrderTableView())
->setUser($viewer)
->setCarts($invoices)
->setHandles($handles);
$account = $subscription->getAccount();
$merchant = $subscription->getMerchant();
$account_id = $account->getID();
$merchant_id = $merchant->getID();
$subscription_id = $subscription->getID();
if ($is_merchant) {
$invoices_uri = $this->getApplicationURI(
"merchant/{$merchant_id}/subscription/order/{$subscription_id}/");
} else {
$invoices_uri = $this->getApplicationURI(
"{$account_id}/subscription/order/{$subscription_id}/");
}
$invoice_header = id(new PHUIHeaderView())
->setHeader(pht('Past Invoices'))
->addActionLink(
id(new PHUIButtonView())
->setTag('a')
->setIcon(
id(new PHUIIconView())
->setIconFont('fa-list'))
->setHref($invoices_uri)
->setText(pht('View All Invoices')));
return id(new PHUIObjectBoxView())
->setHeader($invoice_header)
->appendChild($invoice_table);
}
}
diff --git a/src/applications/phortune/phid/PhortunePurchasePHIDType.php b/src/applications/phortune/phid/PhortunePurchasePHIDType.php
index 5018a4716..b00faa585 100644
--- a/src/applications/phortune/phid/PhortunePurchasePHIDType.php
+++ b/src/applications/phortune/phid/PhortunePurchasePHIDType.php
@@ -1,38 +1,38 @@
<?php
final class PhortunePurchasePHIDType extends PhabricatorPHIDType {
const TYPECONST = 'PRCH';
public function getTypeName() {
return pht('Phortune Purchase');
}
public function newObject() {
return new PhortunePurchase();
}
protected function buildQueryForObjects(
PhabricatorObjectQuery $query,
array $phids) {
return id(new PhortunePurchaseQuery())
->withPHIDs($phids);
}
public function loadHandles(
PhabricatorHandleQuery $query,
array $handles,
array $objects) {
foreach ($handles as $phid => $handle) {
$purchase = $objects[$phid];
$id = $purchase->getID();
$handle->setName($purchase->getFullDisplayName());
- $handle->setURI("/phortune/purchase/{$id}/");
+ $handle->setURI($purchase->getURI());
}
}
}
diff --git a/src/applications/phortune/product/PhortuneProductImplementation.php b/src/applications/phortune/product/PhortuneProductImplementation.php
index 56138befd..27f280ac5 100644
--- a/src/applications/phortune/product/PhortuneProductImplementation.php
+++ b/src/applications/phortune/product/PhortuneProductImplementation.php
@@ -1,38 +1,44 @@
<?php
abstract class PhortuneProductImplementation {
abstract public function loadImplementationsForRefs(
PhabricatorUser $viewer,
array $refs);
abstract public function getRef();
abstract public function getName(PhortuneProduct $product);
abstract public function getPriceAsCurrency(PhortuneProduct $product);
protected function getContentSource() {
return PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_PHORTUNE,
array());
}
public function getPurchaseName(
PhortuneProduct $product,
PhortunePurchase $purchase) {
return $this->getName($product);
}
public function didPurchaseProduct(
PhortuneProduct $product,
PhortunePurchase $purchase) {
return;
}
public function didRefundProduct(
PhortuneProduct $product,
PhortunePurchase $purchase,
PhortuneCurrency $amount) {
return;
}
+ public function getPurchaseURI(
+ PhortuneProduct $product,
+ PhortunePurchase $purchase) {
+ return null;
+ }
+
}
diff --git a/src/applications/phortune/product/PhortuneSubscriptionProduct.php b/src/applications/phortune/product/PhortuneSubscriptionProduct.php
index 9b83cbf6c..b63cc7c21 100644
--- a/src/applications/phortune/product/PhortuneSubscriptionProduct.php
+++ b/src/applications/phortune/product/PhortuneSubscriptionProduct.php
@@ -1,91 +1,107 @@
<?php
final class PhortuneSubscriptionProduct
extends PhortuneProductImplementation {
private $viewer;
private $subscriptionPHID;
private $subscription;
public function setSubscriptionPHID($subscription_phid) {
$this->subscriptionPHID = $subscription_phid;
return $this;
}
public function getSubscriptionPHID() {
return $this->subscriptionPHID;
}
public function setSubscription(PhortuneSubscription $subscription) {
$this->subscription = $subscription;
return $this;
}
public function getSubscription() {
return $this->subscription;
}
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function getRef() {
return $this->getSubscriptionPHID();
}
public function getName(PhortuneProduct $product) {
return $this->getSubscription()->getSubscriptionName();
}
public function getPriceAsCurrency(PhortuneProduct $product) {
// Prices are calculated by the SubscriptionImplementation.
return PhortuneCurrency::newEmptyCurrency();
}
public function didPurchaseProduct(
PhortuneProduct $product,
PhortunePurchase $purchase) {
// TODO: Callback the subscription.
return;
}
public function didRefundProduct(
PhortuneProduct $product,
PhortunePurchase $purchase,
PhortuneCurrency $amount) {
// TODO: Callback the subscription.
return;
}
+ public function getPurchaseName(
+ PhortuneProduct $product,
+ PhortunePurchase $purchase) {
+ return $this->getSubscription()->getPurchaseName(
+ $product,
+ $purchase);
+ }
+
+ public function getPurchaseURI(
+ PhortuneProduct $product,
+ PhortunePurchase $purchase) {
+ return $this->getSubscription()->getPurchaseURI(
+ $product,
+ $purchase);
+ }
+
public function loadImplementationsForRefs(
PhabricatorUser $viewer,
array $refs) {
$subscriptions = id(new PhortuneSubscriptionQuery())
->setViewer($viewer)
->withPHIDs($refs)
->execute();
$subscriptions = mpull($subscriptions, null, 'getPHID');
$objects = array();
foreach ($refs as $ref) {
$subscription = idx($subscriptions, $ref);
if (!$subscription) {
continue;
}
$objects[] = id(new PhortuneSubscriptionProduct())
->setViewer($viewer)
->setSubscriptionPHID($ref)
->setSubscription($subscription);
}
return $objects;
}
}
diff --git a/src/applications/phortune/storage/PhortuneProduct.php b/src/applications/phortune/storage/PhortuneProduct.php
index 3b74cc8c1..a3f89ffec 100644
--- a/src/applications/phortune/storage/PhortuneProduct.php
+++ b/src/applications/phortune/storage/PhortuneProduct.php
@@ -1,112 +1,117 @@
<?php
/**
* A product is something users can purchase.
*/
final class PhortuneProduct extends PhortuneDAO
implements PhabricatorPolicyInterface {
protected $productClassKey;
protected $productClass;
protected $productRefKey;
protected $productRef;
protected $metadata = array();
private $implementation = self::ATTACHABLE;
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'metadata' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'productClassKey' => 'bytes12',
'productClass' => 'text128',
'productRefKey' => 'bytes12',
'productRef' => 'text128',
),
self::CONFIG_KEY_SCHEMA => array(
'key_product' => array(
'columns' => array('productClassKey', 'productRefKey'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhortuneProductPHIDType::TYPECONST);
}
public static function initializeNewProduct() {
return id(new PhortuneProduct());
}
public function attachImplementation(PhortuneProductImplementation $impl) {
$this->implementation = $impl;
}
public function getImplementation() {
return $this->assertAttached($this->implementation);
}
public function save() {
$this->productClassKey = PhabricatorHash::digestForIndex(
$this->productClass);
$this->productRefKey = PhabricatorHash::digestForIndex(
$this->productRef);
return parent::save();
}
public function getPriceAsCurrency() {
return $this->getImplementation()->getPriceAsCurrency($this);
}
public function getProductName() {
return $this->getImplementation()->getName($this);
}
public function getPurchaseName(PhortunePurchase $purchase) {
return $this->getImplementation()->getPurchaseName($this, $purchase);
}
public function didPurchaseProduct(PhortunePurchase $purchase) {
return $this->getImplementation()->didPurchaseProduct($this, $purchase);
}
public function didRefundProduct(
PhortunePurchase $purchase,
PhortuneCurrency $amount) {
return $this->getImplementation()->didRefundProduct(
$this,
$purchase,
$amount);
}
+ public function getPurchaseURI(PhortunePurchase $purchase) {
+ return $this->getImplementation()->getPurchaseURI(
+ $this,
+ $purchase);
+ }
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::POLICY_USER;
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}
diff --git a/src/applications/phortune/storage/PhortunePurchase.php b/src/applications/phortune/storage/PhortunePurchase.php
index 67a1cf011..ff0a5287e 100644
--- a/src/applications/phortune/storage/PhortunePurchase.php
+++ b/src/applications/phortune/storage/PhortunePurchase.php
@@ -1,126 +1,130 @@
<?php
/**
* A purchase represents a user buying something.
*/
final class PhortunePurchase extends PhortuneDAO
implements PhabricatorPolicyInterface {
const STATUS_PENDING = 'purchase:pending';
const STATUS_PROCESSING = 'purchase:processing';
const STATUS_ACTIVE = 'purchase:active';
const STATUS_CANCELED = 'purchase:canceled';
const STATUS_DELIVERED = 'purchase:delivered';
const STATUS_FAILED = 'purchase:failed';
protected $productPHID;
protected $accountPHID;
protected $authorPHID;
protected $cartPHID;
protected $basePriceAsCurrency;
protected $quantity;
protected $status;
protected $metadata = array();
private $cart = self::ATTACHABLE;
private $product = self::ATTACHABLE;
public static function initializeNewPurchase(
PhabricatorUser $actor,
PhortuneProduct $product) {
return id(new PhortunePurchase())
->setAuthorPHID($actor->getPHID())
->setProductPHID($product->getPHID())
->attachProduct($product)
->setQuantity(1)
->setStatus(self::STATUS_PENDING)
->setBasePriceAsCurrency($product->getPriceAsCurrency());
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'metadata' => self::SERIALIZATION_JSON,
),
self::CONFIG_APPLICATION_SERIALIZERS => array(
'basePriceAsCurrency' => new PhortuneCurrencySerializer(),
),
self::CONFIG_COLUMN_SCHEMA => array(
'cartPHID' => 'phid?',
'basePriceAsCurrency' => 'text64',
'quantity' => 'uint32',
'status' => 'text32',
),
self::CONFIG_KEY_SCHEMA => array(
'key_cart' => array(
'columns' => array('cartPHID'),
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhortunePurchasePHIDType::TYPECONST);
}
public function attachCart(PhortuneCart $cart) {
$this->cart = $cart;
return $this;
}
public function getCart() {
return $this->assertAttached($this->cart);
}
public function attachProduct(PhortuneProduct $product) {
$this->product = $product;
return $this;
}
public function getProduct() {
return $this->assertAttached($this->product);
}
public function getFullDisplayName() {
return $this->getProduct()->getPurchaseName($this);
}
+ public function getURI() {
+ return $this->getProduct()->getPurchaseURI($this);
+ }
+
public function getTotalPriceAsCurrency() {
return $this->getBasePriceAsCurrency();
}
public function getMetadataValue($key, $default = null) {
return idx($this->metadata, $key, $default);
}
public function setMetadataValue($key, $value) {
$this->metadata[$key] = $value;
return $this;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return $this->getCart()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getCart()->hasAutomaticCapability($capability, $viewer);
}
public function describeAutomaticCapability($capability) {
return pht('Purchases have the policies of their cart.');
}
}
diff --git a/src/applications/phortune/storage/PhortuneSubscription.php b/src/applications/phortune/storage/PhortuneSubscription.php
index 036a31e0f..638ce85ba 100644
--- a/src/applications/phortune/storage/PhortuneSubscription.php
+++ b/src/applications/phortune/storage/PhortuneSubscription.php
@@ -1,242 +1,268 @@
<?php
/**
* A subscription bills users regularly.
*/
final class PhortuneSubscription extends PhortuneDAO
implements PhabricatorPolicyInterface {
const STATUS_ACTIVE = 'active';
const STATUS_CANCELLED = 'cancelled';
protected $accountPHID;
protected $merchantPHID;
protected $triggerPHID;
protected $authorPHID;
protected $defaultPaymentMethodPHID;
protected $subscriptionClassKey;
protected $subscriptionClass;
protected $subscriptionRefKey;
protected $subscriptionRef;
protected $status;
protected $metadata = array();
private $merchant = self::ATTACHABLE;
private $account = self::ATTACHABLE;
private $implementation = self::ATTACHABLE;
private $trigger = self::ATTACHABLE;
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'metadata' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'defaultPaymentMethodPHID' => 'phid?',
'subscriptionClassKey' => 'bytes12',
'subscriptionClass' => 'text128',
'subscriptionRefKey' => 'bytes12',
'subscriptionRef' => 'text128',
'status' => 'text32',
),
self::CONFIG_KEY_SCHEMA => array(
'key_subscription' => array(
'columns' => array('subscriptionClassKey', 'subscriptionRefKey'),
'unique' => true,
),
'key_account' => array(
'columns' => array('accountPHID'),
),
'key_merchant' => array(
'columns' => array('merchantPHID'),
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhortuneSubscriptionPHIDType::TYPECONST);
}
public static function initializeNewSubscription(
PhortuneAccount $account,
PhortuneMerchant $merchant,
PhabricatorUser $author,
PhortuneSubscriptionImplementation $implementation,
PhabricatorTriggerClock $clock) {
$trigger = id(new PhabricatorWorkerTrigger())
->setClock($clock);
return id(new PhortuneSubscription())
->setStatus(self::STATUS_ACTIVE)
->setAccountPHID($account->getPHID())
->attachAccount($account)
->setMerchantPHID($merchant->getPHID())
->attachMerchant($merchant)
->setAuthorPHID($author->getPHID())
->setSubscriptionClass(get_class($implementation))
->setSubscriptionRef($implementation->getRef())
->attachImplementation($implementation)
->attachTrigger($trigger);
}
public function attachImplementation(
PhortuneSubscriptionImplementation $impl) {
$this->implementation = $impl;
return $this;
}
public function getImplementation() {
return $this->assertAttached($this->implementation);
}
public function attachAccount(PhortuneAccount $account) {
$this->account = $account;
return $this;
}
public function getAccount() {
return $this->assertAttached($this->account);
}
public function attachMerchant(PhortuneMerchant $merchant) {
$this->merchant = $merchant;
return $this;
}
public function getMerchant() {
return $this->assertAttached($this->merchant);
}
public function attachTrigger(PhabricatorWorkerTrigger $trigger) {
$this->trigger = $trigger;
return $this;
}
public function getTrigger() {
return $this->assertAttached($this->trigger);
}
public function save() {
$this->subscriptionClassKey = PhabricatorHash::digestForIndex(
$this->subscriptionClass);
$this->subscriptionRefKey = PhabricatorHash::digestForIndex(
$this->subscriptionRef);
$is_new = (!$this->getID());
$this->openTransaction();
// If we're saving this subscription for the first time, we're also
// going to set up the trigger for it.
if ($is_new) {
$trigger_phid = PhabricatorPHID::generateNewPHID(
PhabricatorWorkerTriggerPHIDType::TYPECONST);
$this->setTriggerPHID($trigger_phid);
}
$result = parent::save();
if ($is_new) {
$trigger_action = new PhabricatorScheduleTaskTriggerAction(
array(
'class' => 'PhortuneSubscriptionWorker',
'data' => array(
'subscriptionPHID' => $this->getPHID(),
),
'options' => array(
'objectPHID' => $this->getPHID(),
'priority' => PhabricatorWorker::PRIORITY_BULK,
),
));
$trigger = $this->getTrigger();
$trigger->setPHID($trigger_phid);
$trigger->setAction($trigger_action);
$trigger->save();
}
$this->saveTransaction();
return $result;
}
public function getSubscriptionName() {
return $this->getImplementation()->getName($this);
}
+ public function getSubscriptionFullName() {
+ return $this->getImplementation()->getFullName($this);
+ }
+
+ public function getSubscriptionCrumbName() {
+ return $this->getImplementation()->getCrumbName($this);
+ }
+
public function getCartName(PhortuneCart $cart) {
return $this->getImplementation()->getCartName($this, $cart);
}
public function getURI() {
$account_id = $this->getAccount()->getID();
$id = $this->getID();
return "/phortune/{$account_id}/subscription/view/{$id}/";
}
public function getMerchantURI() {
$merchant_id = $this->getMerchant()->getID();
$id = $this->getID();
return "/phortune/merchant/{$merchant_id}/subscription/view/{$id}/";
}
public function getCostForBillingPeriodAsCurrency($start_epoch, $end_epoch) {
return $this->getImplementation()->getCostForBillingPeriodAsCurrency(
$this,
$start_epoch,
$end_epoch);
}
+ public function getPurchaseName(
+ PhortuneProduct $product,
+ PhortunePurchase $purchase) {
+ return $this->getImplementation()->getPurchaseName(
+ $this,
+ $product,
+ $purchase);
+ }
+
+ public function getPurchaseURI(
+ PhortuneProduct $product,
+ PhortunePurchase $purchase) {
+ return $this->getImplementation()->getPurchaseURI(
+ $this,
+ $product,
+ $purchase);
+ }
+
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
// NOTE: Both view and edit use the account's edit policy. We punch a hole
// through this for merchants, below.
return $this
->getAccount()
->getPolicy(PhabricatorPolicyCapability::CAN_EDIT);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
if ($this->getAccount()->hasAutomaticCapability($capability, $viewer)) {
return true;
}
// If the viewer controls the merchant this subscription bills to, they can
// view the subscription.
if ($capability == PhabricatorPolicyCapability::CAN_VIEW) {
$can_admin = PhabricatorPolicyFilter::hasCapability(
$viewer,
$this->getMerchant(),
PhabricatorPolicyCapability::CAN_EDIT);
if ($can_admin) {
return true;
}
}
return false;
}
public function describeAutomaticCapability($capability) {
return array(
pht('Subscriptions inherit the policies of the associated account.'),
pht(
'The merchant you are subscribed with can review and manage the '.
'subscription.'),
);
}
}
diff --git a/src/applications/phortune/subscription/PhortuneSubscriptionImplementation.php b/src/applications/phortune/subscription/PhortuneSubscriptionImplementation.php
index 0abc346a8..918908748 100644
--- a/src/applications/phortune/subscription/PhortuneSubscriptionImplementation.php
+++ b/src/applications/phortune/subscription/PhortuneSubscriptionImplementation.php
@@ -1,28 +1,50 @@
<?php
abstract class PhortuneSubscriptionImplementation {
abstract public function loadImplementationsForRefs(
PhabricatorUser $viewer,
array $refs);
abstract public function getRef();
abstract public function getName(PhortuneSubscription $subscription);
+
+ public function getFullName(PhortuneSubscription $subscription) {
+ return $this->getName($subscription);
+ }
+
+ public function getCrumbName(PhortuneSubscription $subscription) {
+ return $this->getName($subscription);
+ }
+
abstract public function getCostForBillingPeriodAsCurrency(
PhortuneSubscription $subscription,
$start_epoch,
$end_epoch);
protected function getContentSource() {
return PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_PHORTUNE,
array());
}
public function getCartName(
PhortuneSubscription $subscription,
PhortuneCart $cart) {
return pht('Subscription');
}
+ public function getPurchaseName(
+ PhortuneSubscription $subscription,
+ PhortuneProduct $product,
+ PhortunePurchase $purchase) {
+ return $product->getProductName();
+ }
+
+ public function getPurchaseURI(
+ PhortuneSubscription $subscription,
+ PhortuneProduct $product,
+ PhortunePurchase $purchase) {
+ return null;
+ }
}
diff --git a/src/applications/phortune/view/PhortuneSubscriptionTableView.php b/src/applications/phortune/view/PhortuneSubscriptionTableView.php
index e9a80a9df..e5d6e5d51 100644
--- a/src/applications/phortune/view/PhortuneSubscriptionTableView.php
+++ b/src/applications/phortune/view/PhortuneSubscriptionTableView.php
@@ -1,80 +1,80 @@
<?php
final class PhortuneSubscriptionTableView extends AphrontView {
private $subscriptions;
private $handles;
private $isMerchantView;
public function setHandles(array $handles) {
$this->handles = $handles;
return $this;
}
public function getHandles() {
return $this->handles;
}
public function setSubscriptions(array $subscriptions) {
$this->subscriptions = $subscriptions;
return $this;
}
public function getSubscriptions() {
return $this->subscriptions;
}
public function setIsMerchantView($is_merchant_view) {
$this->isMerchantView = $is_merchant_view;
return $this;
}
public function getIsMerchantView() {
return $this->isMerchantView;
}
public function render() {
$subscriptions = $this->getSubscriptions();
$handles = $this->getHandles();
$viewer = $this->getUser();
$rows = array();
$rowc = array();
foreach ($subscriptions as $subscription) {
if ($this->getIsMerchantView()) {
$uri = $subscription->getMerchantURI();
} else {
$uri = $subscription->getURI();
}
$subscription_link = $handles[$subscription->getPHID()]->renderLink();
$rows[] = array(
$subscription->getID(),
phutil_tag(
'a',
array(
'href' => $uri,
),
- $subscription->getSubscriptionName()),
+ $subscription->getSubscriptionFullName()),
phabricator_datetime($subscription->getDateCreated(), $viewer),
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('ID'),
pht('Name'),
pht('Created'),
))
->setColumnClasses(
array(
'',
'wide',
'right',
));
return $table;
}
}
diff --git a/src/applications/phortune/worker/PhortuneSubscriptionWorker.php b/src/applications/phortune/worker/PhortuneSubscriptionWorker.php
index b872b6c83..bb3725fec 100644
--- a/src/applications/phortune/worker/PhortuneSubscriptionWorker.php
+++ b/src/applications/phortune/worker/PhortuneSubscriptionWorker.php
@@ -1,189 +1,191 @@
<?php
final class PhortuneSubscriptionWorker extends PhabricatorWorker {
protected function doWork() {
$subscription = $this->loadSubscription();
$range = $this->getBillingPeriodRange($subscription);
list($last_epoch, $next_epoch) = $range;
$account = $subscription->getAccount();
$merchant = $subscription->getMerchant();
$viewer = PhabricatorUser::getOmnipotentUser();
$product = id(new PhortuneProductQuery())
->setViewer($viewer)
->withClassAndRef('PhortuneSubscriptionProduct', $subscription->getPHID())
->executeOne();
$cart_implementation = id(new PhortuneSubscriptionCart())
->setSubscription($subscription);
// TODO: This isn't really ideal. It would be better to use an application
// actor than the original author of the subscription. In particular, if
// someone initiates a subscription, adds some other account managers, and
// later leaves the company, they'll continue "acting" here indefinitely.
// However, for now, some of the stuff later in the pipeline requires a
// valid actor with a real PHID. The subscription should eventually be
// able to create these invoices "as" the application it is acting on
// behalf of.
$actor = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withPHIDs(array($subscription->getAuthorPHID()))
->executeOne();
if (!$actor) {
throw new Exception(pht('Failed to load actor to bill subscription!'));
}
$cart = $account->newCart($actor, $cart_implementation, $merchant);
$purchase = $cart->newPurchase($actor, $product);
$currency = $subscription->getCostForBillingPeriodAsCurrency(
$last_epoch,
$next_epoch);
$purchase
->setBasePriceAsCurrency($currency)
->setMetadataValue('subscriptionPHID', $subscription->getPHID())
+ ->setMetadataValue('epoch.start', $last_epoch)
+ ->setMetadataValue('epoch.end', $next_epoch)
->save();
$cart->setSubscriptionPHID($subscription->getPHID());
$cart->activateCart();
try {
$issues = $this->chargeSubscription($actor, $subscription, $cart);
} catch (Exception $ex) {
$issues = array(
pht(
'There was a technical error while trying to automatically bill '.
'this subscription: %s',
$ex),
);
}
if (!$issues) {
// We're all done; charging the cart sends a billing email as a side
// effect.
return;
}
// TODO: Send an email telling the user that we weren't able to autopay
// so they need to pay this manually.
throw new Exception(implode("\n", $issues));
}
private function chargeSubscription(
PhabricatorUser $viewer,
PhortuneSubscription $subscription,
PhortuneCart $cart) {
$issues = array();
if (!$subscription->getDefaultPaymentMethodPHID()) {
$issues[] = pht(
'There is no payment method associated with this subscription, so '.
'it could not be billed automatically. Add a default payment method '.
'to enable automatic billing.');
return $issues;
}
$method = id(new PhortunePaymentMethodQuery())
->setViewer($viewer)
->withPHIDs(array($subscription->getDefaultPaymentMethodPHID()))
->executeOne();
if (!$method) {
$issues[] = pht(
'The payment method associated with this subscription is invalid '.
'or out of date, so it could not be automatically billed. Update '.
'the default payment method to enable automatic billing.');
return $issues;
}
$provider = $method->buildPaymentProvider();
$charge = $cart->willApplyCharge($viewer, $provider, $method);
try {
$provider->applyCharge($method, $charge);
} catch (Exception $ex) {
$cart->didFailCharge($charge);
$issues[] = pht(
'Automatic billing failed: %s',
$ex->getMessage());
return $issues;
}
$cart->didApplyCharge($charge);
}
/**
* Load the subscription to generate an invoice for.
*
* @return PhortuneSubscription The subscription to invoice.
*/
private function loadSubscription() {
$viewer = PhabricatorUser::getOmnipotentUser();
$data = $this->getTaskData();
$subscription_phid = idx($data, 'subscriptionPHID');
$subscription = id(new PhortuneSubscriptionQuery())
->setViewer($viewer)
->withPHIDs(array($subscription_phid))
->executeOne();
if (!$subscription) {
throw new PhabricatorWorkerPermanentFailureException(
pht(
'Failed to load subscription with PHID "%s".',
$subscription_phid));
}
return $subscription;
}
/**
* Get the start and end epoch timestamps for this billing period.
*
* @param PhortuneSubscription The subscription being billed.
* @return pair<int, int> Beginning and end of the billing range.
*/
private function getBillingPeriodRange(PhortuneSubscription $subscription) {
$data = $this->getTaskData();
$last_epoch = idx($data, 'trigger.last-epoch');
if (!$last_epoch) {
// If this is the first time the subscription is firing, use the
// creation date as the start of the billing period.
$last_epoch = $subscription->getDateCreated();
}
$this_epoch = idx($data, 'trigger.next-epoch');
if (!$last_epoch || !$this_epoch) {
throw new PhabricatorWorkerPermanentFailureException(
pht(
'Subscription is missing billing period information.'));
}
$period_length = ($this_epoch - $last_epoch);
if ($period_length <= 0) {
throw new PhabricatorWorkerPermanentFailureException(
pht(
'Subscription has invalid billing period.'));
}
if (PhabricatorTime::getNow() < $this_epoch) {
throw new Exception(
pht(
'Refusing to generate a subscription invoice for a billing period '.
'which ends in the future.'));
}
return array($last_epoch, $this_epoch);
}
}
diff --git a/src/infrastructure/internationalization/translation/PhabricatorBaseEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorBaseEnglishTranslation.php
index 74ac93bbd..59823fdbe 100644
--- a/src/infrastructure/internationalization/translation/PhabricatorBaseEnglishTranslation.php
+++ b/src/infrastructure/internationalization/translation/PhabricatorBaseEnglishTranslation.php
@@ -1,907 +1,912 @@
<?php
abstract class PhabricatorBaseEnglishTranslation
extends PhabricatorTranslation {
final public function getLanguage() {
return 'en';
}
public function getTranslations() {
return array(
'No daemon(s) with id(s) "%s" exist!' => array(
'No daemon with id %s exists!',
'No daemons with ids %s exist!',
),
'These %d configuration value(s) are related:' => array(
'This configuration value is related:',
'These configuration values are related:',
),
'Task(s)' => array('Task', 'Tasks'),
'ERROR(S)' => array('ERROR', 'ERRORS'),
'%d Error(s)' => array('%d Error', '%d Errors'),
'%d Warning(s)' => array('%d Warning', '%d Warnings'),
'%d Auto-Fix(es)' => array('%d Auto-Fix', '%d Auto-Fixes'),
'%d Advice(s)' => array('%d Advice', '%d Pieces of Advice'),
'%d Detail(s)' => array('%d Detail', '%d Details'),
'(%d line(s))' => array('(%d line)', '(%d lines)'),
'%d line(s)' => array('%d line', '%d lines'),
'%d path(s)' => array('%d path', '%d paths'),
'%d diff(s)' => array('%d diff', '%d diffs'),
'DIFF LINK(S)' => array('DIFF LINK', 'DIFF LINKS'),
'You successfully created %d diff(s).' => array(
'You successfully created %d diff.',
'You successfully created %d diffs.',
),
'Diff creation failed; see body for error(s).' => array(
'Diff creation failed; see body for error.',
'Diff creation failed; see body for errors.',
),
'There are %d raw fact(s) in storage.' => array(
'There is %d raw fact in storage.',
'There are %d raw facts in storage.',
),
'There are %d aggregate fact(s) in storage.' => array(
'There is %d aggregate fact in storage.',
'There are %d aggregate facts in storage.',
),
'%d Commit(s) Awaiting Audit' => array(
'%d Commit Awaiting Audit',
'%d Commits Awaiting Audit',
),
'%d Problem Commit(s)' => array(
'%d Problem Commit',
'%d Problem Commits',
),
'%d Review(s) Blocking Others' => array(
'%d Review Blocking Others',
'%d Reviews Blocking Others',
),
'%d Review(s) Need Attention' => array(
'%d Review Needs Attention',
'%d Reviews Need Attention',
),
'%d Review(s) Waiting on Others' => array(
'%d Review Waiting on Others',
'%d Reviews Waiting on Others',
),
'%d Active Review(s)' => array(
'%d Active Review',
'%d Active Reviews',
),
'%d Flagged Object(s)' => array(
'%d Flagged Object',
'%d Flagged Objects',
),
'%d Object(s) Tracked' => array(
'%d Object Tracked',
'%d Objects Tracked',
),
'%d Assigned Task(s)' => array(
'%d Assigned Task',
'%d Assigned Tasks',
),
'Show %d Lint Message(s)' => array(
'Show %d Lint Message',
'Show %d Lint Messages',
),
'Hide %d Lint Message(s)' => array(
'Hide %d Lint Message',
'Hide %d Lint Messages',
),
'This is a binary file. It is %s byte(s) in length.' => array(
'This is a binary file. It is %s byte in length.',
'This is a binary file. It is %s bytes in length.',
),
'%d Action(s) Have No Effect' => array(
'Action Has No Effect',
'Actions Have No Effect',
),
'%d Action(s) With No Effect' => array(
'Action With No Effect',
'Actions With No Effect',
),
'Some of your %d action(s) have no effect:' => array(
'One of your actions has no effect:',
'Some of your actions have no effect:',
),
'Apply remaining %d action(s)?' => array(
'Apply remaining action?',
'Apply remaining actions?',
),
'Apply %d Other Action(s)' => array(
'Apply Remaining Action',
'Apply Remaining Actions',
),
'The %d action(s) you are taking have no effect:' => array(
'The action you are taking has no effect:',
'The actions you are taking have no effect:',
),
'%s edited member(s), added %d: %s; removed %d: %s.' =>
'%s edited members, added: %3$s; removed: %5$s.',
'%s added %s member(s): %s.' => array(
array(
'%s added a member: %3$s.',
'%s added members: %3$s.',
),
),
'%s removed %s member(s): %s.' => array(
array(
'%s removed a member: %3$s.',
'%s removed members: %3$s.',
),
),
'%s edited project(s), added %s: %s; removed %s: %s.' =>
'%s edited projects, added: %3$s; removed: %5$s.',
'%s added %s project(s): %s.' => array(
array(
'%s added a project: %3$s.',
'%s added projects: %3$s.',
),
),
'%s removed %s project(s): %s.' => array(
array(
'%s removed a project: %3$s.',
'%s removed projects: %3$s.',
),
),
'%s merged %d task(s): %s.' => array(
array(
'%s merged a task: %3$s.',
'%s merged tasks: %3$s.',
),
),
'%s merged %d task(s) %s into %s.' => array(
array(
'%s merged %3$s into %4$s.',
'%s merged tasks %3$s into %4$s.',
),
),
'%s added %s voting user(s): %s.' => array(
array(
'%s added a voting user: %3$s.',
'%s added voting users: %3$s.',
),
),
'%s removed %s voting user(s): %s.' => array(
array(
'%s removed a voting user: %3$s.',
'%s removed voting users: %3$s.',
),
),
'%s added %s blocking task(s): %s.' => array(
array(
'%s added a blocking task: %3$s.',
'%s added blocking tasks: %3$s.',
),
),
'%s added %s blocked task(s): %s.' => array(
array(
'%s added a blocked task: %3$s.',
'%s added blocked tasks: %3$s.',
),
),
'%s removed %s blocking task(s): %s.' => array(
array(
'%s removed a blocking task: %3$s.',
'%s removed blocking tasks: %3$s.',
),
),
'%s removed %s blocked task(s): %s.' => array(
array(
'%s removed a blocked task: %3$s.',
'%s removed blocked tasks: %3$s.',
),
),
'%s added %s blocking task(s) for %s: %s.' => array(
array(
'%s added a blocking task for %3$s: %4$s.',
'%s added blocking tasks for %3$s: %4$s.',
),
),
'%s added %s blocked task(s) for %s: %s.' => array(
array(
'%s added a blocked task for %3$s: %4$s.',
'%s added blocked tasks for %3$s: %4$s.',
),
),
'%s removed %s blocking task(s) for %s: %s.' => array(
array(
'%s removed a blocking task for %3$s: %4$s.',
'%s removed blocking tasks for %3$s: %4$s.',
),
),
'%s removed %s blocked task(s) for %s: %s.' => array(
array(
'%s removed a blocked task for %3$s: %4$s.',
'%s removed blocked tasks for %3$s: %4$s.',
),
),
'%s edited blocking task(s), added %s: %s; removed %s: %s.' =>
'%s edited blocking tasks, added: %3$s; removed: %5$s.',
'%s edited blocking task(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited blocking tasks for %s, added: %4$s; removed: %6$s.',
'%s edited blocked task(s), added %s: %s; removed %s: %s.' =>
'%s edited blocked tasks, added: %3$s; removed: %5$s.',
'%s edited blocked task(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited blocked tasks for %s, added: %4$s; removed: %6$s.',
'%s edited answer(s), added %s: %s; removed %d: %s.' =>
'%s edited answers, added: %3$s; removed: %5$s.',
'%s added %s answer(s): %s.' => array(
array(
'%s added an answer: %3$s.',
'%s added answers: %3$s.',
),
),
'%s removed %s answer(s): %s.' => array(
array(
'%s removed a answer: %3$s.',
'%s removed answers: %3$s.',
),
),
'%s edited question(s), added %s: %s; removed %s: %s.' =>
'%s edited questions, added: %3$s; removed: %5$s.',
'%s added %s question(s): %s.' => array(
array(
'%s added a question: %3$s.',
'%s added questions: %3$s.',
),
),
'%s removed %s question(s): %s.' => array(
array(
'%s removed a question: %3$s.',
'%s removed questions: %3$s.',
),
),
'%s edited mock(s), added %s: %s; removed %s: %s.' =>
'%s edited mocks, added: %3$s; removed: %5$s.',
'%s added %s mock(s): %s.' => array(
array(
'%s added a mock: %3$s.',
'%s added mocks: %3$s.',
),
),
'%s removed %s mock(s): %s.' => array(
array(
'%s removed a mock: %3$s.',
'%s removed mocks: %3$s.',
),
),
'%s added %s task(s): %s.' => array(
array(
'%s added a task: %3$s.',
'%s added tasks: %3$s.',
),
),
'%s removed %s task(s): %s.' => array(
array(
'%s removed a task: %3$s.',
'%s removed tasks: %3$s.',
),
),
'%s edited file(s), added %s: %s; removed %s: %s.' =>
'%s edited files, added: %3$s; removed: %5$s.',
'%s added %s file(s): %s.' => array(
array(
'%s added a file: %3$s.',
'%s added files: %3$s.',
),
),
'%s removed %s file(s): %s.' => array(
array(
'%s removed a file: %3$s.',
'%s removed files: %3$s.',
),
),
'%s edited contributor(s), added %s: %s; removed %s: %s.' =>
'%s edited contributors, added: %3$s; removed: %5$s.',
'%s added %s contributor(s): %s.' => array(
array(
'%s added a contributor: %3$s.',
'%s added contributors: %3$s.',
),
),
'%s removed %s contributor(s): %s.' => array(
array(
'%s removed a contributor: %3$s.',
'%s removed contributors: %3$s.',
),
),
'%s edited %s reviewer(s), added %s: %s; removed %s: %s.' =>
'%s edited reviewers, added: %4$s; removed: %6$s.',
'%s edited %s reviewer(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited reviewers for %3$s, added: %5$s; removed: %7$s.',
'%s added %s reviewer(s): %s.' => array(
array(
'%s added a reviewer: %3$s.',
'%s added reviewers: %3$s.',
),
),
'%s removed %s reviewer(s): %s.' => array(
array(
'%s removed a reviewer: %3$s.',
'%s removed reviewers: %3$s.',
),
),
'%d other(s)' => array(
'1 other',
'%d others',
),
'%s edited subscriber(s), added %d: %s; removed %d: %s.' =>
'%s edited subscribers, added: %3$s; removed: %5$s.',
'%s added %d subscriber(s): %s.' => array(
array(
'%s added a subscriber: %3$s.',
'%s added subscribers: %3$s.',
),
),
'%s removed %d subscriber(s): %s.' => array(
array(
'%s removed a subscriber: %3$s.',
'%s removed subscribers: %3$s.',
),
),
'%s edited participant(s), added %d: %s; removed %d: %s.' =>
'%s edited participants, added: %3$s; removed: %5$s.',
'%s added %d participant(s): %s.' => array(
array(
'%s added a participant: %3$s.',
'%s added participants: %3$s.',
),
),
'%s removed %d participant(s): %s.' => array(
array(
'%s removed a participant: %3$s.',
'%s removed participants: %3$s.',
),
),
'%s edited image(s), added %d: %s; removed %d: %s.' =>
'%s edited images, added: %3$s; removed: %5$s',
'%s added %d image(s): %s.' => array(
array(
'%s added an image: %3$s.',
'%s added images: %3$s.',
),
),
'%s removed %d image(s): %s.' => array(
array(
'%s removed an image: %3$s.',
'%s removed images: %3$s.',
),
),
'%s Line(s)' => array(
'%s Line',
'%s Lines',
),
'Indexing %d object(s) of type %s.' => array(
'Indexing %d object of type %s.',
'Indexing %d object of type %s.',
),
'Run these %d command(s):' => array(
'Run this command:',
'Run these commands:',
),
'Install these %d PHP extension(s):' => array(
'Install this PHP extension:',
'Install these PHP extensions:',
),
'The current Phabricator configuration has these %d value(s):' => array(
'The current Phabricator configuration has this value:',
'The current Phabricator configuration has these values:',
),
'The current MySQL configuration has these %d value(s):' => array(
'The current MySQL configuration has this value:',
'The current MySQL configuration has these values:',
),
'You can update these %d value(s) here:' => array(
'You can update this value here:',
'You can update these values here:',
),
'The current PHP configuration has these %d value(s):' => array(
'The current PHP configuration has this value:',
'The current PHP configuration has these values:',
),
'To update these %d value(s), edit your PHP configuration file.' => array(
'To update this %d value, edit your PHP configuration file.',
'To update these %d values, edit your PHP configuration file.',
),
'To update these %d value(s), edit your PHP configuration file, located '.
'here:' => array(
'To update this value, edit your PHP configuration file, located '.
'here:',
'To update these values, edit your PHP configuration file, located '.
'here:',
),
'PHP also loaded these configuration file(s):' => array(
'PHP also loaded this configuration file:',
'PHP also loaded these configuration files:',
),
'You have %d unresolved setup issue(s)...' => array(
'You have an unresolved setup issue...',
'You have %d unresolved setup issues...',
),
'%s added %d inline comment(s).' => array(
array(
'%s added an inline comment.',
'%s added inline comments.',
),
),
'%d comment(s)' => array('%d comment', '%d comments'),
'%d rejection(s)' => array('%d rejection', '%d rejections'),
'%d update(s)' => array('%d update', '%d updates'),
'This configuration value is defined in these %d '.
'configuration source(s): %s.' => array(
'This configuration value is defined in this '.
'configuration source: %2$s.',
'This configuration value is defined in these %d '.
'configuration sources: %s.',
),
'%d Open Pull Request(s)' => array(
'%d Open Pull Request',
'%d Open Pull Requests',
),
'Stale (%s day(s))' => array(
'Stale (%s day)',
'Stale (%s days)',
),
'Old (%s day(s))' => array(
'Old (%s day)',
'Old (%s days)',
),
'%s Commit(s)' => array(
'%s Commit',
'%s Commits',
),
'%s attached %d file(s): %s.' => array(
array(
'%s attached a file: %3$s.',
'%s attached files: %3$s.',
),
),
'%s detached %d file(s): %s.' => array(
array(
'%s detached a file: %3$s.',
'%s detached files: %3$s.',
),
),
'%s changed file(s), attached %d: %s; detached %d: %s.' =>
'%s changed files, attached: %3$s; detached: %5$s.',
'%s added %s dependencie(s): %s.' => array(
array(
'%s added a dependency: %3$s.',
'%s added dependencies: %3$s.',
),
),
'%s removed %s dependencie(s): %s.' => array(
array(
'%s removed a dependency: %3$s.',
'%s removed dependencies: %3$s.',
),
),
'%s added %s dependent revision(s): %s.' => array(
array(
'%s added a dependent revision: %3$s.',
'%s added dependent revisions: %3$s.',
),
),
'%s removed %s dependent revision(s): %s.' => array(
array(
'%s removed a dependent revision: %3$s.',
'%s removed dependent revisions: %3$s.',
),
),
'%s added %s commit(s): %s.' => array(
array(
'%s added a commit: %3$s.',
'%s added commits: %3$s.',
),
),
'%s removed %s commit(s): %s.' => array(
array(
'%s removed a commit: %3$s.',
'%s removed commits: %3$s.',
),
),
'%s edited commit(s), added %s: %s; removed %s: %s.' =>
'%s edited commits, added %3$s; removed %5$s.',
'%s added %s reverted commit(s): %s.' => array(
array(
'%s added a reverted commit: %3$s.',
'%s added reverted commits: %3$s.',
),
),
'%s removed %s reverted commit(s): %s.' => array(
array(
'%s removed a reverted commit: %3$s.',
'%s removed reverted commits: %3$s.',
),
),
'%s edited reverted commit(s), added %s: %s; removed %s: %s.' =>
'%s edited reverted commits, added %3$s; removed %5$s.',
'%s added %s reverting commit(s): %s.' => array(
array(
'%s added a reverting commit: %3$s.',
'%s added reverting commits: %3$s.',
),
),
'%s removed %s reverting commit(s): %s.' => array(
array(
'%s removed a reverting commit: %3$s.',
'%s removed reverting commits: %3$s.',
),
),
'%s edited reverting commit(s), added %s: %s; removed %s: %s.' =>
'%s edited reverting commits, added %3$s; removed %5$s.',
'%s changed project member(s), added %d: %s; removed %d: %s.' =>
'%s changed project members, added %3$s; removed %5$s.',
'%s added %d project member(s): %s.' => array(
array(
'%s added a member: %3$s.',
'%s added members: %3$s.',
),
),
'%s removed %d project member(s): %s.' => array(
array(
'%s removed a member: %3$s.',
'%s removed members: %3$s.',
),
),
'%d project hashtag(s) are already used: %s.' => array(
'Project hashtag %2$s is already used.',
'%d project hashtags are already used: %2$s.',
),
'%s changed project hashtag(s), added %d: %s; removed %d: %s.' =>
'%s changed project hashtags, added %3$s; removed %5$s.',
'%s added %d project hashtag(s): %s.' => array(
array(
'%s added a hashtag: %3$s.',
'%s added hashtags: %3$s.',
),
),
'%s removed %d project hashtag(s): %s.' => array(
array(
'%s removed a hashtag: %3$s.',
'%s removed hashtags: %3$s.',
),
),
'%d User(s) Need Approval' => array(
'%d User Needs Approval',
'%d Users Need Approval',
),
'%s older changes(s) are hidden.' => array(
'%d older change is hidden.',
'%d older changes are hidden.',
),
'%s, %s line(s)' => array(
'%s, %s line',
'%s, %s lines',
),
'%s pushed %d commit(s) to %s.' => array(
array(
'%s pushed a commit to %3$s.',
'%s pushed %d commits to %s.',
),
),
'%s commit(s)' => array(
'1 commit',
'%s commits',
),
'%s removed %s JIRA issue(s): %s.' => array(
array(
'%s removed a JIRA issue: %3$s.',
'%s removed JIRA issues: %3$s.',
),
),
'%s added %s JIRA issue(s): %s.' => array(
array(
'%s added a JIRA issue: %3$s.',
'%s added JIRA issues: %3$s.',
),
),
'%s added %s required legal document(s): %s.' => array(
array(
'%s added a required legal document: %3$s.',
'%s added required legal documents: %3$s.',
),
),
'%s updated JIRA issue(s): added %s %s; removed %d %s.' =>
'%s updated JIRA issues: added %3$s; removed %5$s.',
'%s edited %s task(s), added %s: %s; removed %s: %s.' =>
'%s edited tasks, added %4$s; removed %6$s.',
'%s added %s task(s) to %s: %s.' => array(
array(
'%s added a task to %3$s: %4$s.',
'%s added tasks to %3$s: %4$s.',
),
),
'%s removed %s task(s) from %s: %s.' => array(
array(
'%s removed a task from %3$s: %4$s.',
'%s removed tasks from %3$s: %4$s.',
),
),
'%s edited %s task(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited tasks for %3$s, added: %5$s; removed %7$s.',
'%s edited %s commit(s), added %s: %s; removed %s: %s.' =>
'%s edited commits, added %4$s; removed %6$s.',
'%s added %s commit(s) to %s: %s.' => array(
array(
'%s added a commit to %3$s: %4$s.',
'%s added commits to %3$s: %4$s.',
),
),
'%s removed %s commit(s) from %s: %s.' => array(
array(
'%s removed a commit from %3$s: %4$s.',
'%s removed commits from %3$s: %4$s.',
),
),
'%s edited %s commit(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited commits for %3$s, added: %5$s; removed %7$s.',
'%s added %s revision(s): %s.' => array(
array(
'%s added a revision: %3$s.',
'%s added revisions: %3$s.',
),
),
'%s removed %s revision(s): %s.' => array(
array(
'%s removed a revision: %3$s.',
'%s removed revisions: %3$s.',
),
),
'%s edited %s revision(s), added %s: %s; removed %s: %s.' =>
'%s edited revisions, added %4$s; removed %6$s.',
'%s added %s revision(s) to %s: %s.' => array(
array(
'%s added a revision to %3$s: %4$s.',
'%s added revisions to %3$s: %4$s.',
),
),
'%s removed %s revision(s) from %s: %s.' => array(
array(
'%s removed a revision from %3$s: %4$s.',
'%s removed revisions from %3$s: %4$s.',
),
),
'%s edited %s revision(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited revisions for %3$s, added: %5$s; removed %7$s.',
'%s edited %s project(s), added %s: %s; removed %s: %s.' =>
'%s edited projects, added %4$s; removed %6$s.',
'%s added %s project(s) to %s: %s.' => array(
array(
'%s added a project to %3$s: %4$s.',
'%s added projects to %3$s: %4$s.',
),
),
'%s removed %s project(s) from %s: %s.' => array(
array(
'%s removed a project from %3$s: %4$s.',
'%s removed projects from %3$s: %4$s.',
),
),
'%s edited %s project(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited projects for %3$s, added: %5$s; removed %7$s.',
'%s added %s panel(s): %s.' => array(
array(
'%s added a panel: %3$s.',
'%s added panels: %3$s.',
),
),
'%s removed %s panel(s): %s.' => array(
array(
'%s removed a panel: %3$s.',
'%s removed panels: %3$s.',
),
),
'%s edited %s panel(s), added %s: %s; removed %s: %s.' =>
'%s edited panels, added %4$s; removed %6$s.',
'%s added %s dashboard(s): %s.' => array(
array(
'%s added a dashboard: %3$s.',
'%s added dashboards: %3$s.',
),
),
'%s removed %s dashboard(s): %s.' => array(
array(
'%s removed a dashboard: %3$s.',
'%s removed dashboards: %3$s.',
),
),
'%s edited %s dashboard(s), added %s: %s; removed %s: %s.' =>
'%s edited dashboards, added %4$s; removed %6$s.',
'%s added %s edge(s): %s.' => array(
array(
'%s added an edge: %3$s.',
'%s added edges: %3$s.',
),
),
'%s added %s edge(s) to %s: %s.' => array(
array(
'%s added an edge to %3$s: %4$s.',
'%s added edges to %3$s: %4$s.',
),
),
'%s removed %s edge(s): %s.' => array(
array(
'%s removed an edge: %3$s.',
'%s removed edges: %3$s.',
),
),
'%s removed %s edge(s) from %s: %s.' => array(
array(
'%s removed an edge from %3$s: %4$s.',
'%s removed edges from %3$s: %4$s.',
),
),
'%s edited edge(s), added %s: %s; removed %s: %s.' =>
'%s edited edges, added: %3$s; removed: %5$s.',
'%s edited %s edge(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited edges for %3$s, added: %5$s; removed %7$s.',
'%d related link(s):' => array(
'Related link:',
'Related links:',
),
+
+ 'You have %d unpaid invoice(s).' => array(
+ 'You have an unpaid invoice.',
+ 'You have unpaid invoices.',
+ ),
);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, May 10, 8:41 AM (1 d, 16 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
65/9f/1311980f1de38fafd7c21f3b3e02

Event Timeline