mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-28 00:10:57 +01:00
Merge branch 'master' into redesign-2015
This commit is contained in:
commit
f1b7fd483e
64 changed files with 813 additions and 242 deletions
17
resources/sql/autopatches/20150605.diviner.edges.sql
Normal file
17
resources/sql/autopatches/20150605.diviner.edges.sql
Normal 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};
|
|
@ -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 = '';
|
19
resources/sql/autopatches/20150605.diviner.xaction.sql
Normal file
19
resources/sql/autopatches/20150605.diviner.xaction.sql
Normal 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};
|
|
@ -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',
|
||||
|
|
|
@ -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'),
|
||||
);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()',
|
||||
|
|
|
@ -18,6 +18,7 @@ final class DivinerAtomCache extends DivinerDiskCache {
|
|||
|
||||
public function delete() {
|
||||
parent::delete();
|
||||
|
||||
$this->fileHashMap = null;
|
||||
$this->atomMap = null;
|
||||
$this->atoms = array();
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -45,6 +45,7 @@ final class DivinerPublishCache extends DivinerDiskCache {
|
|||
|
||||
/* -( Index )-------------------------------------------------------------- */
|
||||
|
||||
|
||||
public function getIndex() {
|
||||
if ($this->index === null) {
|
||||
$this->index = $this->getCache()->getKey('index', array());
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
final class DivinerDefaultEditCapability extends PhabricatorPolicyCapability {
|
||||
|
||||
const CAPABILITY = 'diviner.default.edit';
|
||||
|
||||
public function getCapabilityName() {
|
||||
return pht('Default Edit Policy');
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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),
|
||||
|
|
23
src/applications/diviner/editor/DivinerLiveBookEditor.php
Normal file
23
src/applications/diviner/editor/DivinerLiveBookEditor.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
final class DivinerLiveBookTransactionQuery
|
||||
extends PhabricatorApplicationTransactionQuery {
|
||||
|
||||
public function getTemplateApplicationTransaction() {
|
||||
return new DivinerLiveBookTransaction();
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,11 @@ final class DivinerBookSearchIndexer extends PhabricatorSearchDocumentIndexer {
|
|||
PhabricatorSearchDocumentFieldType::FIELD_BODY,
|
||||
$book->getPreface());
|
||||
|
||||
$this->indexTransactions(
|
||||
$doc,
|
||||
new DivinerLiveBookTransactionQuery(),
|
||||
array($phid));
|
||||
|
||||
return $doc;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
||||
|
|
9
src/applications/diviner/storage/DivinerSchemaSpec.php
Normal file
9
src/applications/diviner/storage/DivinerSchemaSpec.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
final class DivinerSchemaSpec extends PhabricatorConfigSchemaSpec {
|
||||
|
||||
public function buildSchemata() {
|
||||
$this->buildEdgeSchemata(new DivinerLiveBook());
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
final class PhabricatorMailImplementationPHPMailerAdapter
|
||||
extends PhabricatorMailImplementationAdapter {
|
||||
|
||||
private $mailer;
|
||||
|
||||
/**
|
||||
* @phutil-external-symbol class PHPMailer
|
||||
*/
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -131,4 +131,8 @@ abstract class PassphraseCredentialType extends Phobject {
|
|||
return $secret;
|
||||
}
|
||||
|
||||
public function shouldRequireUsername() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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.'),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.'),
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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.',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue