Merge branch 'master' into redesign-2015
This commit is contained in:
		
							
								
								
									
										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);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user