Page MenuHomePhabricator

D13681.id33073.diff
No OneTemporary

D13681.id33073.diff

diff --git a/resources/sql/autopatches/20150721.phurl.edge.1.sql b/resources/sql/autopatches/20150721.phurl.edge.1.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20150721.phurl.edge.1.sql
@@ -0,0 +1,16 @@
+CREATE TABLE {$NAMESPACE}_phurl.edge (
+ src VARBINARY(64) NOT NULL,
+ type INT UNSIGNED NOT NULL,
+ dst VARBINARY(64) NOT NULL,
+ dateCreated INT UNSIGNED NOT NULL,
+ seq INT UNSIGNED NOT NULL,
+ dataID INT UNSIGNED,
+ PRIMARY KEY (src, type, dst),
+ KEY `src` (src, type, dateCreated, seq),
+ UNIQUE KEY `key_dst` (dst, type, src)
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+
+CREATE TABLE {$NAMESPACE}_phurl.edgedata (
+ id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
diff --git a/resources/sql/autopatches/20150721.phurl.url.1.sql b/resources/sql/autopatches/20150721.phurl.url.1.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20150721.phurl.url.1.sql
@@ -0,0 +1,10 @@
+CREATE TABLE {$NAMESPACE}_phurl.phurl_url (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ phid VARBINARY(64) NOT NULL,
+ name VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT},
+ longURL VARCHAR(2047) NOT NULL COLLATE {$COLLATE_TEXT},
+ description VARCHAR(2047) NOT NULL COLLATE {$COLLATE_TEXT},
+ viewPolicy VARBINARY(64) NOT NULL,
+ editPolicy VARBINARY(64) NOT NULL,
+ spacePHID varbinary(64) DEFAULT NULL
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
diff --git a/resources/sql/autopatches/20150721.phurl.url.2.sql b/resources/sql/autopatches/20150721.phurl.url.2.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20150721.phurl.url.2.sql
@@ -0,0 +1,3 @@
+ALTER TABLE {$NAMESPACE}_phurl.phurl_url
+ ADD dateCreated int unsigned NOT NULL,
+ ADD dateModified int unsigned NOT NULL;
diff --git a/resources/sql/autopatches/20150721.phurl.xaction.1.sql b/resources/sql/autopatches/20150721.phurl.xaction.1.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20150721.phurl.xaction.1.sql
@@ -0,0 +1,19 @@
+CREATE TABLE {$NAMESPACE}_phurl.phurl_urltransaction (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ phid VARBINARY(64) NOT NULL,
+ authorPHID VARBINARY(64) NOT NULL,
+ objectPHID VARBINARY(64) NOT NULL,
+ viewPolicy VARBINARY(64) NOT NULL,
+ editPolicy VARBINARY(64) NOT NULL,
+ commentPHID VARBINARY(64) DEFAULT NULL,
+ commentVersion INT UNSIGNED NOT NULL,
+ transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
+ oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+ newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+ contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+ metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+ UNIQUE KEY `key_phid` (`phid`),
+ KEY `key_object` (`objectPHID`)
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
diff --git a/resources/sql/autopatches/20150721.phurl.xactioncomment.1.sql b/resources/sql/autopatches/20150721.phurl.xactioncomment.1.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20150721.phurl.xactioncomment.1.sql
@@ -0,0 +1,16 @@
+CREATE TABLE {$NAMESPACE}_phurl.phurl_urltransaction_comment (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ phid VARBINARY(64) NOT NULL,
+ transactionPHID VARBINARY(64) DEFAULT NULL,
+ authorPHID VARBINARY(64) NOT NULL,
+ viewPolicy VARBINARY(64) NOT NULL,
+ editPolicy VARBINARY(64) NOT NULL,
+ commentVersion INT UNSIGNED NOT NULL,
+ content LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+ contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+ isDeleted TINYINT(1) NOT NULL,
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+ UNIQUE KEY `key_phid` (`phid`),
+ UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`)
+) ENGINE=InnoDB COLLATE {$COLLATE_TEXT}
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2442,6 +2442,21 @@
'PhabricatorPhrequentConfigOptions' => 'applications/phrequent/config/PhabricatorPhrequentConfigOptions.php',
'PhabricatorPhrictionApplication' => 'applications/phriction/application/PhabricatorPhrictionApplication.php',
'PhabricatorPhrictionConfigOptions' => 'applications/phriction/config/PhabricatorPhrictionConfigOptions.php',
+ 'PhabricatorPhurlApplication' => 'applications/phurl/application/PhabricatorPhurlApplication.php',
+ 'PhabricatorPhurlController' => 'applications/phurl/controller/PhabricatorPhurlController.php',
+ 'PhabricatorPhurlDAO' => 'applications/phurl/storage/PhabricatorPhurlDAO.php',
+ 'PhabricatorPhurlSchemaSpec' => 'applications/phurl/storage/PhabricatorPhurlSchemaSpec.php',
+ 'PhabricatorPhurlURL' => 'applications/phurl/storage/PhabricatorPhurlURL.php',
+ 'PhabricatorPhurlURLEditController' => 'applications/phurl/controller/PhabricatorPhurlURLEditController.php',
+ 'PhabricatorPhurlURLEditor' => 'applications/phurl/editor/PhabricatorPhurlURLEditor.php',
+ 'PhabricatorPhurlURLListController' => 'applications/phurl/controller/PhabricatorPhurlURLListController.php',
+ 'PhabricatorPhurlURLPHIDType' => 'applications/phurl/phid/PhabricatorPhurlURLPHIDType.php',
+ 'PhabricatorPhurlURLQuery' => 'applications/phurl/query/PhabricatorPhurlURLQuery.php',
+ 'PhabricatorPhurlURLSearchEngine' => 'applications/phurl/query/PhabricatorPhurlURLSearchEngine.php',
+ 'PhabricatorPhurlURLTransaction' => 'applications/phurl/storage/PhabricatorPhurlURLTransaction.php',
+ 'PhabricatorPhurlURLTransactionComment' => 'applications/phurl/storage/PhabricatorPhurlURLTransactionComment.php',
+ 'PhabricatorPhurlURLTransactionQuery' => 'applications/phurl/query/PhabricatorPhurlURLTransactionQuery.php',
+ 'PhabricatorPhurlURLViewController' => 'applications/phurl/controller/PhabricatorPhurlURLViewController.php',
'PhabricatorPlatformSite' => 'aphront/site/PhabricatorPlatformSite.php',
'PhabricatorPolicies' => 'applications/policy/constants/PhabricatorPolicies.php',
'PhabricatorPolicy' => 'applications/policy/storage/PhabricatorPolicy.php',
@@ -6280,6 +6295,33 @@
'PhabricatorPhrequentConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorPhrictionApplication' => 'PhabricatorApplication',
'PhabricatorPhrictionConfigOptions' => 'PhabricatorApplicationConfigOptions',
+ 'PhabricatorPhurlApplication' => 'PhabricatorApplication',
+ 'PhabricatorPhurlController' => 'PhabricatorController',
+ 'PhabricatorPhurlDAO' => 'PhabricatorLiskDAO',
+ 'PhabricatorPhurlSchemaSpec' => 'PhabricatorConfigSchemaSpec',
+ 'PhabricatorPhurlURL' => array(
+ 'PhabricatorPhurlDAO',
+ 'PhabricatorPolicyInterface',
+ 'PhabricatorProjectInterface',
+ 'PhabricatorMarkupInterface',
+ 'PhabricatorApplicationTransactionInterface',
+ 'PhabricatorSubscribableInterface',
+ 'PhabricatorTokenReceiverInterface',
+ 'PhabricatorDestructibleInterface',
+ 'PhabricatorMentionableInterface',
+ 'PhabricatorFlaggableInterface',
+ 'PhabricatorSpacesInterface',
+ ),
+ 'PhabricatorPhurlURLEditController' => 'PhabricatorPhurlController',
+ 'PhabricatorPhurlURLEditor' => 'PhabricatorApplicationTransactionEditor',
+ 'PhabricatorPhurlURLListController' => 'PhabricatorPhurlController',
+ 'PhabricatorPhurlURLPHIDType' => 'PhabricatorPHIDType',
+ 'PhabricatorPhurlURLQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'PhabricatorPhurlURLSearchEngine' => 'PhabricatorApplicationSearchEngine',
+ 'PhabricatorPhurlURLTransaction' => 'PhabricatorApplicationTransaction',
+ 'PhabricatorPhurlURLTransactionComment' => 'PhabricatorApplicationTransactionComment',
+ 'PhabricatorPhurlURLTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
+ 'PhabricatorPhurlURLViewController' => 'PhabricatorPhurlController',
'PhabricatorPlatformSite' => 'PhabricatorSite',
'PhabricatorPolicies' => 'PhabricatorPolicyConstants',
'PhabricatorPolicy' => array(
diff --git a/src/applications/phurl/application/PhabricatorPhurlApplication.php b/src/applications/phurl/application/PhabricatorPhurlApplication.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phurl/application/PhabricatorPhurlApplication.php
@@ -0,0 +1,67 @@
+<?php
+
+final class PhabricatorPhurlApplication extends PhabricatorApplication {
+
+ public function getName() {
+ return pht('Phurl');
+ }
+
+ public function getShortDescription() {
+ return pht('URL Shortener');
+ }
+
+ public function getFlavorText() {
+ return pht('Shorten your favorite URL.');
+ }
+
+ public function getBaseURI() {
+ return '/phurl/';
+ }
+
+ public function getFontIcon() {
+ return 'fa-compress';
+ }
+
+ // public function getTitleGlyph() {
+ // // Unicode has a calendar character but it's in some distant code plane,
+ // // use "keyboard" since it looks vaguely similar.
+ // return "\xE2\x8C\xA8";
+ // }
+
+ public function isPrototype() {
+ return true;
+ }
+
+ public function getRoutes() {
+ return array(
+ // 'U(?P<id>[1-9]\d*)' => 'PhabricatorPhurlURLViewController',
+ '/phurl/' => array(
+ '(?:query/(?P<queryKey>[^/]+)/)?'
+ => 'PhabricatorPhurlURLListController',
+ 'url/' => array(
+ 'view/'
+ => 'PhabricatorPhurlURLViewController',
+ 'create/'
+ => 'PhabricatorPhurlURLEditController',
+ 'edit/(?P<id>[1-9]\d*)/(?:(?P<sequence>\d+)/)?'
+ => 'PhabricatorPhurlURLEditController',
+ ),
+ ),
+ );
+ }
+
+ // public function getMailCommandObjects() {
+ // return array(
+ // 'event' => array(
+ // 'name' => pht('Email Commands: Events'),
+ // 'header' => pht('Interacting with Calendar Events'),
+ // 'object' => new PhabricatorCalendarEvent(),
+ // 'summary' => pht(
+ // 'This page documents the commands you can use to interact with '.
+ // 'events in Calendar. These commands work when creating new tasks '.
+ // 'via email and when replying to existing tasks.'),
+ // ),
+ // );
+ // }
+
+}
diff --git a/src/applications/phurl/controller/PhabricatorPhurlController.php b/src/applications/phurl/controller/PhabricatorPhurlController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phurl/controller/PhabricatorPhurlController.php
@@ -0,0 +1,24 @@
+<?php
+
+abstract class PhabricatorPhurlController extends PhabricatorController {
+
+ protected function buildApplicationCrumbs() {
+ $crumbs = parent::buildApplicationCrumbs();
+
+ // $actions = id(new PhabricatorActionListView())
+ // ->setUser($this->getViewer())
+ // ->addAction(
+ // id(new PhabricatorActionView())
+ // ->setName(pht('Shorten URL'))
+ // ->setHref('/phurl/url/create/'));
+
+ $crumbs->addAction(
+ id(new PHUIListItemView())
+ ->setName(pht('Shorten URL'))
+ ->setHref($this->getApplicationURI().'url/create/')
+ ->setIcon('fa-plus-square'));
+ // ->setDropdownMenu($actions));
+
+ return $crumbs;
+ }
+}
diff --git a/src/applications/phurl/controller/PhabricatorPhurlURLEditController.php b/src/applications/phurl/controller/PhabricatorPhurlURLEditController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phurl/controller/PhabricatorPhurlURLEditController.php
@@ -0,0 +1,247 @@
+<?php
+
+final class PhabricatorPhurlURLEditController
+ extends PhabricatorPhurlController {
+
+ private $id;
+
+ public function willProcessRequest(array $data) {
+ $this->id = idx($data, 'id');
+ }
+
+ public function isCreate() {
+ return !$this->id;
+ }
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $request->getViewer();
+ $user_phid = $viewer->getPHID();
+ $error_name = true;
+ $error_long_url = true;
+ $validation_exception = null;
+
+ $next_workflow = $request->getStr('next');
+ $uri_query = $request->getStr('query');
+
+ if ($this->isCreate()) {
+ $url = PhabricatorPhurlURL::initializeNewPhurlURL(
+ $viewer);
+ $submit_label = pht('Create');
+ $page_title = pht('Shorten URL');
+ $redirect = 'created';
+ $subscribers = array();
+ $cancel_uri = $this->getApplicationURI();
+ } else {
+ $url = id(new PhabricatorPhurlURLQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($this->id))
+ ->requireCapabilities(
+ array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ ))
+ ->executeOne();
+
+ if (!$url) {
+ return new Aphront404Response();
+ }
+
+ $submit_label = pht('Update');
+ $page_title = pht('Update URL');
+
+ $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID(
+ $url->getPHID());
+
+ $cancel_uri = '/U'.$url->getID().'/view';
+ }
+
+ if ($this->isCreate()) {
+ $projects = array();
+ } else {
+ $projects = PhabricatorEdgeQuery::loadDestinationPHIDs(
+ $url->getPHID(),
+ PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
+ $projects = array_reverse($projects);
+ }
+
+ $name = $url->getName();
+ $long_url = $url->getLongURL();
+ $description = $url->getDescription();
+ $edit_policy = $url->getEditPolicy();
+ $view_policy = $url->getViewPolicy();
+ $space = $url->getSpacePHID();
+
+ if ($request->isFormPost()) {
+ $xactions = array();
+ $name = $request->getStr('name');
+ $long_url = $request->getStr('longURL');
+ $projects = $request->getArr('projects');
+ $description = $request->getStr('description');
+ $subscribers = $request->getArr('subscribers');
+ $edit_policy = $request->getStr('editPolicy');
+ $view_policy = $request->getStr('viewPolicy');
+ $space = $request->getStr('spacePHID');
+
+ $xactions[] = id(new PhabricatorPhurlURLTransaction())
+ ->setTransactionType(
+ PhabricatorPhurlURLTransaction::TYPE_NAME)
+ ->setNewValue($name);
+
+ $xactions[] = id(new PhabricatorPhurlURLTransaction())
+ ->setTransactionType(
+ PhabricatorPhurlURLTransaction::TYPE_URL)
+ ->setNewValue($long_url);
+
+ $xactions[] = id(new PhabricatorPhurlURLTransaction())
+ ->setTransactionType(
+ PhabricatorTransactions::TYPE_SUBSCRIBERS)
+ ->setNewValue(array('=' => array_fuse($subscribers)));
+
+ $xactions[] = id(new PhabricatorPhurlURLTransaction())
+ ->setTransactionType(
+ PhabricatorPhurlURLTransaction::TYPE_DESCRIPTION)
+ ->setNewValue($description);
+
+ $xactions[] = id(new PhabricatorPhurlURLTransaction())
+ ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
+ ->setNewValue($request->getStr('viewPolicy'));
+
+ $xactions[] = id(new PhabricatorPhurlURLTransaction())
+ ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
+ ->setNewValue($request->getStr('editPolicy'));
+
+ $xactions[] = id(new PhabricatorPhurlURLTransaction())
+ ->setTransactionType(PhabricatorTransactions::TYPE_SPACE)
+ ->setNewValue($space);
+
+ $editor = id(new PhabricatorPhurlURLEditor())
+ ->setActor($viewer)
+ ->setContentSourceFromRequest($request)
+ ->setContinueOnNoEffect(true);
+
+ try {
+ $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
+ $xactions[] = id(new PhabricatorPhurlURLTransaction())
+ ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
+ ->setMetadataValue('edge:type', $proj_edge_type)
+ ->setNewValue(array('=' => array_fuse($projects)));
+
+ $xactions = $editor->applyTransactions($url, $xactions);
+ return id(new AphrontRedirectResponse());
+ } catch (PhabricatorApplicationTransactionValidationException $ex) {
+ $validation_exception = $ex;
+ $error_name = $ex->getShortMessage(
+ PhabricatorPhurlURLTransaction::TYPE_NAME);
+ $error_long_url = $ex->getShortMessage(
+ PhabricatorPhurlURLTransaction::TYPE_URL);
+ }
+ }
+
+ $current_policies = id(new PhabricatorPolicyQuery())
+ ->setViewer($viewer)
+ ->setObject($url)
+ ->execute();
+
+ $name = id(new AphrontFormTextControl())
+ ->setLabel(pht('Name'))
+ ->setName('name')
+ ->setValue($name)
+ ->setError($error_name);
+
+ $long_url = id(new AphrontFormTextControl())
+ ->setLabel(pht('URL'))
+ ->setName('longURL')
+ ->setValue($long_url)
+ ->setError($error_long_url);
+
+ $projects = id(new AphrontFormTokenizerControl())
+ ->setLabel(pht('Projects'))
+ ->setName('projects')
+ ->setValue($projects)
+ ->setUser($viewer)
+ ->setDatasource(new PhabricatorProjectDatasource());
+
+ $description = id(new PhabricatorRemarkupControl())
+ ->setLabel(pht('Description'))
+ ->setName('description')
+ ->setValue($description)
+ ->setUser($viewer);
+
+ $view_policies = id(new AphrontFormPolicyControl())
+ ->setUser($viewer)
+ ->setValue($view_policy)
+ ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
+ ->setPolicyObject($url)
+ ->setPolicies($current_policies)
+ ->setSpacePHID($space)
+ ->setName('viewPolicy');
+ $edit_policies = id(new AphrontFormPolicyControl())
+ ->setUser($viewer)
+ ->setValue($edit_policy)
+ ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
+ ->setPolicyObject($url)
+ ->setPolicies($current_policies)
+ ->setName('editPolicy');
+
+ $subscribers = id(new AphrontFormTokenizerControl())
+ ->setLabel(pht('Subscribers'))
+ ->setName('subscribers')
+ ->setValue($subscribers)
+ ->setUser($viewer)
+ ->setDatasource(new PhabricatorMetaMTAMailableDatasource());
+
+ $form = id(new AphrontFormView())
+ ->addHiddenInput('next', $next_workflow)
+ ->addHiddenInput('query', $uri_query)
+ ->setUser($viewer)
+ ->appendChild($name)
+ ->appendChild($long_url)
+ ->appendControl($view_policies)
+ ->appendControl($edit_policies)
+ ->appendControl($subscribers)
+ ->appendChild($projects)
+ ->appendChild($description);
+
+
+ if ($request->isAjax()) {
+ return $this->newDialog()
+ ->setTitle($page_title)
+ ->setWidth(AphrontDialogView::WIDTH_FULL)
+ ->appendForm($form)
+ ->addCancelButton($cancel_uri)
+ ->addSubmitButton($submit_label);
+ }
+
+ $submit = id(new AphrontFormSubmitControl())
+ ->addCancelButton($cancel_uri)
+ ->setValue($submit_label);
+
+ $form->appendChild($submit);
+
+ $form_box = id(new PHUIObjectBoxView())
+ ->setHeaderText($page_title)
+ ->setForm($form);
+
+ $crumbs = $this->buildApplicationCrumbs();
+
+ if (!$this->isCreate()) {
+ $crumbs->addTextCrumb('U'.$url->getId(), '/U'.$url->getId());
+ }
+
+ $crumbs->addTextCrumb($page_title);
+
+ $object_box = id(new PHUIObjectBoxView())
+ ->setHeaderText($page_title)
+ ->setValidationException($validation_exception)
+ ->appendChild($form);
+
+ return $this->buildApplicationPage(
+ array(
+ $crumbs,
+ $object_box,
+ ),
+ array(
+ 'title' => $page_title,
+ ));
+ }
+}
diff --git a/src/applications/phurl/controller/PhabricatorPhurlURLListController.php b/src/applications/phurl/controller/PhabricatorPhurlURLListController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phurl/controller/PhabricatorPhurlURLListController.php
@@ -0,0 +1,34 @@
+<?php
+
+final class PhabricatorPhurlURLListController
+ extends PhabricatorPhurlController {
+
+ public function shouldAllowPublic() {
+ return true;
+ }
+
+ public function handleRequest(AphrontRequest $request) {
+ $engine = new PhabricatorPhurlURLSearchEngine();
+ $controller = id(new PhabricatorApplicationSearchController())
+ ->setQueryKey($request->getURIData('queryKey'))
+ ->setSearchEngine($engine)
+ ->setNavigation($this->buildSideNav());
+ return $this->delegateToController($controller);
+ }
+
+ public function buildSideNav() {
+ $user = $this->getRequest()->getUser();
+
+ $nav = new AphrontSideNavFilterView();
+ $nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
+
+ id(new PhabricatorPhurlURLSearchEngine())
+ ->setViewer($user)
+ ->addNavigationItems($nav->getMenu());
+
+ $nav->selectFilter(null);
+
+ return $nav;
+ }
+
+}
diff --git a/src/applications/phurl/controller/PhabricatorPhurlURLViewController.php b/src/applications/phurl/controller/PhabricatorPhurlURLViewController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phurl/controller/PhabricatorPhurlURLViewController.php
@@ -0,0 +1,147 @@
+<?php
+
+final class PhabricatorPhurlURLViewController
+ extends PhabricatorPhurlController {
+
+ private $id;
+
+ public function shouldAllowPublic() {
+ return true;
+ }
+
+ public function willProcessRequest(array $data) {
+ $this->id = $data['id'];
+ }
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $timeline = null;
+
+ $url = id(new PhabricatorPhurlURLQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($this->id))
+ ->executeOne();
+ if (!$url) {
+ return new Aphront404Response();
+ }
+
+ $title = 'U'.$url->getID();
+ $page_title = $title.' '.$url->getName();
+ $crumbs = $this->buildApplicationCrumbs();
+ $crumbs->addTextCrumb($title, '/U'.$url->getID());
+
+ $timeline = $this->buildTransactionTimeline(
+ $url,
+ new PhabricatorPhurlURLTransactionQuery());
+
+ $header = $this->buildHeaderView($url);
+ $actions = $this->buildActionView($url);
+ $properties = $this->buildPropertyView($url);
+
+ $properties->setActionList($actions);
+ $box = id(new PHUIObjectBoxView())
+ ->setHeader($header)
+ ->addPropertyList($properties);
+
+ $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
+ // $add_comment_header = $is_serious
+ // ? pht('Add Comment')
+ // : pht('More Cowbell');
+ // $draft = PhabricatorDraft::newFromUserAndKey($viewer, $url->getPHID());
+ // $comment_uri = $this->getApplicationURI(
+ // '/phurl/comment/'.$url->getID().'/');
+ // $add_comment_form = id(new PhabricatorApplicationTransactionCommentView())
+ // ->setUser($viewer)
+ // ->setObjectPHID($url->getPHID())
+ // ->setDraft($draft)
+ // ->setHeaderText($add_comment_header)
+ // ->setAction($comment_uri)
+ // ->setSubmitButtonName(pht('Add Comment'));
+
+ return $this->buildApplicationPage(
+ array(
+ $crumbs,
+ $box,
+ $timeline,
+ // $add_comment_form,
+ ),
+ array(
+ 'title' => $page_title,
+ 'pageObjects' => array($url->getPHID()),
+ ));
+ }
+
+ private function buildHeaderView(PhabricatorPhurlURL $url) {
+ $viewer = $this->getRequest()->getUser();
+ $id = $url->getID();
+
+ $icon = 'fa-compress';
+ $color = 'green';
+ $status = pht('Active');
+
+ $header = id(new PHUIHeaderView())
+ ->setUser($viewer)
+ ->setHeader($url->getName())
+ ->setStatus($icon, $color, $status)
+ ->setPolicyObject($url);
+
+ return $header;
+ }
+
+ private function buildActionView(PhabricatorPhurlURL $url) {
+ $viewer = $this->getRequest()->getUser();
+ $id = $url->getID();
+
+ $actions = id(new PhabricatorActionListView())
+ ->setObjectURI($this->getApplicationURI('phurl/'.$id.'/'))
+ ->setUser($viewer)
+ ->setObject($url);
+
+ $can_edit = PhabricatorPolicyFilter::hasCapability(
+ $viewer,
+ $url,
+ PhabricatorPolicyCapability::CAN_EDIT);
+
+ $actions->addAction(
+ id(new PhabricatorActionView())
+ ->setName(pht('Edit'))
+ ->setIcon('fa-pencil')
+ ->setHref($this->getApplicationURI("phurl/edit/{$id}/"))
+ ->setDisabled(!$can_edit)
+ ->setWorkflow(!$can_edit));
+
+ return $actions;
+ }
+
+ private function buildPropertyView(PhabricatorPhurlURL $url) {
+ $viewer = $this->getRequest()->getUser();
+
+ $properties = id(new PHUIPropertyListView())
+ ->setUser($viewer)
+ ->setObject($url);
+
+ // $properties->invokeWillRenderEvent();
+
+ $properties->addProperty(
+ pht('Original URL'),
+ $url->getLongURL());
+
+ if (strlen($url->getDescription())) {
+ $description = PhabricatorMarkupEngine::renderOneObject(
+ id(new PhabricatorMarkupOneOff())->setContent($url->getDescription()),
+ 'default',
+ $viewer);
+
+ $properties->addSectionHeader(
+ pht('Description'),
+ PHUIPropertyListView::ICON_SUMMARY);
+
+ $properties->addTextContent($description);
+ }
+
+ return $properties;
+ }
+
+}
diff --git a/src/applications/phurl/editor/PhabricatorPhurlURLEditor.php b/src/applications/phurl/editor/PhabricatorPhurlURLEditor.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phurl/editor/PhabricatorPhurlURLEditor.php
@@ -0,0 +1,234 @@
+<?php
+
+final class PhabricatorPhurlURLEditor
+ extends PhabricatorApplicationTransactionEditor {
+
+ public function getEditorApplicationClass() {
+ return 'PhabricatorPhurlApplication';
+ }
+
+ public function getEditorObjectsDescription() {
+ return pht('Phurl');
+ }
+
+ public function getTransactionTypes() {
+ $types = parent::getTransactionTypes();
+
+ $types[] = PhabricatorPhurlURLTransaction::TYPE_NAME;
+ $types[] = PhabricatorPhurlURLTransaction::TYPE_URL;
+ $types[] = PhabricatorPhurlURLTransaction::TYPE_DESCRIPTION;
+
+ $types[] = PhabricatorTransactions::TYPE_COMMENT;
+ $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
+ $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
+
+ return $types;
+ }
+
+ protected function getCustomTransactionOldValue(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+ switch ($xaction->getTransactionType()) {
+ case PhabricatorPhurlURLTransaction::TYPE_NAME:
+ return $object->getName();
+ case PhabricatorPhurlURLTransaction::TYPE_URL:
+ return $object->getLongURL();
+ case PhabricatorPhurlURLTransaction::TYPE_DESCRIPTION:
+ return $object->getDescription();
+ }
+
+ return parent::getCustomTransactionOldValue($object, $xaction);
+ }
+
+ protected function getCustomTransactionNewValue(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+ switch ($xaction->getTransactionType()) {
+ case PhabricatorPhurlURLTransaction::TYPE_NAME:
+ case PhabricatorPhurlURLTransaction::TYPE_URL:
+ case PhabricatorPhurlURLTransaction::TYPE_DESCRIPTION:
+ return $xaction->getNewValue();
+ }
+
+ return parent::getCustomTransactionNewValue($object, $xaction);
+ }
+
+ protected function applyCustomInternalTransaction(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+
+ switch ($xaction->getTransactionType()) {
+ case PhabricatorPhurlURLTransaction::TYPE_NAME:
+ $object->setName($xaction->getNewValue());
+ return;
+ case PhabricatorPhurlURLTransaction::TYPE_URL:
+ var_dump($xaction->getNewValue());
+ $object->setLongURL($xaction->getNewValue());
+ return;
+ case PhabricatorPhurlURLTransaction::TYPE_DESCRIPTION:
+ $object->setDescription($xaction->getNewValue());
+ return;
+ }
+
+ return parent::applyCustomInternalTransaction($object, $xaction);
+ }
+
+ protected function applyCustomExternalTransaction(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+
+ switch ($xaction->getTransactionType()) {
+ case PhabricatorPhurlURLTransaction::TYPE_NAME:
+ case PhabricatorPhurlURLTransaction::TYPE_URL:
+ case PhabricatorPhurlURLTransaction::TYPE_DESCRIPTION:
+ return;
+ }
+
+ return parent::applyCustomExternalTransaction($object, $xaction);
+ }
+
+ protected function didApplyInternalEffects(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+
+ return $xactions;
+ }
+
+ protected function applyFinalEffects(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+ return $xactions;
+ }
+
+
+ protected function validateAllTransactions(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+ $errors = array();
+ return $errors;
+ }
+
+ protected function validateTransaction(
+ PhabricatorLiskDAO $object,
+ $type,
+ array $xactions) {
+
+ $errors = parent::validateTransaction($object, $type, $xactions);
+
+ switch ($type) {
+ case PhabricatorPhurlURLTransaction::TYPE_NAME:
+ $missing = $this->validateIsEmptyTextField(
+ $object->getName(),
+ $xactions);
+
+ if ($missing) {
+ $error = new PhabricatorApplicationTransactionValidationError(
+ $type,
+ pht('Required'),
+ pht('URL name is required.'),
+ nonempty(last($xactions), null));
+
+ $error->setIsMissingFieldError(true);
+ $errors[] = $error;
+ }
+ break;
+ case PhabricatorPhurlURLTransaction::TYPE_URL:
+ $missing = $this->validateIsEmptyTextField(
+ $object->getLongURL(),
+ $xactions);
+
+ if ($missing) {
+ $error = new PhabricatorApplicationTransactionValidationError(
+ $type,
+ pht('Required'),
+ pht('URL path is required.'),
+ nonempty(last($xactions), null));
+
+ $error->setIsMissingFieldError(true);
+ $errors[] = $error;
+ }
+ break;
+ }
+
+ return $errors;
+ }
+
+ protected function shouldPublishFeedStory(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+ return true;
+ }
+
+ protected function supportsSearch() {
+ return true;
+ }
+
+ protected function shouldSendMail(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+ return true;
+ }
+
+ protected function getMailSubjectPrefix() {
+ return pht('[Phurl]');
+ }
+
+ protected function getMailTo(PhabricatorLiskDAO $object) {
+ $phids = array();
+
+ if ($object->getPHID()) {
+ $phids[] = $object->getPHID();
+ }
+ $phids[] = $this->getActingAsPHID();
+ $phids = array_unique($phids);
+
+ return $phids;
+ }
+
+ public function getMailTagsMap() {
+ return array(
+ PhabricatorPhurlURLTransaction::MAILTAG_CONTENT =>
+ pht(
+ "A URL's name or path changes."),
+ PhabricatorPhurlURLTransaction::MAILTAG_OTHER =>
+ pht('Other event activity not listed above occurs.'),
+ );
+ }
+
+ // protected function buildReplyHandler(PhabricatorLiskDAO $object) {
+ // return id(new PhabricatorCalendarReplyHandler())
+ // ->setMailReceiver($object);
+ // }
+
+ protected function buildMailTemplate(PhabricatorLiskDAO $object) {
+ $id = $object->getID();
+ $name = $object->getName();
+
+ return id(new PhabricatorMetaMTAMail())
+ ->setSubject("U{$id}: {$name}")
+ ->addHeader('Thread-Topic', "U{$id}: ".$object->getName());
+ }
+
+ protected function buildMailBody(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+
+ $description = $object->getDescription();
+ $body = parent::buildMailBody($object, $xactions);
+
+ if (strlen($description)) {
+ $body->addTextSection(
+ pht('URL DESCRIPTION'),
+ $object->getDescription());
+ }
+
+ $body->addLinkSection(
+ pht('URL DETAIL'),
+ PhabricatorEnv::getProductionURI('/U'.$object->getID()));
+
+
+ return $body;
+ }
+
+
+}
diff --git a/src/applications/phurl/phid/PhabricatorPhurlURLPHIDType.php b/src/applications/phurl/phid/PhabricatorPhurlURLPHIDType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phurl/phid/PhabricatorPhurlURLPHIDType.php
@@ -0,0 +1,73 @@
+<?php
+
+final class PhabricatorPhurlURLPHIDType extends PhabricatorPHIDType {
+
+ const TYPECONST = 'PHRL';
+
+ public function getTypeName() {
+ return pht('URL');
+ }
+
+ public function getPHIDTypeApplicationClass() {
+ return 'PhabricatorPhurlApplication';
+ }
+
+ public function newObject() {
+ return new PhabricatorPhurlURL();
+ }
+
+ protected function buildQueryForObjects(
+ PhabricatorObjectQuery $query,
+ array $phids) {
+
+ return id(new PhabricatorPhurlURLQuery())
+ ->withPHIDs($phids);
+ }
+
+ public function loadHandles(
+ PhabricatorHandleQuery $query,
+ array $handles,
+ array $objects) {
+
+ foreach ($handles as $phid => $handle) {
+ $url = $objects[$phid];
+
+ $id = $url->getID();
+ $name = $url->getName();
+
+ $handle
+ ->setName($name)
+ ->setFullName(pht('U%d: %s', $id, $name))
+ ->setURI('/U'.$id.'/view');
+ }
+ }
+
+ public function canLoadNamedObject($name) {
+ return preg_match('/^U[1-9]\d*$/i', $name);
+ }
+
+ public function loadNamedObjects(
+ PhabricatorObjectQuery $query,
+ array $names) {
+
+ $id_map = array();
+ foreach ($names as $name) {
+ $id = (int)substr($name, 1);
+ $id_map[$id][] = $name;
+ }
+
+ $objects = id(new PhabricatorPhurlURLQuery())
+ ->setViewer($query->getViewer())
+ ->withIDs(array_keys($id_map))
+ ->execute();
+
+ $results = array();
+ foreach ($objects as $id => $object) {
+ foreach (idx($id_map, $id, array()) as $name) {
+ $results[$name] = $object;
+ }
+ }
+
+ return $results;
+ }
+}
diff --git a/src/applications/phurl/query/PhabricatorPhurlURLQuery.php b/src/applications/phurl/query/PhabricatorPhurlURLQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phurl/query/PhabricatorPhurlURLQuery.php
@@ -0,0 +1,125 @@
+<?php
+
+final class PhabricatorPhurlURLQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $ids;
+ private $phids;
+ private $names;
+ private $longURLs;
+
+ public function newResultObject() {
+ return new PhabricatorPhurlURL();
+ }
+
+ public function withIDs(array $ids) {
+ $this->ids = $ids;
+ return $this;
+ }
+
+ public function withPHIDs(array $phids) {
+ $this->phids = $phids;
+ return $this;
+ }
+
+ public function withNames(array $names) {
+ $this->names = $names;
+ return $this;
+ }
+
+ public function withLongURLs(array $long_urls) {
+ $this->longURLs = $long_urls;
+ return $this;
+ }
+
+ public function getOrderableColumns() {
+ return array(
+ 'start' => array(
+ 'table' => $this->getPrimaryTableAlias(),
+ 'column' => 'id',
+ 'reverse' => true,
+ 'type' => 'int',
+ 'unique' => false,
+ ),
+ ) + parent::getOrderableColumns();
+ }
+
+ protected function getPagingValueMap($cursor, array $keys) {
+ $url = $this->loadCursorObject($cursor);
+ return array(
+ 'id' => $url->getID(),
+ );
+ }
+
+ protected function loadPage() {
+ $table = new PhabricatorPhurlURL();
+ $conn_r = $table->establishConnection('r');
+ $viewer = $this->getViewer();
+
+ $data = queryfx_all(
+ $conn_r,
+ 'SELECT url.* FROM %T url %Q %Q %Q %Q %Q',
+ $table->getTableName(),
+ $this->buildJoinClause($conn_r),
+ $this->buildWhereClause($conn_r),
+ $this->buildGroupClause($conn_r),
+ $this->buildOrderClause($conn_r),
+ $this->buildLimitClause($conn_r));
+
+ return $table->loadAllFromArray($data);
+ }
+
+ protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+ $where = array();
+
+ if ($this->ids) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'url.id IN (%Ld)',
+ $this->ids);
+ }
+
+ if ($this->phids) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'url.phid IN (%Ls)',
+ $this->phids);
+ }
+
+ if ($this->names) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'url.name IN (%Ls)',
+ $this->names);
+ }
+
+ if ($this->longURLs) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'url.longURL IN (%Ls)',
+ $this->longURLs);
+ }
+
+ $where[] = $this->buildPagingClause($conn_r);
+
+ return $this->formatWhereClause($where);
+ }
+
+ protected function getPrimaryTableAlias() {
+ return 'url';
+ }
+
+ protected function getApplicationSearchObjectPHIDColumn() {
+ return 'url.phid';
+ }
+
+ public function getQueryApplicationClass() {
+ return 'PhabricatorPhurlApplication';
+ }
+
+
+ protected function willFilterPage(array $urls) {
+ return $urls;
+ }
+
+}
diff --git a/src/applications/phurl/query/PhabricatorPhurlURLSearchEngine.php b/src/applications/phurl/query/PhabricatorPhurlURLSearchEngine.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phurl/query/PhabricatorPhurlURLSearchEngine.php
@@ -0,0 +1,110 @@
+<?php
+
+final class PhabricatorPhurlURLSearchEngine
+ extends PhabricatorApplicationSearchEngine {
+
+ public function getResultTypeDescription() {
+ return pht('Shortened URLs');
+ }
+
+ public function getApplicationClassName() {
+ return 'PhabricatorPhurlApplication';
+ }
+
+ public function newQuery() {
+ return new PhabricatorPhurlURLQuery();
+ }
+
+ protected function shouldShowOrderField() {
+ return false;
+ }
+
+ protected function buildCustomSearchFields() {
+ return array(
+ id(new PhabricatorSearchDatasourceField())
+ ->setLabel(pht('Created By'))
+ ->setKey('creatorPHIDs')
+ ->setDatasource(new PhabricatorPeopleUserFunctionDatasource()),
+ );
+ }
+
+ protected function buildQueryFromParameters(array $map) {
+ $query = $this->newQuery();
+ $viewer = $this->requireViewer();
+
+ if ($map['creatorPHIDs']) {
+ $query->withPHIDs($map['creatorPHIDs']);
+ }
+
+ return $query;
+ }
+
+ protected function getURI($path) {
+ return '/phurl/'.$path;
+ }
+
+ protected function getBuiltinQueryNames() {
+ $names = array(
+ 'authored' => pht('Authored'),
+ 'all' => pht('All URLs'),
+ );
+
+ return $names;
+ }
+
+ public function buildSavedQueryFromBuiltin($query_key) {
+ $query = $this->newSavedQuery();
+ $query->setQueryKey($query_key);
+ $viewer = $this->requireViewer();
+
+ switch ($query_key) {
+ case 'authored':
+ return $query->setParameter('creatorPHIDs', array($viewer->getPHID()));
+ case 'all':
+ return $query;
+ }
+
+ return parent::buildSavedQueryFromBuiltin($query_key);
+ }
+
+ protected function getRequiredHandlePHIDsForResultList(
+ array $objects,
+ PhabricatorSavedQuery $query) {
+ $phids = array();
+ foreach ($objects as $url) {
+ $phids[$url->getUserPHID()] = 1;
+ }
+ return array_keys($phids);
+ }
+
+ protected function renderResultList(
+ array $urls,
+ PhabricatorSavedQuery $query,
+ array $handles) {
+
+ assert_instances_of($urls, 'PhabricatorPhurlURL');
+ $viewer = $this->requireViewer();
+ $list = new PHUIObjectItemListView();
+
+ foreach ($urls as $url) {
+ $creator_handle = $handles[$url->getUserPHID()];
+
+ $item = id(new PHUIObjectItemView())
+ ->setUser($viewer)
+ ->setObject($url)
+ ->setHeader($viewer->renderHandle($url->getPHID())->render());
+
+ $list->addItem($item);
+ }
+
+ $result = new PhabricatorApplicationSearchResultView();
+ $result->setObjectList($list);
+ $result->setNoDataString(pht('No URLs found.'));
+
+ return $result;
+ }
+
+ public function getPageSize(PhabricatorSavedQuery $saved) {
+ return $saved->getParameter('limit', 100);
+ }
+}
diff --git a/src/applications/phurl/query/PhabricatorPhurlURLTransactionQuery.php b/src/applications/phurl/query/PhabricatorPhurlURLTransactionQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phurl/query/PhabricatorPhurlURLTransactionQuery.php
@@ -0,0 +1,10 @@
+<?php
+
+final class PhabricatorPhurlURLTransactionQuery
+ extends PhabricatorApplicationTransactionQuery {
+
+ public function getTemplateApplicationTransaction() {
+ return new PhabricatorPhurlURLTransaction();
+ }
+
+}
diff --git a/src/applications/phurl/storage/PhabricatorPhurlDAO.php b/src/applications/phurl/storage/PhabricatorPhurlDAO.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phurl/storage/PhabricatorPhurlDAO.php
@@ -0,0 +1,9 @@
+<?php
+
+abstract class PhabricatorPhurlDAO extends PhabricatorLiskDAO {
+
+ public function getApplicationName() {
+ return 'phurl';
+ }
+
+}
diff --git a/src/applications/phurl/storage/PhabricatorPhurlSchemaSpec.php b/src/applications/phurl/storage/PhabricatorPhurlSchemaSpec.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phurl/storage/PhabricatorPhurlSchemaSpec.php
@@ -0,0 +1,10 @@
+<?php
+
+final class PhabricatorPhurlSchemaSpec
+ extends PhabricatorConfigSchemaSpec {
+
+ public function buildSchemata() {
+ $this->buildEdgeSchemata(new PhabricatorPhurlURL());
+ }
+
+}
diff --git a/src/applications/phurl/storage/PhabricatorPhurlURL.php b/src/applications/phurl/storage/PhabricatorPhurlURL.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phurl/storage/PhabricatorPhurlURL.php
@@ -0,0 +1,230 @@
+<?php
+
+final class PhabricatorPhurlURL extends PhabricatorPhurlDAO
+ implements PhabricatorPolicyInterface,
+ PhabricatorProjectInterface,
+ PhabricatorMarkupInterface,
+ PhabricatorApplicationTransactionInterface,
+ PhabricatorSubscribableInterface,
+ PhabricatorTokenReceiverInterface,
+ PhabricatorDestructibleInterface,
+ PhabricatorMentionableInterface,
+ PhabricatorFlaggableInterface,
+ PhabricatorSpacesInterface {
+
+ protected $name;
+ protected $longURL;
+ protected $description;
+
+ protected $viewPolicy;
+ protected $editPolicy;
+
+ protected $spacePHID;
+
+ const DEFAULT_ICON = 'fa-compress';
+
+ public static function initializeNewPhurlURL(PhabricatorUser $actor) {
+ $app = id(new PhabricatorApplicationQuery())
+ ->setViewer($actor)
+ ->withClasses(array('PhabricatorPhurlApplication'))
+ ->executeOne();
+
+ return id(new PhabricatorPhurlURL())
+ ->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy())
+ ->setEditPolicy($actor->getPHID())
+ ->setSpacePHID($actor->getDefaultSpacePHID());
+ }
+
+ public function save() {
+ // if (!$this->mailKey) {
+ // $this->mailKey = Filesystem::readRandomCharacters(20);
+ // }
+
+ return parent::save();
+ }
+
+ protected function getConfiguration() {
+ return array(
+ self::CONFIG_AUX_PHID => true,
+ self::CONFIG_COLUMN_SCHEMA => array(
+ 'name' => 'text',
+ 'longURL' => 'text',
+ 'description' => 'text',
+ 'icon' => 'text32',
+ 'mailKey' => 'bytes20',
+ ),
+ // self::CONFIG_KEY_SCHEMA => array(
+ // 'userPHID_dateFrom' => array(
+ // 'columns' => array('userPHID', 'dateTo'),
+ // ),
+ // 'key_instance' => array(
+ // 'columns' => array('instanceOfEventPHID', 'sequenceIndex'),
+ // 'unique' => true,
+ // ),
+ // ),
+ // self::CONFIG_SERIALIZATION => array(
+ // 'recurrenceFrequency' => self::SERIALIZATION_JSON,
+ // ),
+ ) + parent::getConfiguration();
+ }
+
+ public function generatePHID() {
+ return PhabricatorPHID::generateNewPHID(
+ PhabricatorPhurlURLPHIDType::TYPECONST);
+ }
+
+ public function getMonogram() {
+ return 'U'.$this->getID();
+ }
+
+ public function getURI() {
+ $uri = '/'.$this->getMonogram();
+ return $uri;
+ }
+
+/* -( Markup Interface )--------------------------------------------------- */
+
+
+ /**
+ * @task markup
+ */
+ public function getMarkupFieldKey($field) {
+ $hash = PhabricatorHash::digest($this->getMarkupText($field));
+ $id = $this->getID();
+ return "phurl:U{$id}:{$field}:{$hash}";
+ }
+
+
+ /**
+ * @task markup
+ */
+ public function getMarkupText($field) {
+ return $this->getDescription();
+ }
+
+
+ /**
+ * @task markup
+ */
+ public function newMarkupEngine($field) {
+ return PhabricatorMarkupEngine::newCalendarMarkupEngine();
+ }
+
+
+ /**
+ * @task markup
+ */
+ public function didMarkupText(
+ $field,
+ $output,
+ PhutilMarkupEngine $engine) {
+ return $output;
+ }
+
+
+ /**
+ * @task markup
+ */
+ public function shouldUseMarkupCache($field) {
+ return (bool)$this->getID();
+ }
+
+/* -( PhabricatorPolicyInterface )----------------------------------------- */
+
+
+ public function getCapabilities() {
+ return array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ );
+ }
+
+ public function getPolicy($capability) {
+ switch ($capability) {
+ case PhabricatorPolicyCapability::CAN_VIEW:
+ return $this->getViewPolicy();
+ case PhabricatorPolicyCapability::CAN_EDIT:
+ return $this->getEditPolicy();
+ }
+ }
+
+ public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+ $user_phid = $this->getPHID();
+ if ($user_phid) {
+ $viewer_phid = $viewer->getPHID();
+ if ($viewer_phid == $user_phid) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public function describeAutomaticCapability($capability) {
+ return pht('The owner of an event can always view and edit it,
+ and invitees can always view it, except if the event is an
+ instance of a recurring event.');
+ }
+
+/* -( PhabricatorApplicationTransactionInterface )------------------------- */
+
+
+ public function getApplicationTransactionEditor() {
+ return new PhabricatorPhurlURLEditor();
+ }
+
+ public function getApplicationTransactionObject() {
+ return $this;
+ }
+
+ public function getApplicationTransactionTemplate() {
+ return new PhabricatorPhurlURLTransaction();
+ }
+
+ public function willRenderTimeline(
+ PhabricatorApplicationTransactionView $timeline,
+ AphrontRequest $request) {
+
+ return $timeline;
+ }
+
+/* -( PhabricatorSubscribableInterface )----------------------------------- */
+
+
+ public function isAutomaticallySubscribed($phid) {
+ return ($phid == $this->getPHID());
+ }
+
+ public function shouldShowSubscribersProperty() {
+ return true;
+ }
+
+ public function shouldAllowSubscription($phid) {
+ return true;
+ }
+
+/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
+
+
+ public function getUsersToNotifyOfTokenGiven() {
+ return array($this->getUserPHID());
+ }
+
+/* -( PhabricatorDestructibleInterface )----------------------------------- */
+
+
+ public function destroyObjectPermanently(
+ PhabricatorDestructionEngine $engine) {
+
+ $this->openTransaction();
+ $this->delete();
+ $this->saveTransaction();
+ }
+
+/* -( PhabricatorSpacesInterface )----------------------------------------- */
+
+
+ public function getSpacePHID() {
+ return $this->spacePHID;
+ }
+}
diff --git a/src/applications/phurl/storage/PhabricatorPhurlURLTransaction.php b/src/applications/phurl/storage/PhabricatorPhurlURLTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phurl/storage/PhabricatorPhurlURLTransaction.php
@@ -0,0 +1,201 @@
+<?php
+
+final class PhabricatorPhurlURLTransaction
+ extends PhabricatorApplicationTransaction {
+
+ const TYPE_NAME = 'phurl.name';
+ const TYPE_URL = 'phurl.longurl';
+ const TYPE_DESCRIPTION = 'phurl.description';
+
+ // const MAILTAG_RESCHEDULE = 'calendar-reschedule';
+ // const MAILTAG_CONTENT = 'calendar-content';
+ // const MAILTAG_OTHER = 'calendar-other';
+
+ public function getApplicationName() {
+ return 'phurl';
+ }
+
+ public function getApplicationTransactionType() {
+ return PhabricatorPhurlURLPHIDType::TYPECONST;
+ }
+
+ public function getApplicationTransactionCommentObject() {
+ return new PhabricatorPhurlURLTransactionComment();
+ }
+
+ public function getRequiredHandlePHIDs() {
+ $phids = parent::getRequiredHandlePHIDs();
+
+ switch ($this->getTransactionType()) {
+ case self::TYPE_NAME:
+ case self::TYPE_URL:
+ case self::TYPE_DESCRIPTION:
+ $phids[] = $this->getObjectPHID();
+ break;
+ }
+
+ return $phids;
+ }
+
+ public function shouldHide() {
+ $old = $this->getOldValue();
+ switch ($this->getTransactionType()) {
+ case self::TYPE_DESCRIPTION:
+ return ($old === null);
+ }
+ return parent::shouldHide();
+ }
+
+ public function getIcon() {
+ switch ($this->getTransactionType()) {
+ case self::TYPE_NAME:
+ case self::TYPE_URL:
+ case self::TYPE_DESCRIPTION:
+ return 'fa-pencil';
+ break;
+ }
+ return parent::getIcon();
+ }
+
+ public function getTitle() {
+ $author_phid = $this->getAuthorPHID();
+ $object_phid = $this->getObjectPHID();
+
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ $type = $this->getTransactionType();
+ switch ($type) {
+ case self::TYPE_NAME:
+ if ($old === null) {
+ return pht(
+ '%s created this URL.',
+ $this->renderHandleLink($author_phid));
+ } else {
+ return pht(
+ '%s changed the name of the URL from %s to %s.',
+ $this->renderHandleLink($author_phid),
+ $old,
+ $new);
+ }
+ case self::TYPE_URL:
+ if ($old === null) {
+ return pht(
+ '%s created this URL',
+ $this->renderHandleLink($author_phid));
+ } else {
+ return pht(
+ '%s changed the destination of the URL from %s to %s.',
+ $this->renderHandleLink($author_phid),
+ $old,
+ $new);
+ }
+ case self::TYPE_DESCRIPTION:
+ return pht(
+ "%s updated the URL's description.",
+ $this->renderHandleLink($author_phid));
+ }
+ return parent::getTitle();
+ }
+
+ public function getTitleForFeed() {
+ $author_phid = $this->getAuthorPHID();
+ $object_phid = $this->getObjectPHID();
+
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ $viewer = $this->getViewer();
+
+ $type = $this->getTransactionType();
+ switch ($type) {
+ case self::TYPE_NAME:
+ if ($old === null) {
+ return pht(
+ '%s created %s.',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($object_phid));
+ } else {
+ return pht(
+ '%s changed the name of %s from %s to %s.',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($object_phid),
+ $old,
+ $new);
+ }
+ case self::TYPE_URL:
+ if ($old === null) {
+ return pht(
+ '%s created %s.',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($object_phid));
+ } else {
+ return pht(
+ '%s changed the destination of %s from %s to %s',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($object_phid),
+ $old,
+ $new);
+ }
+ case self::TYPE_DESCRIPTION:
+ return pht(
+ '%s updated the description of %s.',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($object_phid));
+ }
+
+ return parent::getTitleForFeed();
+ }
+
+ public function getColor() {
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ switch ($this->getTransactionType()) {
+ case self::TYPE_NAME:
+ case self::TYPE_URL:
+ case self::TYPE_DESCRIPTION:
+ return PhabricatorTransactions::COLOR_GREEN;
+ }
+
+ return parent::getColor();
+ }
+
+
+ public function hasChangeDetails() {
+ switch ($this->getTransactionType()) {
+ case self::TYPE_DESCRIPTION:
+ return ($this->getOldValue() !== null);
+ }
+
+ return parent::hasChangeDetails();
+ }
+
+ public function renderChangeDetails(PhabricatorUser $viewer) {
+ switch ($this->getTransactionType()) {
+ case self::TYPE_DESCRIPTION:
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ return $this->renderTextCorpusChangeDetails(
+ $viewer,
+ $old,
+ $new);
+ }
+
+ return parent::renderChangeDetails($viewer);
+ }
+
+ public function getMailTags() {
+ $tags = array();
+ switch ($this->getTransactionType()) {
+ case self::TYPE_NAME:
+ case self::TYPE_DESCRIPTION:
+ case self::TYPE_URL:
+ $tags[] = self::MAILTAG_CONTENT;
+ break;
+ }
+ return $tags;
+ }
+
+}
diff --git a/src/applications/phurl/storage/PhabricatorPhurlURLTransactionComment.php b/src/applications/phurl/storage/PhabricatorPhurlURLTransactionComment.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phurl/storage/PhabricatorPhurlURLTransactionComment.php
@@ -0,0 +1,10 @@
+<?php
+
+final class PhabricatorPhurlURLTransactionComment
+ extends PhabricatorApplicationTransactionComment {
+
+ public function getApplicationTransactionObject() {
+ return new PhabricatorPhurlURLTransaction();
+ }
+
+}
diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
--- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
+++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
@@ -106,6 +106,7 @@
'db.almanac' => array(),
'db.multimeter' => array(),
'db.spaces' => array(),
+ 'db.phurl' => array(),
'0000.legacy.sql' => array(
'legacy' => 0,
),

File Metadata

Mime Type
text/plain
Expires
Oct 15 2024, 1:18 AM (4 w, 6 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6710768
Default Alt Text
D13681.id33073.diff (52 KB)

Event Timeline