1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-27 09:12:41 +01:00

Merge branch 'master' into redesign-2015

This commit is contained in:
epriestley 2015-06-16 19:34:36 -07:00
commit f1b7fd483e
64 changed files with 813 additions and 242 deletions

View file

@ -0,0 +1,17 @@
CREATE TABLE {$NAMESPACE}_diviner.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}_diviner.edgedata (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,6 @@
ALTER TABLE {$NAMESPACE}_diviner.diviner_livebook
ADD COLUMN editPolicy VARBINARY(64) NOT NULL AFTER viewPolicy;
UPDATE {$NAMESPACE}_diviner.diviner_livebook
SET editPolicy = 'admin'
WHERE editPolicy = '';

View file

@ -0,0 +1,19 @@
CREATE TABLE {$NAMESPACE}_diviner.diviner_livebooktransaction (
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};

View file

@ -648,19 +648,25 @@ phutil_register_library_map(array(
'DivinerAtomizeWorkflow' => 'applications/diviner/workflow/DivinerAtomizeWorkflow.php',
'DivinerAtomizer' => 'applications/diviner/atomizer/DivinerAtomizer.php',
'DivinerBookController' => 'applications/diviner/controller/DivinerBookController.php',
'DivinerBookEditController' => 'applications/diviner/controller/DivinerBookEditController.php',
'DivinerBookItemView' => 'applications/diviner/view/DivinerBookItemView.php',
'DivinerBookPHIDType' => 'applications/diviner/phid/DivinerBookPHIDType.php',
'DivinerBookQuery' => 'applications/diviner/query/DivinerBookQuery.php',
'DivinerBookSearchIndexer' => 'applications/diviner/search/DivinerBookSearchIndexer.php',
'DivinerController' => 'applications/diviner/controller/DivinerController.php',
'DivinerDAO' => 'applications/diviner/storage/DivinerDAO.php',
'DivinerDefaultEditCapability' => 'applications/diviner/capability/DivinerDefaultEditCapability.php',
'DivinerDefaultRenderer' => 'applications/diviner/renderer/DivinerDefaultRenderer.php',
'DivinerDefaultViewCapability' => 'applications/diviner/capability/DivinerDefaultViewCapability.php',
'DivinerDiskCache' => 'applications/diviner/cache/DivinerDiskCache.php',
'DivinerFileAtomizer' => 'applications/diviner/atomizer/DivinerFileAtomizer.php',
'DivinerFindController' => 'applications/diviner/controller/DivinerFindController.php',
'DivinerGenerateWorkflow' => 'applications/diviner/workflow/DivinerGenerateWorkflow.php',
'DivinerLiveAtom' => 'applications/diviner/storage/DivinerLiveAtom.php',
'DivinerLiveBook' => 'applications/diviner/storage/DivinerLiveBook.php',
'DivinerLiveBookEditor' => 'applications/diviner/editor/DivinerLiveBookEditor.php',
'DivinerLiveBookTransaction' => 'applications/diviner/storage/DivinerLiveBookTransaction.php',
'DivinerLiveBookTransactionQuery' => 'applications/diviner/query/DivinerLiveBookTransactionQuery.php',
'DivinerLivePublisher' => 'applications/diviner/publisher/DivinerLivePublisher.php',
'DivinerLiveSymbol' => 'applications/diviner/storage/DivinerLiveSymbol.php',
'DivinerMainController' => 'applications/diviner/controller/DivinerMainController.php',
@ -670,6 +676,7 @@ phutil_register_library_map(array(
'DivinerPublisher' => 'applications/diviner/publisher/DivinerPublisher.php',
'DivinerRenderer' => 'applications/diviner/renderer/DivinerRenderer.php',
'DivinerReturnTableView' => 'applications/diviner/view/DivinerReturnTableView.php',
'DivinerSchemaSpec' => 'applications/diviner/storage/DivinerSchemaSpec.php',
'DivinerSectionView' => 'applications/diviner/view/DivinerSectionView.php',
'DivinerStaticPublisher' => 'applications/diviner/publisher/DivinerStaticPublisher.php',
'DivinerSymbolRemarkupRule' => 'applications/diviner/markup/DivinerSymbolRemarkupRule.php',
@ -1269,6 +1276,7 @@ phutil_register_library_map(array(
'PassphraseCredentialTypeTestCase' => 'applications/passphrase/credentialtype/__tests__/PassphraseCredentialTypeTestCase.php',
'PassphraseCredentialViewController' => 'applications/passphrase/controller/PassphraseCredentialViewController.php',
'PassphraseDAO' => 'applications/passphrase/storage/PassphraseDAO.php',
'PassphraseNoteCredentialType' => 'applications/passphrase/credentialtype/PassphraseNoteCredentialType.php',
'PassphrasePasswordCredentialType' => 'applications/passphrase/credentialtype/PassphrasePasswordCredentialType.php',
'PassphrasePasswordKey' => 'applications/passphrase/keys/PassphrasePasswordKey.php',
'PassphraseQueryConduitAPIMethod' => 'applications/passphrase/conduit/PassphraseQueryConduitAPIMethod.php',
@ -4005,13 +4013,16 @@ phutil_register_library_map(array(
'DivinerAtomizeWorkflow' => 'DivinerWorkflow',
'DivinerAtomizer' => 'Phobject',
'DivinerBookController' => 'DivinerController',
'DivinerBookEditController' => 'DivinerController',
'DivinerBookItemView' => 'AphrontTagView',
'DivinerBookPHIDType' => 'PhabricatorPHIDType',
'DivinerBookQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'DivinerBookSearchIndexer' => 'PhabricatorSearchDocumentIndexer',
'DivinerController' => 'PhabricatorController',
'DivinerDAO' => 'PhabricatorLiskDAO',
'DivinerDefaultEditCapability' => 'PhabricatorPolicyCapability',
'DivinerDefaultRenderer' => 'DivinerRenderer',
'DivinerDefaultViewCapability' => 'PhabricatorPolicyCapability',
'DivinerDiskCache' => 'Phobject',
'DivinerFileAtomizer' => 'DivinerAtomizer',
'DivinerFindController' => 'DivinerController',
@ -4020,8 +4031,13 @@ phutil_register_library_map(array(
'DivinerLiveBook' => array(
'DivinerDAO',
'PhabricatorPolicyInterface',
'PhabricatorProjectInterface',
'PhabricatorDestructibleInterface',
'PhabricatorApplicationTransactionInterface',
),
'DivinerLiveBookEditor' => 'PhabricatorApplicationTransactionEditor',
'DivinerLiveBookTransaction' => 'PhabricatorApplicationTransaction',
'DivinerLiveBookTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'DivinerLivePublisher' => 'DivinerPublisher',
'DivinerLiveSymbol' => array(
'DivinerDAO',
@ -4036,6 +4052,7 @@ phutil_register_library_map(array(
'DivinerPublisher' => 'Phobject',
'DivinerRenderer' => 'Phobject',
'DivinerReturnTableView' => 'AphrontTagView',
'DivinerSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'DivinerSectionView' => 'AphrontTagView',
'DivinerStaticPublisher' => 'DivinerPublisher',
'DivinerSymbolRemarkupRule' => 'PhutilRemarkupRule',
@ -4761,6 +4778,7 @@ phutil_register_library_map(array(
'PassphraseCredentialTypeTestCase' => 'PhabricatorTestCase',
'PassphraseCredentialViewController' => 'PassphraseController',
'PassphraseDAO' => 'PhabricatorLiskDAO',
'PassphraseNoteCredentialType' => 'PassphraseCredentialType',
'PassphrasePasswordCredentialType' => 'PassphraseCredentialType',
'PassphrasePasswordKey' => 'PassphraseAbstractKey',
'PassphraseQueryConduitAPIMethod' => 'PassphraseConduitAPIMethod',

View file

@ -19,7 +19,7 @@ final class PhabricatorAuditActionConstants extends Phobject {
self::ACCEPT => pht("Accept Commit \xE2\x9C\x94"),
self::RESIGN => pht('Resign from Audit'),
self::CLOSE => pht('Close Audit'),
self::ADD_CCS => pht('Add CCs'),
self::ADD_CCS => pht('Add Subscribers'),
self::ADD_AUDITORS => pht('Add Auditors'),
);

View file

@ -449,14 +449,25 @@ abstract class PhabricatorApplication
$class,
PhabricatorUser $viewer) {
if (!self::isClassInstalled($class)) {
return false;
$cache = PhabricatorCaches::getRequestCache();
$viewer_phid = $viewer->getPHID();
$key = 'app.'.$class.'.installed.'.$viewer_phid;
$result = $cache->getKey($key);
if ($result === null) {
if (!self::isClassInstalled($class)) {
$result = false;
} else {
$result = PhabricatorPolicyFilter::hasCapability(
$viewer,
self::getByClass($class),
PhabricatorPolicyCapability::CAN_VIEW);
}
$cache->setKey($key, $result);
}
return PhabricatorPolicyFilter::hasCapability(
$viewer,
self::getByClass($class),
PhabricatorPolicyCapability::CAN_VIEW);
return $result;
}

View file

@ -9,12 +9,16 @@ abstract class PhabricatorCalendarController extends PhabricatorController {
->setUser($this->getViewer())
->addAction(
id(new PhabricatorActionView())
->setName(pht('Create Private Event'))
->setHref('/calendar/event/create/?mode=private'))
->setName(pht('Create Event'))
->setHref('/calendar/event/create/'))
->addAction(
id(new PhabricatorActionView())
->setName(pht('Create Public Event'))
->setHref('/calendar/event/create/?mode=public'));
->setHref('/calendar/event/create/?mode=public'))
->addAction(
id(new PhabricatorActionView())
->setName(pht('Create Recurring Event'))
->setHref('/calendar/event/create/?mode=recurring'));
$crumbs->addAction(
id(new PHUIListItemView())

View file

@ -291,7 +291,9 @@ final class PhabricatorCalendarEventViewController
if ($event->getInstanceOfEventPHID()) {
$properties->addProperty(
pht('Recurrence of Event'),
$viewer->renderHandle($event->getInstanceOfEventPHID()));
pht('%s of %s',
$event->getSequenceIndex(),
$viewer->renderHandle($event->getInstanceOfEventPHID())->render()));
}
}

View file

@ -58,6 +58,9 @@ final class PhabricatorCalendarEventSearchEngine
$min_range = $this->getDateFrom($saved)->getEpoch();
$max_range = $this->getDateTo($saved)->getEpoch();
$user_datasource = id(new PhabricatorPeopleUserFunctionDatasource())
->setViewer($viewer);
if ($this->isMonthView($saved) ||
$this->isDayView($saved)) {
list($start_year, $start_month, $start_day) =
@ -124,11 +127,13 @@ final class PhabricatorCalendarEventSearchEngine
}
$invited_phids = $saved->getParameter('invitedPHIDs');
$invited_phids = $user_datasource->evaluateTokens($invited_phids);
if ($invited_phids) {
$query->withInvitedPHIDs($invited_phids);
}
$creator_phids = $saved->getParameter('creatorPHIDs');
$creator_phids = $user_datasource->evaluateTokens($creator_phids);
if ($creator_phids) {
$query->withCreatorPHIDs($creator_phids);
}
@ -196,13 +201,13 @@ final class PhabricatorCalendarEventSearchEngine
$form
->appendControl(
id(new AphrontFormTokenizerControl())
->setDatasource(new PhabricatorPeopleDatasource())
->setDatasource(new PhabricatorPeopleUserFunctionDatasource())
->setName('creators')
->setLabel(pht('Created By'))
->setValue($creator_phids))
->appendControl(
id(new AphrontFormTokenizerControl())
->setDatasource(new PhabricatorPeopleDatasource())
->setDatasource(new PhabricatorPeopleUserFunctionDatasource())
->setName('invited')
->setLabel(pht('Invited'))
->setValue($invited_phids))

View file

@ -50,10 +50,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
if ($mode == 'public') {
$view_policy = PhabricatorPolicies::getMostOpenPolicy();
} else if ($mode == 'recurring') {
}
if ($mode == 'recurring') {
$is_recurring = true;
} else {
$view_policy = $actor->getPHID();
}
return id(new PhabricatorCalendarEvent())

View file

@ -27,14 +27,14 @@ final class PhabricatorAuthSetupCheck extends PhabricatorSetupCheck {
'You have not configured any authentication providers yet. You '.
'should add a provider (like username/password, LDAP, or GitHub '.
'OAuth) so users can register and log in. You can add and configure '.
'providers using the [[%s | "Auth" application]].',
'/auth/');
'providers using the Auth Application.');
$this
->newIssue('auth.noproviders')
->setShortName(pht('No Auth Providers'))
->setName(pht('No Authentication Providers Configured'))
->setMessage($message);
->setMessage($message)
->addLink('/auth/', pht('Auth Application'));
}
}
}

View file

@ -355,6 +355,7 @@ final class ConpherenceThreadQuery
$start_epoch = $epochs['start_epoch'];
$end_epoch = $epochs['end_epoch'];
$events = array();
if ($participant_phids) {
$events = id(new PhabricatorCalendarEventQuery())
->setViewer($this->getViewer())
@ -363,8 +364,6 @@ final class ConpherenceThreadQuery
->withDateRange($start_epoch, $end_epoch)
->execute();
$events = mpull($events, null, 'getPHID');
} else {
$events = null;
}
$invitees = array();

View file

@ -39,6 +39,7 @@ final class PhabricatorDivinerApplication extends PhabricatorApplication {
'find/' => 'DivinerFindController',
),
'/book/(?P<book>[^/]+)/' => 'DivinerBookController',
'/book/(?P<book>[^/]+)/edit/' => 'DivinerBookEditController',
'/book/'.
'(?P<book>[^/]+)/'.
'(?P<type>[^/]+)/'.
@ -52,6 +53,18 @@ final class PhabricatorDivinerApplication extends PhabricatorApplication {
return self::GROUP_UTILITIES;
}
protected function getCustomCapabilities() {
return array(
DivinerDefaultViewCapability::CAPABILITY => array(
'template' => DivinerBookPHIDType::TYPECONST,
),
DivinerDefaultEditCapability::CAPABILITY => array(
'default' => PhabricatorPolicies::POLICY_ADMIN,
'template' => DivinerBookPHIDType::TYPECONST,
),
);
}
public function getRemarkupRules() {
return array(
new DivinerSymbolRemarkupRule(),

View file

@ -20,7 +20,7 @@ final class DivinerArticleAtomizer extends DivinerAtomizer {
$atom->setDocblockMetaValue('title', $title);
}
// If the article has no @name, use the filename after stripping any
// If the article has no `@name`, use the filename after stripping any
// extension.
$name = idx($meta, 'name');
if (!$name) {

View file

@ -151,9 +151,9 @@ final class DivinerPHPAtomizer extends DivinerAtomizer {
if (count($docs) < count($params)) {
$atom->addWarning(
pht(
'This call takes %d parameters, but only %d are documented.',
count($params),
count($docs)));
'This call takes %s parameter(s), but only %s are documented.',
new PhutilNumber(count($params)),
new PhutilNumber(count($docs))));
}
}
@ -212,13 +212,13 @@ final class DivinerPHPAtomizer extends DivinerAtomizer {
if (preg_match('/@(return|param|task|author)/', $value, $matches)) {
$atom->addWarning(
pht(
'Atom "%s" is preceded by a comment containing "@%s", but the '.
'comment is not a documentation comment. Documentation '.
'comments must begin with "%s", followed by a newline. Did '.
'Atom "%s" is preceded by a comment containing `%s`, but '.
'the comment is not a documentation comment. Documentation '.
'comments must begin with `%s`, followed by a newline. Did '.
'you mean to use a documentation comment? (As the comment is '.
'not a documentation comment, it will be ignored.)',
$atom->getName(),
$matches[1],
'@'.$matches[1],
'/**'));
}
}
@ -248,8 +248,8 @@ final class DivinerPHPAtomizer extends DivinerAtomizer {
if ($matches[1] !== $name) {
$atom->addWarning(
pht(
'Parameter "%s" is named "%s" in the documentation. The '.
'documentation may be out of date.',
'Parameter "%s" is named "%s" in the documentation. '.
'The documentation may be out of date.',
$name,
$matches[1]));
}
@ -292,8 +292,8 @@ final class DivinerPHPAtomizer extends DivinerAtomizer {
if ($return) {
$atom->addWarning(
pht(
'Method %s has explicitly documented %s. The %s method always '.
'returns %s. Diviner documents this implicitly.',
'Method `%s` has explicitly documented `%s`. The `%s` method '.
'always returns `%s`. Diviner documents this implicitly.',
'__construct()',
'@return',
'__construct()',

View file

@ -18,6 +18,7 @@ final class DivinerAtomCache extends DivinerDiskCache {
public function delete() {
parent::delete();
$this->fileHashMap = null;
$this->atomMap = null;
$this->atoms = array();

View file

@ -26,8 +26,8 @@ abstract class DivinerDiskCache extends Phobject {
* Convert a long-form hash key like `ccbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaN` into
* a shortened directory form, like `cc/bb/aaaaaaaaN`. In conjunction with
* @{class:PhutilDirectoryKeyValueCache}, this gives us nice directories
* inside .divinercache instead of a million hash files with huge names at
* top level.
* inside `.divinercache` instead of a million hash files with huge names at
* the top level.
*/
protected function getHashKey($hash) {
return implode(

View file

@ -45,6 +45,7 @@ final class DivinerPublishCache extends DivinerDiskCache {
/* -( Index )-------------------------------------------------------------- */
public function getIndex() {
if ($this->index === null) {
$this->index = $this->getCache()->getKey('index', array());

View file

@ -0,0 +1,11 @@
<?php
final class DivinerDefaultEditCapability extends PhabricatorPolicyCapability {
const CAPABILITY = 'diviner.default.edit';
public function getCapabilityName() {
return pht('Default Edit Policy');
}
}

View file

@ -0,0 +1,15 @@
<?php
final class DivinerDefaultViewCapability extends PhabricatorPolicyCapability {
const CAPABILITY = 'diviner.default.view';
public function getCapabilityName() {
return pht('Default View Policy');
}
public function shouldAllowPublicPolicySetting() {
return true;
}
}

View file

@ -2,33 +2,24 @@
final class DivinerAtomController extends DivinerController {
private $bookName;
private $atomType;
private $atomName;
private $atomContext;
private $atomIndex;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->bookName = $data['book'];
$this->atomType = $data['type'];
$this->atomName = $data['name'];
$this->atomContext = nonempty(idx($data, 'context'), null);
$this->atomIndex = nonempty(idx($data, 'index'), null);
}
public function processRequest() {
$request = $this->getRequest();
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getUser();
$book_name = $request->getURIData('book');
$atom_type = $request->getURIData('type');
$atom_name = $request->getURIData('name');
$atom_context = nonempty($request->getURIData('context'), null);
$atom_index = nonempty($request->getURIData('index'), null);
require_celerity_resource('diviner-shared-css');
$book = id(new DivinerBookQuery())
->setViewer($viewer)
->withNames(array($this->bookName))
->withNames(array($book_name))
->executeOne();
if (!$book) {
@ -38,10 +29,10 @@ final class DivinerAtomController extends DivinerController {
$symbol = id(new DivinerAtomQuery())
->setViewer($viewer)
->withBookPHIDs(array($book->getPHID()))
->withTypes(array($this->atomType))
->withNames(array($this->atomName))
->withContexts(array($this->atomContext))
->withIndexes(array($this->atomIndex))
->withTypes(array($atom_type))
->withNames(array($atom_name))
->withContexts(array($atom_context))
->withIndexes(array($atom_index))
->withIsDocumentable(true)
->needAtoms(true)
->needExtends(true)
@ -75,7 +66,7 @@ final class DivinerAtomController extends DivinerController {
->setName(DivinerAtom::getAtomTypeNameString(
$atom ? $atom->getType() : $symbol->getType())));
$properties = id(new PHUIPropertyListView());
$properties = new PHUIPropertyListView();
$group = $atom ? $atom->getProperty('group') : $symbol->getGroupName();
if ($group) {
@ -133,9 +124,7 @@ final class DivinerAtomController extends DivinerController {
$document->appendChild(
id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
->appendChild(
pht(
'This atom no longer exists.')));
->appendChild(pht('This atom no longer exists.')));
}
if ($atom) {
@ -303,7 +292,6 @@ final class DivinerAtomController extends DivinerController {
pht('Implements'),
phutil_implode_html(phutil_tag('br'), $items));
}
}
private function renderAtomTag(DivinerLiveSymbol $symbol) {
@ -470,6 +458,7 @@ final class DivinerAtomController extends DivinerController {
private function renderFullSignature(
DivinerLiveSymbol $symbol,
$is_link = false) {
switch ($symbol->getType()) {
case DivinerAtom::TYPE_CLASS:
case DivinerAtom::TYPE_INTERFACE:

View file

@ -2,20 +2,15 @@
final class DivinerAtomListController extends DivinerController {
private $key;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->key = idx($data, 'key', 'all');
}
public function handleRequest(AphrontRequest $request) {
$query_key = $request->getURIData('key');
public function processRequest() {
$request = $this->getRequest();
$controller = id(new PhabricatorApplicationSearchController())
->setQueryKey($this->key)
->setQueryKey($query_key)
->setSearchEngine(new DivinerAtomSearchEngine())
->setNavigation($this->buildSideNavView());

View file

@ -2,41 +2,46 @@
final class DivinerBookController extends DivinerController {
private $bookName;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->bookName = $data['book'];
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$book_name = $request->getURIData('book');
$book = id(new DivinerBookQuery())
->setViewer($viewer)
->withNames(array($this->bookName))
->withNames(array($book_name))
->executeOne();
if (!$book) {
return new Aphront404Response();
}
$actions = $this->buildActionView($viewer, $book);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setBorder(true);
$crumbs->addTextCrumb(
$book->getShortTitle(),
'/book/'.$book->getName().'/');
$action_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('Actions'))
->setHref('#')
->setIconFont('fa-bars')
->addClass('phui-mobile-menu')
->setDropdownMenu($actions);
$header = id(new PHUIHeaderView())
->setHeader($book->getTitle())
->setUser($viewer)
->setPolicyObject($book)
->setEpoch($book->getDateModified());
->setEpoch($book->getDateModified())
->addActionLink($action_button);
$document = new PHUIDocumentView();
$document->setHeader($header);
@ -99,4 +104,28 @@ final class DivinerBookController extends DivinerController {
));
}
private function buildActionView(
PhabricatorUser $user,
DivinerLiveBook $book) {
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$book,
PhabricatorPolicyCapability::CAN_EDIT);
$action_view = id(new PhabricatorActionListView())
->setUser($user)
->setObject($book)
->setObjectURI($this->getRequest()->getRequestURI());
$action_view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Book'))
->setIcon('fa-pencil')
->setHref('/book/'.$book->getName().'/edit/')
->setDisabled(!$can_edit));
return $action_view;
}
}

View file

@ -0,0 +1,117 @@
<?php
final class DivinerBookEditController extends DivinerController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$book_name = $request->getURIData('book');
$book = id(new DivinerBookQuery())
->setViewer($viewer)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->needProjectPHIDs(true)
->withNames(array($book_name))
->executeOne();
if (!$book) {
return new Aphront404Response();
}
$view_uri = '/book/'.$book->getName().'/';
if ($request->isFormPost()) {
$v_projects = $request->getArr('projectPHIDs');
$v_view = $request->getStr('viewPolicy');
$v_edit = $request->getStr('editPolicy');
$xactions = array();
$xactions[] = id(new DivinerLiveBookTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue(
'edge:type',
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST)
->setNewValue(
array(
'=' => array_fuse($v_projects),
));
$xactions[] = id(new DivinerLiveBookTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
->setNewValue($v_view);
$xactions[] = id(new DivinerLiveBookTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
->setNewValue($v_edit);
id(new DivinerLiveBookEditor())
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request)
->setActor($viewer)
->applyTransactions($book, $xactions);
return id(new AphrontRedirectResponse())->setURI($view_uri);
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Edit Basics'));
$title = pht('Edit %s', $book->getTitle());
$policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->setObject($book)
->execute();
$view_capability = PhabricatorPolicyCapability::CAN_VIEW;
$edit_capability = PhabricatorPolicyCapability::CAN_EDIT;
$form = id(new AphrontFormView())
->setUser($viewer)
->appendControl(
id(new AphrontFormTokenizerControl())
->setDatasource(new PhabricatorProjectDatasource())
->setName('projectPHIDs')
->setLabel(pht('Projects'))
->setValue($book->getProjectPHIDs()))
->appendChild(
id(new AphrontFormPolicyControl())
->setName('viewPolicy')
->setPolicyObject($book)
->setCapability($view_capability)
->setPolicies($policies)
->setCaption($book->describeAutomaticCapability($view_capability)))
->appendChild(
id(new AphrontFormPolicyControl())
->setName('editPolicy')
->setPolicyObject($book)
->setCapability($edit_capability)
->setPolicies($policies)
->setCaption($book->describeAutomaticCapability($edit_capability)))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save'))
->addCancelButton($view_uri));
$object_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setForm($form);
$timeline = $this->buildTransactionTimeline(
$book,
new DivinerLiveBookTransactionQuery());
$timeline->setShouldTerminate(true);
return $this->buildApplicationPage(
array(
$crumbs,
$object_box,
$timeline,
),
array(
'title' => $title,
));
}
}

View file

@ -3,19 +3,15 @@
abstract class DivinerController extends PhabricatorController {
protected function buildSideNavView() {
$menu = $this->buildMenu();
$menu = $this->buildApplicationMenu();
return AphrontSideNavFilterView::newFromMenu($menu);
}
public function buildApplicationMenu() {
return $this->buildMenu();
}
private function buildMenu() {
$menu = new PHUIListView();
id(new DivinerAtomSearchEngine())
->setViewer($this->getRequest()->getUser())
->setViewer($this->getRequest()->getViewer())
->addNavigationItems($menu);
return $menu;
@ -24,12 +20,8 @@ abstract class DivinerController extends PhabricatorController {
protected function renderAtomList(array $symbols) {
assert_instances_of($symbols, 'DivinerLiveSymbol');
$request = $this->getRequest();
$user = $request->getUser();
$list = array();
foreach ($symbols as $symbol) {
switch ($symbol->getType()) {
case DivinerAtom::TYPE_FUNCTION:
$title = $symbol->getTitle().'()';
@ -43,8 +35,7 @@ abstract class DivinerController extends PhabricatorController {
->setTitle($title)
->setHref($symbol->getURI())
->setSubtitle($symbol->getSummary())
->setType(DivinerAtom::getAtomTypeNameString(
$symbol->getType()));
->setType(DivinerAtom::getAtomTypeNameString($symbol->getType()));
$list[] = $item;
}

View file

@ -6,11 +6,10 @@ final class DivinerFindController extends DivinerController {
return true;
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$book_name = $request->getStr('book');
$book_name = $request->getStr('book');
$query_text = $request->getStr('name');
$book = null;
@ -19,6 +18,7 @@ final class DivinerFindController extends DivinerController {
->setViewer($viewer)
->withNames(array($book_name))
->executeOne();
if (!$book) {
return new Aphront404Response();
}
@ -70,8 +70,8 @@ final class DivinerFindController extends DivinerController {
->setTitle(pht('Documentation Not Found'))
->appendChild(
pht(
'Unable to find the specified documentation. You may have '.
'followed a bad or outdated link.'))
'Unable to find the specified documentation. '.
'You may have followed a bad or outdated link.'))
->addCancelButton($not_found_uri, pht('Read More Documentation'));
return id(new AphrontDialogResponse())->setDialog($dialog);

View file

@ -6,9 +6,8 @@ final class DivinerMainController extends DivinerController {
return true;
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$books = id(new DivinerBookQuery())
->setViewer($viewer)
@ -53,24 +52,20 @@ final class DivinerMainController extends DivinerController {
->appendChild($list);
$document->appendChild($list);
} else {
$text = pht(
"(NOTE) **Looking for Phabricator documentation?** If you're looking ".
"for help and information about Phabricator, you can ".
"[[ https://secure.phabricator.com/diviner/ | browse the public ".
"Phabricator documentation ]] on the live site.\n\n".
"Diviner is the documentation generator used to build the Phabricator ".
"documentation.\n\n".
"(NOTE) **Looking for Phabricator documentation?** ".
"If you're looking for help and information about Phabricator, ".
"you can [[https://secure.phabricator.com/diviner/ | ".
"browse the public Phabricator documentation]] on the live site.\n\n".
"Diviner is the documentation generator used to build the ".
"Phabricator documentation.\n\n".
"You haven't generated any Diviner documentation books yet, so ".
"there's nothing to show here. If you'd like to generate your own ".
"local copy of the Phabricator documentation and have it appear ".
"here, run this command:\n\n".
" phabricator/ $ ./bin/diviner generate\n\n".
"Right now, Diviner isn't very useful for generating documentation ".
"for projects other than Phabricator. If you're interested in using ".
"it in your own projects, leave feedback for us on ".
"[[ https://secure.phabricator.com/T4558 | T4558 ]].");
" %s\n\n",
'phabricator/ $ ./bin/diviner generate');
$text = PhabricatorMarkupEngine::renderOneObject(
id(new PhabricatorMarkupOneOff())->setContent($text),

View file

@ -0,0 +1,23 @@
<?php
final class DivinerLiveBookEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorDivinerApplication';
}
public function getEditorObjectsDescription() {
return pht('Diviner Books');
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
return $types;
}
}

View file

@ -12,6 +12,14 @@ final class DivinerAtomPHIDType extends PhabricatorPHIDType {
return new DivinerLiveSymbol();
}
public function getTypeIcon() {
return 'fa-cube';
}
public function getPHIDTypeApplicationClass() {
return 'PhabricatorDivinerApplication';
}
protected function buildQueryForObjects(
PhabricatorObjectQuery $query,
array $phids) {

View file

@ -12,6 +12,14 @@ final class DivinerBookPHIDType extends PhabricatorPHIDType {
return new DivinerLiveBook();
}
public function getTypeIcon() {
return 'fa-book';
}
public function getPHIDTypeApplicationClass() {
return 'PhabricatorDivinerApplication';
}
protected function buildQueryForObjects(
PhabricatorObjectQuery $query,
array $phids) {

View file

@ -8,11 +8,15 @@ final class DivinerLivePublisher extends DivinerPublisher {
if (!$this->book) {
$book_name = $this->getConfig('name');
$book = id(new DivinerLiveBook())->loadOneWhere('name = %s', $book_name);
$book = id(new DivinerLiveBook())->loadOneWhere(
'name = %s',
$book_name);
if (!$book) {
$book = id(new DivinerLiveBook())
->setName($book_name)
->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy())
->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN)
->save();
}
@ -144,7 +148,6 @@ final class DivinerLivePublisher extends DivinerPublisher {
->setContent(null)
->save();
}
}
}

View file

@ -133,10 +133,20 @@ abstract class DivinerPublisher extends Phobject {
$created = array_keys($created);
}
echo pht('Deleting %d documents.', count($deleted))."\n";
$console = PhutilConsole::getConsole();
$console->writeOut(
"%s\n",
pht(
'Deleting %s document(s).',
new PhutilNumber(count($deleted))));
$this->deleteDocumentsByHash($deleted);
echo pht('Creating %d documents.', count($created))."\n";
$console->writeOut(
"%s\n",
pht(
'Creating %s document(s).',
new PhutilNumber(count($created))));
$this->createDocumentsByHash($created);
}

View file

@ -79,7 +79,6 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
return $this;
}
/**
* Include or exclude "ghosts", which are symbols which used to exist but do
* not exist currently (for example, a function which existed in an older
@ -137,6 +136,7 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
foreach ($atoms as $key => $atom) {
$book = idx($books, $atom->getBookPHID());
if (!$book) {
$this->didRejectResult($atom);
unset($atoms[$key]);
continue;
}
@ -158,12 +158,9 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
// Load all of the symbols this symbol extends, recursively. Commonly,
// this means all the ancestor classes and interfaces it extends and
// implements.
if ($this->needExtends) {
// First, load all the matching symbols by name. This does 99% of the
// work in most cases, assuming things are named at all reasonably.
$names = array();
foreach ($atoms as $atom) {
if (!$atom->getAtom()) {
@ -303,6 +300,7 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
if ($this->titles) {
$hashes = array();
foreach ($this->titles as $title) {
$slug = DivinerAtomRef::normalizeTitleString($title);
$hash = PhabricatorHash::digestForIndex($slug);
@ -318,6 +316,7 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
if ($this->contexts) {
$with_null = false;
$contexts = $this->contexts;
foreach ($contexts as $key => $value) {
if ($value === null) {
unset($contexts[$key]);
@ -373,10 +372,9 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
}
if ($this->nameContains) {
// NOTE: This CONVERT() call makes queries case-insensitive, since the
// column has binary collation. Eventually, this should move into
// NOTE: This `CONVERT()` call makes queries case-insensitive, since
// the column has binary collation. Eventually, this should move into
// fulltext.
$where[] = qsprintf(
$conn_r,
'CONVERT(name USING utf8) LIKE %~',
@ -388,7 +386,6 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
return $this->formatWhereClause($where);
}
/**
* Walk a list of atoms and collect all the node hashes of the atoms'
* children. When recursing, also walk up the tree and collect children of
@ -413,6 +410,7 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
foreach ($child_hashes as $hash) {
$hashes[$hash] = $hash;
}
if ($recurse_up) {
$hashes += $this->getAllChildHashes($symbol->getExtends(), true);
}
@ -421,7 +419,6 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
return $hashes;
}
/**
* Attach child atoms to existing atoms. In recursive mode, also attach child
* atoms to atoms that these atoms extend.
@ -452,7 +449,9 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
$symbol_children[] = $children[$hash];
}
}
$symbol->attachChildren($symbol_children);
if ($recurse_up) {
$this->attachAllChildren($symbol->getExtends(), $children, true);
}

View file

@ -6,6 +6,8 @@ final class DivinerBookQuery extends PhabricatorCursorPagedPolicyAwareQuery {
private $phids;
private $names;
private $needProjectPHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
@ -21,6 +23,11 @@ final class DivinerBookQuery extends PhabricatorCursorPagedPolicyAwareQuery {
return $this;
}
public function needProjectPHIDs($need_phids) {
$this->needProjectPHIDs = $need_phids;
return $this;
}
protected function loadPage() {
$table = new DivinerLiveBook();
$conn_r = $table->establishConnection('r');
@ -36,6 +43,30 @@ final class DivinerBookQuery extends PhabricatorCursorPagedPolicyAwareQuery {
return $table->loadAllFromArray($data);
}
protected function didFilterPage(array $books) {
assert_instances_of($books, 'DivinerLiveBook');
if ($this->needProjectPHIDs) {
$edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(mpull($books, 'getPHID'))
->withEdgeTypes(
array(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
));
$edge_query->execute();
foreach ($books as $book) {
$project_phids = $edge_query->getDestinationPHIDs(
array(
$book->getPHID(),
));
$book->attachProjectPHIDs($project_phids);
}
}
return $books;
}
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
$where = array();

View file

@ -0,0 +1,10 @@
<?php
final class DivinerLiveBookTransactionQuery
extends PhabricatorApplicationTransactionQuery {
public function getTemplateApplicationTransaction() {
return new DivinerLiveBookTransaction();
}
}

View file

@ -202,8 +202,8 @@ final class DivinerDefaultRenderer extends DivinerRenderer {
}
if ($ref->getBook() != $this->getConfig('name')) {
// If the ref is from a different book, we can't normalize it. Just return
// it as-is if it has enough information to resolve.
// If the ref is from a different book, we can't normalize it.
// Just return it as-is if it has enough information to resolve.
if ($ref->getName() && $ref->getType()) {
return $ref;
} else {
@ -260,5 +260,4 @@ final class DivinerDefaultRenderer extends DivinerRenderer {
$ref->getTitle());
}
}

View file

@ -27,7 +27,15 @@ final class DivinerAtomSearchIndexer extends PhabricatorSearchDocumentIndexer {
PhabricatorSearchRelationship::RELATIONSHIP_BOOK,
$atom->getBookPHID(),
DivinerBookPHIDType::TYPECONST,
$book->getDateCreated());
PhabricatorTime::getNow());
$doc->addRelationship(
$atom->getGraphHash()
? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED
: PhabricatorSearchRelationship::RELATIONSHIP_OPEN,
$atom->getBookPHID(),
DivinerBookPHIDType::TYPECONST,
PhabricatorTime::getNow());
return $doc;
}

View file

@ -18,6 +18,11 @@ final class DivinerBookSearchIndexer extends PhabricatorSearchDocumentIndexer {
PhabricatorSearchDocumentFieldType::FIELD_BODY,
$book->getPreface());
$this->indexTransactions(
$doc,
new DivinerLiveBookTransactionQuery(),
array($phid));
return $doc;
}

View file

@ -3,12 +3,17 @@
final class DivinerLiveBook extends DivinerDAO
implements
PhabricatorPolicyInterface,
PhabricatorDestructibleInterface {
PhabricatorProjectInterface,
PhabricatorDestructibleInterface,
PhabricatorApplicationTransactionInterface {
protected $name;
protected $viewPolicy;
protected $editPolicy;
protected $configurationData = array();
private $projectPHIDs = self::ATTACHABLE;
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
@ -63,28 +68,47 @@ final class DivinerLiveBook extends DivinerDAO
return idx($spec, 'name', $group);
}
public function attachProjectPHIDs(array $project_phids) {
$this->projectPHIDs = $project_phids;
return $this;
}
public function getProjectPHIDs() {
return $this->assertAttached($this->projectPHIDs);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::getMostOpenPolicy();
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
@ -102,4 +126,27 @@ final class DivinerLiveBook extends DivinerDAO
$this->saveTransaction();
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new DivinerLiveBookEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new DivinerLiveBookTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
}

View file

@ -0,0 +1,18 @@
<?php
final class DivinerLiveBookTransaction
extends PhabricatorApplicationTransaction {
public function getApplicationName() {
return 'diviner';
}
public function getApplicationTransactionType() {
return DivinerBookPHIDType::TYPECONST;
}
public function getApplicationTransactionCommentObject() {
return null;
}
}

View file

@ -137,10 +137,9 @@ final class DivinerLiveSymbol extends DivinerDAO
}
public function save() {
// NOTE: The identity hash is just a sanity check because the unique tuple
// on this table is way way too long to fit into a normal UNIQUE KEY. We
// don't use it directly, but its existence prevents duplicate records.
// on this table is way way too long to fit into a normal `UNIQUE KEY`.
// We don't use it directly, but its existence prevents duplicate records.
if (!$this->identityHash) {
$this->identityHash = PhabricatorHash::digestForIndex(
@ -159,14 +158,17 @@ final class DivinerLiveSymbol extends DivinerDAO
public function getTitle() {
$title = parent::getTitle();
if (!strlen($title)) {
$title = $this->getName();
}
return $title;
}
public function setTitle($value) {
$this->writeField('title', $value);
if (strlen($value)) {
$slug = DivinerAtomRef::normalizeTitleString($value);
$hash = PhabricatorHash::digestForIndex($slug);
@ -174,6 +176,7 @@ final class DivinerLiveSymbol extends DivinerDAO
} else {
$this->titleSlugHash = null;
}
return $this;
}
@ -200,16 +203,15 @@ final class DivinerLiveSymbol extends DivinerDAO
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return $this->getBook()->getCapabilities();
}
public function getPolicy($capability) {
return $this->getBook()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getBook()->hasAutomaticCapability($capability, $viewer);
}
@ -219,19 +221,17 @@ final class DivinerLiveSymbol extends DivinerDAO
}
/* -( Markup Interface )--------------------------------------------------- */
/* -( PhabricatorMarkupInterface )------------------------------------------ */
public function getMarkupFieldKey($field) {
return $this->getPHID().':'.$field.':'.$this->getGraphHash();
}
public function newMarkupEngine($field) {
return PhabricatorMarkupEngine::getEngine('diviner');
}
public function getMarkupText($field) {
if (!$this->getAtom()) {
return;
@ -240,21 +240,18 @@ final class DivinerLiveSymbol extends DivinerDAO
return $this->getAtom()->getDocblockText();
}
public function didMarkupText(
$field,
$output,
PhutilMarkupEngine $engine) {
public function didMarkupText($field, $output, PhutilMarkupEngine $engine) {
return $output;
}
public function shouldUseMarkupCache($field) {
return true;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {

View file

@ -0,0 +1,9 @@
<?php
final class DivinerSchemaSpec extends PhabricatorConfigSchemaSpec {
public function buildSchemata() {
$this->buildEdgeSchemata(new DivinerLiveBook());
}
}

View file

@ -43,23 +43,23 @@ final class DivinerBookItemView extends AphrontTagView {
$title = phutil_tag(
'span',
array(
'class' => 'diviner-book-item-title',
),
array(
'class' => 'diviner-book-item-title',
),
$this->title);
$subtitle = phutil_tag(
'span',
array(
'class' => 'diviner-book-item-subtitle',
),
array(
'class' => 'diviner-book-item-subtitle',
),
$this->subtitle);
$type = phutil_tag(
'span',
array(
'class' => 'diviner-book-item-type',
),
array(
'class' => 'diviner-book-item-type',
),
$this->type);
return array($title, $type, $subtitle);

View file

@ -36,8 +36,10 @@ final class DivinerAtomizeWorkflow extends DivinerWorkflow {
$atomizer_class = $args->getArg('atomizer');
if (!$atomizer_class) {
throw new Exception(
pht('Specify an atomizer class with %s.', '--atomizer'));
throw new PhutilArgumentUsageException(
pht(
'Specify an atomizer class with %s.',
'--atomizer'));
}
$symbols = id(new PhutilSymbolLoader())
@ -46,7 +48,7 @@ final class DivinerAtomizeWorkflow extends DivinerWorkflow {
->setAncestorClass('DivinerAtomizer')
->selectAndLoadSymbols();
if (!$symbols) {
throw new Exception(
throw new PhutilArgumentUsageException(
pht(
"Atomizer class '%s' must be a concrete subclass of %s.",
$atomizer_class,

View file

@ -50,6 +50,7 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
} else {
$cwd = getcwd();
$this->log(pht('FINDING DOCUMENTATION BOOKS'));
$books = id(new FileFinder($cwd))
->withType('f')
->withSuffix('book')
@ -92,7 +93,7 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
// amount of work we can, so that regenerating documentation after minor
// changes is quick.
//
// = ATOM CACHE =
// = Atom Cache =
//
// In the first stage, we find all the direct changes to source code since
// the last run. This stage relies on two data structures:
@ -118,7 +119,7 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
// its methods). The File Hash Map contains an exhaustive list of all atoms
// with type "file", but not child atoms of those top-level atoms.)
//
// = GRAPH CACHE =
// = Graph Cache =
//
// We now know which atoms exist, and can compare the Atom Map to some
// existing cache to figure out what has changed. However, this isn't
@ -176,8 +177,9 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
->setConcreteOnly(true)
->setAncestorClass('DivinerPublisher')
->selectAndLoadSymbols();
if (!$symbols) {
throw new Exception(
throw new PhutilArgumentUsageException(
pht(
"Publisher class '%s' must be a concrete subclass of %s.",
$publisher_class,
@ -188,22 +190,37 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
$this->publishDocumentation($args->getArg('clean'), $publisher);
}
/* -( Atom Cache )--------------------------------------------------------- */
private function buildAtomCache() {
$this->log(pht('BUILDING ATOM CACHE'));
$file_hashes = $this->findFilesInProject();
$this->log(pht('Found %d file(s) in project.', count($file_hashes)));
$this->log(
pht(
'Found %s file(s) in project.',
new PhutilNumber(count($file_hashes))));
$this->deleteDeadAtoms($file_hashes);
$atomize = $this->getFilesToAtomize($file_hashes);
$this->log(pht('Found %d unatomized, uncached file(s).', count($atomize)));
$this->log(
pht(
'Found %s unatomized, uncached file(s).',
new PhutilNumber(count($atomize))));
$file_atomizers = $this->getAtomizersForFiles($atomize);
$this->log(pht('Found %d file(s) to atomize.', count($file_atomizers)));
$this->log(
pht(
'Found %s file(s) to atomize.',
new PhutilNumber(count($file_atomizers))));
$futures = $this->buildAtomizerFutures($file_atomizers);
$this->log(pht('Atomizing %d file(s).', count($file_atomizers)));
$this->log(
pht(
'Atomizing %s file(s).',
new PhutilNumber(count($file_atomizers))));
if ($futures) {
$this->resolveAtomizerFutures($futures, $file_hashes);
@ -344,6 +361,7 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
->setTotal(count($futures));
$futures = id(new FutureIterator($futures))
->limit(4);
foreach ($futures as $key => $future) {
try {
$atoms = $future->resolveJSON();
@ -396,6 +414,7 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
/* -( Graph Cache )-------------------------------------------------------- */
private function buildGraphCache() {
$this->log(pht('BUILDING GRAPH CACHE'));
@ -407,7 +426,10 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
$dirty_nhashes = array();
$del_atoms = array_diff_key($symbol_map, $atoms);
$this->log(pht('Found %d obsolete atom(s) in graph.', count($del_atoms)));
$this->log(
pht(
'Found %s obsolete atom(s) in graph.',
new PhutilNumber(count($del_atoms))));
foreach ($del_atoms as $nhash => $shash) {
$atom_cache->deleteSymbol($nhash);
@ -418,7 +440,10 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
}
$new_atoms = array_diff_key($atoms, $symbol_map);
$this->log(pht('Found %d new atom(s) in graph.', count($new_atoms)));
$this->log(
pht(
'Found %s new atom(s) in graph.',
new PhutilNumber(count($new_atoms))));
foreach ($new_atoms as $nhash => $ignored) {
$shash = $this->computeSymbolHash($nhash);
@ -454,7 +479,10 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
}
}
$this->log(pht('Found %d affected atoms.', count($dirty_nhashes)));
$this->log(
pht(
'Found %s affected atoms.',
new PhutilNumber(count($dirty_nhashes))));
foreach ($dirty_nhashes as $nhash => $ignored) {
$atom_cache->addGraph($nhash, $this->computeGraphHash($nhash));

View file

@ -850,8 +850,8 @@ abstract class HeraldAdapter extends Phobject {
case HeraldRuleTypeConfig::RULE_TYPE_OBJECT:
$standard = array(
self::ACTION_NOTHING => pht('Do nothing'),
self::ACTION_ADD_CC => pht('Add emails to CC'),
self::ACTION_REMOVE_CC => pht('Remove emails from CC'),
self::ACTION_ADD_CC => pht('Add Subscribers'),
self::ACTION_REMOVE_CC => pht('Remove Subscribers'),
self::ACTION_EMAIL => pht('Send an email to'),
self::ACTION_AUDIT => pht('Trigger an Audit by'),
self::ACTION_FLAG => pht('Mark with flag'),
@ -868,8 +868,8 @@ abstract class HeraldAdapter extends Phobject {
case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
$standard = array(
self::ACTION_NOTHING => pht('Do nothing'),
self::ACTION_ADD_CC => pht('Add me to CC'),
self::ACTION_REMOVE_CC => pht('Remove me from CC'),
self::ACTION_ADD_CC => pht('Add me as a subscriber'),
self::ACTION_REMOVE_CC => pht('Remove me as a subscriber'),
self::ACTION_EMAIL => pht('Send me an email'),
self::ACTION_AUDIT => pht('Trigger an Audit by me'),
self::ACTION_FLAG => pht('Mark with flag'),

View file

@ -3,6 +3,8 @@
final class PhabricatorMailImplementationPHPMailerAdapter
extends PhabricatorMailImplementationAdapter {
private $mailer;
/**
* @phutil-external-symbol class PHPMailer
*/

View file

@ -55,7 +55,7 @@ final class PhabricatorMailTarget extends Phobject {
return $this->viewer;
}
public function sendMail(PhabricatorMetaMTAMail $mail) {
public function willSendMail(PhabricatorMetaMTAMail $mail) {
$viewer = $this->getViewer();
$mail->addPHIDHeaders('X-Phabricator-To', $this->rawToPHIDs);
@ -92,7 +92,7 @@ final class PhabricatorMailTarget extends Phobject {
$mail->addCCs($cc);
}
return $mail->save();
return $mail;
}
private function getRecipientsSummary(

View file

@ -47,7 +47,7 @@ final class PassphraseCredentialEditController extends PassphraseController {
$is_new = true;
// Prefill username if provided.
$credential->setUsername($request->getStr('username'));
$credential->setUsername((string)$request->getStr('username'));
if (!$request->getStr('isInitialized')) {
$type->didInitializeNewCredential($viewer, $credential);
@ -151,10 +151,11 @@ final class PassphraseCredentialEditController extends PassphraseController {
$credential->openTransaction();
if (!$credential->getIsLocked()) {
$xactions[] = id(new PassphraseCredentialTransaction())
if ($type->shouldRequireUsername()) {
$xactions[] = id(new PassphraseCredentialTransaction())
->setTransactionType($type_username)
->setNewValue($v_username);
}
// If some value other than a sequence of bullets was provided for
// the credential, update it. In particular, note that we are
// explicitly allowing empty secrets: one use case is HTTP auth where
@ -263,15 +264,18 @@ final class PassphraseCredentialEditController extends PassphraseController {
pht('This credential is permanently locked and can not be edited.'));
}
$form
if ($type->shouldRequireUsername()) {
$form
->appendChild(
id(new AphrontFormTextControl())
->setName('username')
->setLabel(pht('Login/Username'))
->setValue($v_username)
->setDisabled($credential_is_locked)
->setError($e_username))
->appendChild(
->setError($e_username));
}
$form
->appendChild(
$secret_control
->setName('secret')
->setLabel($type->getSecretLabel())

View file

@ -182,9 +182,11 @@ final class PassphraseCredentialViewController extends PassphraseController {
pht('Editable By'),
$descriptions[PhabricatorPolicyCapability::CAN_EDIT]);
$properties->addProperty(
pht('Username'),
$credential->getUsername());
if ($type->shouldRequireUsername()) {
$properties->addProperty(
pht('Username'),
$credential->getUsername());
}
$used_by_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$credential->getPHID(),

View file

@ -131,4 +131,8 @@ abstract class PassphraseCredentialType extends Phobject {
return $secret;
}
public function shouldRequireUsername() {
return true;
}
}

View file

@ -0,0 +1,37 @@
<?php
final class PassphraseNoteCredentialType
extends PassphraseCredentialType {
const CREDENTIAL_TYPE = 'note';
const PROVIDES_TYPE = 'provides/note';
public function getCredentialType() {
return self::CREDENTIAL_TYPE;
}
public function getProvidesType() {
return self::PROVIDES_TYPE;
}
public function getCredentialTypeName() {
return pht('Note');
}
public function getCredentialTypeDescription() {
return pht('Store a plaintext note.');
}
public function getSecretLabel() {
return pht('Note');
}
public function newSecretControl() {
return id(new AphrontFormTextAreaControl());
}
public function shouldRequireUsername() {
return false;
}
}

View file

@ -174,6 +174,10 @@ final class PassphraseCredentialTransactionEditor
}
break;
case PassphraseCredentialTransaction::TYPE_USERNAME:
$credential_type = $object->getCredentialTypeImplementation();
if (!$credential_type->shouldRequireUsername()) {
break;
}
$missing = $this->validateIsEmptyTextField(
$object->getUsername(),
$xactions);

View file

@ -36,13 +36,14 @@ final class PhrictionMoveController extends PhrictionController {
// about it.
if (strlen($v_slug)) {
$normal_slug = PhabricatorSlug::normalize($v_slug);
if ($normal_slug !== $v_slug) {
$no_slash_slug = rtrim($normal_slug, '/');
if ($normal_slug !== $v_slug && $no_slash_slug !== $v_slug) {
return $this->newDialog()
->setTitle(pht('Adjust Path'))
->appendParagraph(
pht(
'The path you entered (%s) is not a valid wiki document '.
'path. Paths may not contain special characters.',
'path. Paths may not contain spaces or special characters.',
phutil_tag('strong', array(), $v_slug)))
->appendParagraph(
pht(

View file

@ -391,6 +391,10 @@ final class PhrictionTransactionEditor
pht("A document's content changes."),
PhrictionTransaction::MAILTAG_DELETE =>
pht('A document is deleted.'),
PhrictionTransaction::MAILTAG_SUBSCRIBERS =>
pht('A document\'s subscribers change.'),
PhrictionTransaction::MAILTAG_OTHER =>
pht('Other document activity not listed above occurs.'),
);
}

View file

@ -9,9 +9,11 @@ final class PhrictionTransaction
const TYPE_MOVE_TO = 'move-to';
const TYPE_MOVE_AWAY = 'move-away';
const MAILTAG_TITLE = 'phriction-title';
const MAILTAG_CONTENT = 'phriction-content';
const MAILTAG_DELETE = 'phriction-delete';
const MAILTAG_TITLE = 'phriction-title';
const MAILTAG_CONTENT = 'phriction-content';
const MAILTAG_DELETE = 'phriction-delete';
const MAILTAG_SUBSCRIBERS = 'phriction-subscribers';
const MAILTAG_OTHER = 'phriction-other';
public function getApplicationName() {
return 'phriction';
@ -280,7 +282,12 @@ final class PhrictionTransaction
case self::TYPE_DELETE:
$tags[] = self::MAILTAG_DELETE;
break;
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
$tags[] = self::MAILTAG_SUBSCRIBERS;
break;
default:
$tags[] = self::MAILTAG_OTHER;
break;
}
return $tags;
}

View file

@ -433,6 +433,8 @@ final class PhabricatorProjectTransactionEditor
pht('Project membership changes.'),
PhabricatorProjectTransaction::MAILTAG_WATCHERS =>
pht('Project watcher list changes.'),
PhabricatorProjectTransaction::MAILTAG_SUBSCRIBERS =>
pht('Project subscribers change.'),
PhabricatorProjectTransaction::MAILTAG_OTHER =>
pht('Other project activity not listed above occurs.'),
);

View file

@ -14,10 +14,11 @@ final class PhabricatorProjectTransaction
// NOTE: This is deprecated, members are just a normal edge now.
const TYPE_MEMBERS = 'project:members';
const MAILTAG_METADATA = 'project-metadata';
const MAILTAG_MEMBERS = 'project-members';
const MAILTAG_WATCHERS = 'project-watchers';
const MAILTAG_OTHER = 'project-other';
const MAILTAG_METADATA = 'project-metadata';
const MAILTAG_MEMBERS = 'project-members';
const MAILTAG_SUBSCRIBERS = 'project-subscribers';
const MAILTAG_WATCHERS = 'project-watchers';
const MAILTAG_OTHER = 'project-other';
public function getApplicationName() {
return 'project';
@ -369,6 +370,9 @@ final class PhabricatorProjectTransaction
case self::TYPE_COLOR:
$tags[] = self::MAILTAG_METADATA;
break;
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
$tags[] = self::MAILTAG_SUBSCRIBERS;
break;
case PhabricatorTransactions::TYPE_EDGE:
$type = $this->getMetadata('edge:type');
$type = head($type);

View file

@ -166,28 +166,22 @@ final class ReleephRequestTransactionalEditor
protected function shouldSendMail(
PhabricatorLiskDAO $object,
array $xactions) {
return true;
}
protected function sendMail(
PhabricatorLiskDAO $object,
array $xactions) {
// Avoid sending emails that only talk about commit discovery.
$types = array_unique(mpull($xactions, 'getTransactionType'));
if ($types === array(ReleephRequestTransaction::TYPE_DISCOVERY)) {
return null;
return false;
}
// Don't email people when we discover that something picks or reverts OK.
if ($types === array(ReleephRequestTransaction::TYPE_PICK_STATUS)) {
if (!mfilter($xactions, 'isBoringPickStatus', true /* negate */)) {
// If we effectively call "isInterestingPickStatus" and get nothing...
return null;
return false;
}
}
return parent::sendMail($object, $xactions);
return true;
}
protected function buildReplyHandler(PhabricatorLiskDAO $object) {

View file

@ -29,18 +29,25 @@ final class PhabricatorCommitBranchesField
'callsign' => $this->getObject()->getRepository()->getCallsign(),
);
$branches_raw = id(new ConduitCall('diffusion.branchquery', $params))
->setUser($this->getViewer())
->execute();
try {
$branches_raw = id(new ConduitCall('diffusion.branchquery', $params))
->setUser($this->getViewer())
->execute();
$branches = DiffusionRepositoryRef::loadAllFromDictionaries($branches_raw);
if (!$branches) {
return;
$branches = DiffusionRepositoryRef::loadAllFromDictionaries(
$branches_raw);
if (!$branches) {
return;
}
$branch_names = mpull($branches, 'getShortName');
sort($branch_names);
$branch_text = implode(', ', $branch_names);
} catch (Exception $ex) {
$branch_text = pht('<%s: %s>', get_class($ex), $ex->getMessage());
}
$branch_names = mpull($branches, 'getShortName');
sort($branch_names);
$body->addTextSection(pht('BRANCHES'), implode(', ', $branch_names));
$body->addTextSection(pht('BRANCHES'), $branch_text);
}
}

View file

@ -1040,10 +1040,10 @@ abstract class PhabricatorApplicationTransactionEditor
// Hook for edges or other properties that may need (re-)loading
$object = $this->willPublish($object, $xactions);
$mailed = array();
$messages = array();
if (!$this->getDisableEmail()) {
if ($this->shouldSendMail($object, $xactions)) {
$mailed = $this->sendMail($object, $xactions);
$messages = $this->buildMail($object, $xactions);
}
}
@ -1055,10 +1055,21 @@ abstract class PhabricatorApplicationTransactionEditor
}
if ($this->shouldPublishFeedStory($object, $xactions)) {
$this->publishFeedStory(
$object,
$xactions,
$mailed);
$mailed = array();
foreach ($messages as $mail) {
foreach ($mail->buildRecipientList() as $phid) {
$mailed[$phid] = true;
}
}
$this->publishFeedStory($object, $xactions, $mailed);
}
// NOTE: This actually sends the mail. We do this last to reduce the chance
// that we send some mail, hit an exception, then send the mail again when
// retrying.
foreach ($messages as $mail) {
$mail->save();
}
return $xactions;
@ -2241,7 +2252,7 @@ abstract class PhabricatorApplicationTransactionEditor
/**
* @task mail
*/
protected function sendMail(
private function buildMail(
PhabricatorLiskDAO $object,
array $xactions) {
@ -2255,8 +2266,7 @@ abstract class PhabricatorApplicationTransactionEditor
// Set this explicitly before we start swapping out the effective actor.
$this->setActingAsPHID($this->getActingAsPHID());
$mailed = array();
$messages = array();
foreach ($targets as $target) {
$original_actor = $this->getActor();
@ -2270,7 +2280,7 @@ abstract class PhabricatorApplicationTransactionEditor
// Reload handles for the new viewer.
$this->loadHandles($xactions);
$mail = $this->sendMailToTarget($object, $xactions, $target);
$mail = $this->buildMailForTarget($object, $xactions, $target);
} catch (Exception $ex) {
$caught = $ex;
}
@ -2283,16 +2293,14 @@ abstract class PhabricatorApplicationTransactionEditor
}
if ($mail) {
foreach ($mail->buildRecipientList() as $phid) {
$mailed[$phid] = true;
}
$messages[] = $mail;
}
}
return array_keys($mailed);
return $messages;
}
private function sendMailToTarget(
private function buildMailForTarget(
PhabricatorLiskDAO $object,
array $xactions,
PhabricatorMailTarget $target) {
@ -2354,7 +2362,7 @@ abstract class PhabricatorApplicationTransactionEditor
$mail->setParentMessageID($this->getParentMessageID());
}
return $target->sendMail($mail);
return $target->willSendMail($mail);
}
private function addMailProjectMetadata(

View file

@ -1124,6 +1124,52 @@ final class PhabricatorUSEnglishTranslation
'%s changed package owners, added: %4$s; removed: %6$s.',
),
'Found %s book(s).' => array(
'Found %s book.',
'Found %s books.',
),
'Found %s file(s) in project.' => array(
'Found %s file in project.',
'Found %s files in project.',
),
'Found %s unatomized, uncached file(s).' => array(
'Found %s unatomized, uncached file.',
'Found %s unatomized, uncached files.',
),
'Found %s file(s) to atomize.' => array(
'Found %s file to atomize.',
'Found %s files to atomize.',
),
'Atomizing %s file(s).' => array(
'Atomizing %s file.',
'Atomizing %s files.',
),
'Creating %s document(s).' => array(
'Creating %s document.',
'Creating %s documents.',
),
'Deleting %s document(s).' => array(
'Deleting %s document.',
'Deleting %s documents.',
),
'Found %s obsolete atom(s) in graph.' => array(
'Found %s obsolete atom in graph.',
'Found %s obsolete atoms in graph.',
),
'Found %s new atom(s) in graph.' => array(
'Found %s new atom in graph.',
'Found %s new atoms in graph.',
),
'This call takes %s parameter(s), but only %s are documented.' => array(
array(
'This call takes %s parameter, but only %s is documented.',
'This call takes %s parameter, but only %s are documented.',
),
array(
'This call takes %s parameters, but only %s is documented.',
'This call takes %s parameters, but only %s are documented.',
),
),
);
}

View file

@ -872,6 +872,15 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
* @task order
*/
public function getOrderableColumns() {
$cache = PhabricatorCaches::getRequestCache();
$class = get_class($this);
$cache_key = 'query.orderablecolumns.'.$class;
$columns = $cache->getKey($cache_key);
if ($columns !== null) {
return $columns;
}
$columns = array(
'id' => array(
'table' => $this->getPrimaryTableAlias(),
@ -909,6 +918,8 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
}
}
$cache->setKey($cache_key, $columns);
return $columns;
}

View file

@ -35,7 +35,6 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery {
private $workspace = array();
private $inFlightPHIDs = array();
private $policyFilteredPHIDs = array();
private $canUseApplication;
/**
* Should we continue or throw an exception when a query result is filtered
@ -679,21 +678,13 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery {
* execute the query.
*/
public function canViewerUseQueryApplication() {
if ($this->canUseApplication === null) {
$class = $this->getQueryApplicationClass();
if (!$class) {
$this->canUseApplication = true;
} else {
$result = id(new PhabricatorApplicationQuery())
->setViewer($this->getViewer())
->withClasses(array($class))
->execute();
$this->canUseApplication = (bool)$result;
}
$class = $this->getQueryApplicationClass();
if (!$class) {
return true;
}
return $this->canUseApplication;
$viewer = $this->getViewer();
return PhabricatorApplication::isClassInstalledForViewer($class, $viewer);
}
}