diff --git a/resources/sql/autopatches/20151130.phurl.mailkey.1.sql b/resources/sql/autopatches/20151130.phurl.mailkey.1.sql new file mode 100644 index 0000000000..67e9e25586 --- /dev/null +++ b/resources/sql/autopatches/20151130.phurl.mailkey.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phurl.phurl_url + ADD mailKey binary(20) NOT NULL; diff --git a/resources/sql/autopatches/20151130.phurl.mailkey.2.php b/resources/sql/autopatches/20151130.phurl.mailkey.2.php new file mode 100644 index 0000000000..93c7e7a4d2 --- /dev/null +++ b/resources/sql/autopatches/20151130.phurl.mailkey.2.php @@ -0,0 +1,18 @@ +establishConnection('w'); +$iterator = new LiskMigrationIterator($table); +foreach ($iterator as $url) { + $id = $url->getID(); + + echo pht('Adding mail key for Phurl %d...', $id); + echo "\n"; + + queryfx( + $conn_w, + 'UPDATE %T SET mailKey = %s WHERE id = %d', + $table->getTableName(), + Filesystem::readRandomCharacters(20), + $id); +} diff --git a/src/applications/phurl/editor/PhabricatorPhurlURLEditor.php b/src/applications/phurl/editor/PhabricatorPhurlURLEditor.php index 712478c839..c36100f12b 100644 --- a/src/applications/phurl/editor/PhabricatorPhurlURLEditor.php +++ b/src/applications/phurl/editor/PhabricatorPhurlURLEditor.php @@ -1,267 +1,265 @@ getTransactionType()) { case PhabricatorPhurlURLTransaction::TYPE_NAME: return $object->getName(); case PhabricatorPhurlURLTransaction::TYPE_URL: return $object->getLongURL(); case PhabricatorPhurlURLTransaction::TYPE_ALIAS: return $object->getAlias(); 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(); case PhabricatorPhurlURLTransaction::TYPE_ALIAS: if (!strlen($xaction->getNewValue())) { return null; } 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: $object->setLongURL($xaction->getNewValue()); return; case PhabricatorPhurlURLTransaction::TYPE_ALIAS: $object->setAlias($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_ALIAS: case PhabricatorPhurlURLTransaction::TYPE_DESCRIPTION: return; } return parent::applyCustomExternalTransaction($object, $xaction); } protected function validateTransaction( PhabricatorLiskDAO $object, $type, array $xactions) { $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { case PhabricatorPhurlURLTransaction::TYPE_ALIAS: $overdrawn = $this->validateIsTextFieldTooLong( $object->getName(), $xactions, 64); if ($overdrawn) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Alias Too Long'), pht('The alias can be no longer than 64 characters.'), nonempty(last($xactions), null)); } foreach ($xactions as $xaction) { if ($xaction->getOldValue() != $xaction->getNewValue()) { $new_alias = $xaction->getNewValue(); if (!preg_match('/[a-zA-Z]/', $new_alias)) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid Alias'), pht('The alias must contain at least one letter.'), $xaction); } if (preg_match('/[^a-z0-9]/i', $new_alias)) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid Alias'), pht('The alias may only contain letters and numbers.'), $xaction); } } } 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; } foreach ($xactions as $xaction) { if ($xaction->getOldValue() != $xaction->getNewValue()) { $protocols = PhabricatorEnv::getEnvConfig('uri.allowed-protocols'); $uri = new PhutilURI($xaction->getNewValue()); if (!isset($protocols[$uri->getProtocol()])) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid URL'), pht('The protocol of the URL is invalid.'), null); } } } 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 => + PhabricatorPhurlURLTransaction::MAILTAG_DETAILS => pht( - "A URL's name or path changes."), - PhabricatorPhurlURLTransaction::MAILTAG_OTHER => - pht('Other event activity not listed above occurs.'), + "A URL's details change."), ); } 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->addRemarkupSection( pht('URL DESCRIPTION'), $object->getDescription()); } $body->addLinkSection( pht('URL DETAIL'), PhabricatorEnv::getProductionURI('/U'.$object->getID())); return $body; } protected function didCatchDuplicateKeyException( PhabricatorLiskDAO $object, array $xactions, Exception $ex) { $errors = array(); $errors[] = new PhabricatorApplicationTransactionValidationError( PhabricatorPhurlURLTransaction::TYPE_ALIAS, pht('Duplicate'), pht('This alias is already in use.'), null); throw new PhabricatorApplicationTransactionValidationException($errors); } } diff --git a/src/applications/phurl/storage/PhabricatorPhurlURL.php b/src/applications/phurl/storage/PhabricatorPhurlURL.php index 34a143bc90..7df1d241a7 100644 --- a/src/applications/phurl/storage/PhabricatorPhurlURL.php +++ b/src/applications/phurl/storage/PhabricatorPhurlURL.php @@ -1,194 +1,204 @@ setViewer($actor) ->withClasses(array('PhabricatorPhurlApplication')) ->executeOne(); return id(new PhabricatorPhurlURL()) ->setAuthorPHID($actor->getPHID()) ->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy()) ->setEditPolicy($actor->getPHID()) ->setSpacePHID($actor->getDefaultSpacePHID()); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'text', 'alias' => 'sort64?', 'longURL' => 'text', 'description' => 'text', + 'mailKey' => 'bytes20', ), self::CONFIG_KEY_SCHEMA => array( 'key_instance' => array( 'columns' => array('alias'), 'unique' => true, ), 'key_author' => array( 'columns' => array('authorPHID'), ), ), ) + parent::getConfiguration(); } + public function save() { + if (!$this->getMailKey()) { + $this->setMailKey(Filesystem::readRandomCharacters(20)); + } + return parent::save(); + } + public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorPhurlURLPHIDType::TYPECONST); } public function getMonogram() { return 'U'.$this->getID(); } public function getURI() { $uri = '/'.$this->getMonogram(); return $uri; } public function isValid() { $allowed_protocols = PhabricatorEnv::getEnvConfig('uri.allowed-protocols'); $uri = new PhutilURI($this->getLongURL()); return isset($allowed_protocols[$uri->getProtocol()]); } public function getDisplayName() { if ($this->getName()) { return $this->getName(); } else { return $this->getLongURL(); } } public function getRedirectURI() { if (strlen($this->getAlias())) { return '/u/'.$this->getAlias(); } else { return '/u/'.$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->getAuthorPHID(); 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 a URL can always view and edit it.'); } /* -( 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->getAuthorPHID()); } public function shouldShowSubscribersProperty() { return true; } public function shouldAllowSubscription($phid) { return true; } /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ public function getUsersToNotifyOfTokenGiven() { return array($this->getAuthorPHID()); } /* -( 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 index 6d275bcefb..d520b30518 100644 --- a/src/applications/phurl/storage/PhabricatorPhurlURLTransaction.php +++ b/src/applications/phurl/storage/PhabricatorPhurlURLTransaction.php @@ -1,244 +1,243 @@ getTransactionType()) { case self::TYPE_NAME: case self::TYPE_URL: case self::TYPE_ALIAS: 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_ALIAS: 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 set the destination of the URL to %s.', $this->renderHandleLink($author_phid), $new); } else { return pht( '%s changed the destination of the URL from %s to %s.', $this->renderHandleLink($author_phid), $old, $new); } case self::TYPE_ALIAS: if ($old === null) { return pht( '%s set the alias of the URL to %s.', $this->renderHandleLink($author_phid), $new); } else if ($new === null) { return pht( '%s removed the alias of the URL.', $this->renderHandleLink($author_phid)); } else { return pht( '%s changed the alias 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 set the destination of %s to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $new); } 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_ALIAS: if ($old === null) { return pht( '%s set the alias of %s to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $new); } else if ($new === null) { return pht( '%s removed the alias of %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } else { return pht( '%s changed the alias 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_ALIAS: 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: case self::TYPE_ALIAS: - $tags[] = self::MAILTAG_CONTENT; + $tags[] = self::MAILTAG_DETAILS; break; } return $tags; } }