diff --git a/scripts/ssh/ssh-auth.php b/scripts/ssh/ssh-auth.php index bcbda8b8e..2a0cc784f 100755 --- a/scripts/ssh/ssh-auth.php +++ b/scripts/ssh/ssh-auth.php @@ -1,84 +1,81 @@ #!/usr/bin/env php setViewer(PhabricatorUser::getOmnipotentUser()) ->withKeys(array($public_key)) ->withIsActive(true) ->executeOne(); if($ssh_key) { $object = $ssh_key->getObject(); if (!($object instanceof PhabricatorUser) and !($object instanceof AlmanacDevice)) { exit(1); } $bin = $root.'/bin/ssh-exec'; $key_argv = array(); if ($object instanceof PhabricatorUser) { $key_argv[] = '--phabricator-ssh-user'; $key_argv[] = $object->getUsername(); } else if ($object instanceof AlmanacDevice) { if (!$ssh_key->getIsTrusted()) { continue; } $key_argv[] = '--phabricator-ssh-device'; $key_argv[] = $object->getName(); } else { // We don't know what sort of key this is; don't permit SSH auth. continue; } $key_argv[] = '--phabricator-ssh-key'; $key_argv[] = $ssh_key->getID(); $cmd = csprintf('%s %Ls', $bin, $key_argv); $cmd = addcslashes($cmd, '"\\'); // Strip out newlines and other nonsense from the key type and key body. $type = $ssh_key->getKeyType(); $type = preg_replace('@[\x00-\x20]+@', '', $type); if (!strlen($type)) { exit(1); } $key = $ssh_key->getKeyBody(); $key = preg_replace('@[\x00-\x20]+@', '', $key); if (!strlen($key)) { exit(1); } $options = array( 'command="'.$cmd.'"', 'no-port-forwarding', 'no-X11-forwarding', 'no-agent-forwarding', 'no-pty', ); $options = implode(',', $options); echo $options.' '.$type.' '.$key."\n"; exit(0); } exit(1); diff --git a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php index 81a18d1f7..795410f98 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php @@ -1,729 +1,732 @@ getRepository(); $viewer = $this->getViewer(); $action_list = id(new PhabricatorActionListView()) ->setViewer($viewer); $action_list = id(new PhabricatorActionListView()) ->setViewer($viewer); + $action_list = id(new PhabricatorActionListView()) + ->setViewer($viewer); + $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $repository, PhabricatorPolicyCapability::CAN_EDIT); $edit_uri = $this->getEditPageURI(); $activate_uri = $repository->getPathURI('edit/activate/'); $delete_uri = $repository->getPathURI('edit/delete/'); $encoding_uri = $this->getEditPageURI('encoding'); $dangerous_uri = $repository->getPathURI('edit/dangerous/'); $enormous_uri = $repository->getPathURI('edit/enormous/'); $update_uri = $repository->getPathURI('edit/update/'); if ($repository->isTracked()) { $activate_icon = 'fa-ban'; $activate_label = pht('Deactivate Repository'); } else { $activate_icon = 'fa-check'; $activate_label = pht('Activate Repository'); } $should_dangerous = $repository->shouldAllowDangerousChanges(); if ($should_dangerous) { $dangerous_name = pht('Prevent Dangerous Changes'); $can_dangerous = $can_edit; } else { $dangerous_icon = 'fa-exclamation-triangle'; $dangerous_name = pht('Allow Dangerous Changes'); $can_dangerous = ($can_edit && $repository->canAllowDangerousChanges()); } $should_enormous = $repository->shouldAllowEnormousChanges(); if ($should_enormous) { $enormous_icon = 'fa-shield'; $enormous_name = pht('Prevent Enormous Changes'); $can_enormous = $can_edit; } else { $enormous_icon = 'fa-exclamation-triangle'; $enormous_name = pht('Allow Enormous Changes'); $can_enormous = ($can_edit && $repository->canAllowEnormousChanges()); } $action_list->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Basic Information')) ->setHref($edit_uri) ->setIcon('fa-pencil') ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $action_list->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Text Encoding')) ->setIcon('fa-text-width') ->setHref($encoding_uri) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $action_list->addAction( id(new PhabricatorActionView()) ->setName($dangerous_name) ->setHref($dangerous_uri) ->setIcon($dangerous_icon) ->setDisabled(!$can_dangerous) ->setWorkflow(true)); $action_list->addAction( id(new PhabricatorActionView()) ->setName($enormous_name) ->setHref($enormous_uri) ->setIcon($enormous_icon) ->setDisabled(!$can_enormous) ->setWorkflow(true)); $action_list->addAction( id(new PhabricatorActionView()) ->setName($activate_label) ->setHref($activate_uri) ->setIcon($activate_icon) ->setDisabled(!$can_edit) ->setWorkflow(true)); $action_list->addAction( id(new PhabricatorActionView()) ->setName(pht('Update Now')) ->setHref($update_uri) ->setIcon('fa-refresh') ->setWorkflow(true) ->setDisabled(!$can_edit)); // C4science customization //$action_list->addAction( // id(new PhabricatorActionView()) // ->setType(PhabricatorActionView::TYPE_DIVIDER)); // C4science customization //$action_list->addAction( // id(new PhabricatorActionView()) // ->setName(pht('Delete Repository')) // ->setHref($delete_uri) // ->setIcon('fa-times') // ->setColor(PhabricatorActionView::RED) // ->setDisabled(true) // ->setWorkflow(true)); return $this->newCurtainView() ->setActionList($action_list); } public function buildManagementPanelContent() { $basics = $this->buildBasics(); $basics = $this->newBox(pht('Properties'), $basics); $repository = $this->getRepository(); $is_new = $repository->isNewlyInitialized(); $info_view = null; if ($is_new) { $messages = array(); $messages[] = pht( 'This newly created repository is not active yet. Configure policies, '. 'options, and URIs. When ready, %s the repository.', phutil_tag('strong', array(), pht('Activate'))); if ($repository->isHosted()) { $messages[] = pht( 'If activated now, this repository will become a new hosted '. 'repository. To observe an existing repository instead, configure '. 'it in the %s panel.', phutil_tag('strong', array(), pht('URIs'))); } else { $messages[] = pht( 'If activated now, this repository will observe an existing remote '. 'repository and begin importing changes.'); } $info_view = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setErrors($messages); } $description = $this->buildDescription(); if ($description) { $description = $this->newBox(pht('Description'), $description); } $status = $this->buildStatus(); return array($info_view, $basics, $description, $status); } private function buildBasics() { $repository = $this->getRepository(); $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setViewer($viewer); $name = $repository->getName(); $view->addProperty(pht('Name'), $name); $type = PhabricatorRepositoryType::getNameForRepositoryType( $repository->getVersionControlSystem()); $view->addProperty(pht('Type'), $type); $callsign = $repository->getCallsign(); if (!strlen($callsign)) { $callsign = phutil_tag('em', array(), pht('No Callsign')); } $view->addProperty(pht('Callsign'), $callsign); $short_name = $repository->getRepositorySlug(); if ($short_name === null) { $short_name = phutil_tag('em', array(), pht('No Short Name')); } $view->addProperty(pht('Short Name'), $short_name); $encoding = $repository->getDetail('encoding'); if (!$encoding) { $encoding = phutil_tag('em', array(), pht('Use Default (UTF-8)')); } $view->addProperty(pht('Encoding'), $encoding); $can_dangerous = $repository->canAllowDangerousChanges(); if (!$can_dangerous) { $dangerous = phutil_tag('em', array(), pht('Not Preventable')); } else { $should_dangerous = $repository->shouldAllowDangerousChanges(); if ($should_dangerous) { $dangerous = pht('Allowed'); } else { $dangerous = pht('Not Allowed'); } } $view->addProperty(pht('Dangerous Changes'), $dangerous); $can_enormous = $repository->canAllowEnormousChanges(); if (!$can_enormous) { $enormous = phutil_tag('em', array(), pht('Not Preventable')); } else { $should_enormous = $repository->shouldAllowEnormousChanges(); if ($should_enormous) { $enormous = pht('Allowed'); } else { $enormous = pht('Not Allowed'); } } $view->addProperty(pht('Enormous Changes'), $enormous); return $view; } private function buildDescription() { $repository = $this->getRepository(); $viewer = $this->getViewer(); $description = $repository->getDetail('description'); $view = id(new PHUIPropertyListView()) ->setViewer($viewer); if (!strlen($description)) { return null; } else { $description = new PHUIRemarkupView($viewer, $description); } $view->addTextContent($description); return $view; } private function buildStatus() { $repository = $this->getRepository(); $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setViewer($viewer); $view->addProperty( pht('Update Frequency'), $this->buildRepositoryUpdateInterval($repository)); $messages = $this->loadStatusMessages($repository); $status = $this->buildRepositoryStatus($repository, $messages); $raw_error = $this->buildRepositoryRawError($repository, $messages); $view->addProperty(pht('Status'), $status); if ($raw_error) { $view->addSectionHeader(pht('Raw Error')); $view->addTextContent($raw_error); } return $this->newBox(pht('Status'), $view); } private function buildRepositoryUpdateInterval( PhabricatorRepository $repository) { $smart_wait = $repository->loadUpdateInterval(); $doc_href = PhabricatorEnv::getDoclink( 'Diffusion User Guide: Repository Updates'); return array( phutil_format_relative_time_detailed($smart_wait), " \xC2\xB7 ", phutil_tag( 'a', array( 'href' => $doc_href, 'target' => '_blank', ), pht('Learn More')), ); } private function buildRepositoryStatus( PhabricatorRepository $repository, array $messages) { $viewer = $this->getViewer(); $is_cluster = $repository->getAlmanacServicePHID(); $view = new PHUIStatusListView(); if ($repository->isTracked()) { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') ->setTarget(pht('Repository Active'))); } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_WARNING, 'bluegrey') ->setTarget(pht('Repository Inactive')) ->setNote( pht('Activate this repository to begin or resume import.'))); return $view; } $binaries = array(); $svnlook_check = false; switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $binaries[] = 'git'; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $binaries[] = 'svn'; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $binaries[] = 'hg'; break; } if ($repository->isHosted()) { $proto_https = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTPS; $proto_http = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTP; $can_http = $repository->canServeProtocol($proto_http, false) || $repository->canServeProtocol($proto_https, false); if ($can_http) { switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $binaries[] = 'git-http-backend'; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $binaries[] = 'svnserve'; $binaries[] = 'svnadmin'; $binaries[] = 'svnlook'; $svnlook_check = true; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $binaries[] = 'hg'; break; } } $proto_ssh = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH; $can_ssh = $repository->canServeProtocol($proto_ssh, false); if ($can_ssh) { switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $binaries[] = 'git-receive-pack'; $binaries[] = 'git-upload-pack'; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $binaries[] = 'svnserve'; $binaries[] = 'svnadmin'; $binaries[] = 'svnlook'; $svnlook_check = true; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $binaries[] = 'hg'; break; } } } $binaries = array_unique($binaries); if (!$is_cluster) { // We're only checking for binaries if we aren't running with a cluster // configuration. In theory, we could check for binaries on the // repository host machine, but we'd need to make this more complicated // to do that. foreach ($binaries as $binary) { $where = Filesystem::resolveBinary($binary); if (!$where) { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') ->setTarget( pht('Missing Binary %s', phutil_tag('tt', array(), $binary))) ->setNote(pht( "Unable to find this binary in the webserver's PATH. You may ". "need to configure %s.", $this->getEnvConfigLink()))); } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') ->setTarget( pht('Found Binary %s', phutil_tag('tt', array(), $binary))) ->setNote(phutil_tag('tt', array(), $where))); } } // This gets checked generically above. However, for svn commit hooks, we // need this to be in environment.append-paths because subversion strips // PATH. if ($svnlook_check) { $where = Filesystem::resolveBinary('svnlook'); if ($where) { $path = substr($where, 0, strlen($where) - strlen('svnlook')); $dirs = PhabricatorEnv::getEnvConfig('environment.append-paths'); $in_path = false; foreach ($dirs as $dir) { if (Filesystem::isDescendant($path, $dir)) { $in_path = true; break; } } if (!$in_path) { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') ->setTarget( pht('Missing Binary %s', phutil_tag('tt', array(), $binary))) ->setNote(pht( 'Unable to find this binary in `%s`. '. 'You need to configure %s and include %s.', 'environment.append-paths', $this->getEnvConfigLink(), $path))); } } } } $doc_href = PhabricatorEnv::getDoclink('Managing Daemons with phd'); $daemon_instructions = pht( 'Use %s to start daemons. See %s.', phutil_tag('tt', array(), 'bin/phd start'), phutil_tag( 'a', array( 'href' => $doc_href, ), pht('Managing Daemons with phd'))); $pull_daemon = id(new PhabricatorDaemonLogQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE) ->withDaemonClasses(array('PhabricatorRepositoryPullLocalDaemon')) ->setLimit(1) ->execute(); if ($pull_daemon) { // TODO: In a cluster environment, we need a daemon on this repository's // host, specifically, and we aren't checking for that right now. This // is a reasonable proxy for things being more-or-less correctly set up, // though. $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') ->setTarget(pht('Pull Daemon Running'))); } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') ->setTarget(pht('Pull Daemon Not Running')) ->setNote($daemon_instructions)); } $task_daemon = id(new PhabricatorDaemonLogQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE) ->withDaemonClasses(array('PhabricatorTaskmasterDaemon')) ->setLimit(1) ->execute(); if ($task_daemon) { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') ->setTarget(pht('Task Daemon Running'))); } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') ->setTarget(pht('Task Daemon Not Running')) ->setNote($daemon_instructions)); } if ($is_cluster) { // Just omit this status check for now in cluster environments. We // could make a service call and pull it from the repository host // eventually. } else if ($repository->usesLocalWorkingCopy()) { $local_parent = dirname($repository->getLocalPath()); if (Filesystem::pathExists($local_parent)) { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') ->setTarget(pht('Storage Directory OK')) ->setNote(phutil_tag('tt', array(), $local_parent))); } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') ->setTarget(pht('No Storage Directory')) ->setNote( pht( 'Storage directory %s does not exist, or is not readable by '. 'the webserver. Create this directory or make it readable.', phutil_tag('tt', array(), $local_parent)))); return $view; } $local_path = $repository->getLocalPath(); $message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_INIT); if ($message) { switch ($message->getStatusCode()) { case PhabricatorRepositoryStatusMessage::CODE_ERROR: $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') ->setTarget(pht('Initialization Error')) ->setNote($message->getParameter('message'))); return $view; case PhabricatorRepositoryStatusMessage::CODE_OKAY: if (Filesystem::pathExists($local_path)) { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') ->setTarget(pht('Working Copy OK')) ->setNote(phutil_tag('tt', array(), $local_path))); } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') ->setTarget(pht('Working Copy Error')) ->setNote( pht( 'Working copy %s has been deleted, or is not '. 'readable by the webserver. Make this directory '. 'readable. If it has been deleted, the daemons should '. 'restore it automatically.', phutil_tag('tt', array(), $local_path)))); return $view; } break; default: $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'green') ->setTarget(pht('Initializing Working Copy')) ->setNote(pht('Daemons are initializing the working copy.'))); return $view; } } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'orange') ->setTarget(pht('No Working Copy Yet')) ->setNote( pht('Waiting for daemons to build a working copy.'))); return $view; } } $message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_FETCH); if ($message) { switch ($message->getStatusCode()) { case PhabricatorRepositoryStatusMessage::CODE_ERROR: $message = $message->getParameter('message'); $suggestion = null; if (preg_match('/Permission denied \(publickey\)./', $message)) { $suggestion = pht( 'Public Key Error: This error usually indicates that the '. 'keypair you have configured does not have permission to '. 'access the repository.'); } $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') ->setTarget(pht('Update Error')) ->setNote($suggestion)); return $view; case PhabricatorRepositoryStatusMessage::CODE_OKAY: $ago = (PhabricatorTime::getNow() - $message->getEpoch()); $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') ->setTarget(pht('Updates OK')) ->setNote( pht( 'Last updated %s (%s ago).', phabricator_datetime($message->getEpoch(), $viewer), phutil_format_relative_time_detailed($ago)))); break; } } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'orange') ->setTarget(pht('Waiting For Update')) ->setNote( pht('Waiting for daemons to read updates.'))); } if ($repository->isImporting()) { $ratio = $repository->loadImportProgress(); $percentage = sprintf('%.2f%%', 100 * $ratio); $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'green') ->setTarget(pht('Importing')) ->setNote( pht('%s Complete', $percentage))); } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') ->setTarget(pht('Fully Imported'))); } if (idx($messages, PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE)) { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_UP, 'indigo') ->setTarget(pht('Prioritized')) ->setNote(pht('This repository will be updated soon!'))); } return $view; } private function buildRepositoryRawError( PhabricatorRepository $repository, array $messages) { $viewer = $this->getViewer(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $repository, PhabricatorPolicyCapability::CAN_EDIT); $raw_error = null; $message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_FETCH); if ($message) { switch ($message->getStatusCode()) { case PhabricatorRepositoryStatusMessage::CODE_ERROR: $raw_error = $message->getParameter('message'); break; } } if ($raw_error !== null) { if (!$can_edit) { $raw_message = pht( 'You must be able to edit a repository to see raw error messages '. 'because they sometimes disclose sensitive information.'); $raw_message = phutil_tag('em', array(), $raw_message); } else { $raw_message = phutil_escape_html_newlines($raw_error); } } else { $raw_message = null; } return $raw_message; } private function loadStatusMessages(PhabricatorRepository $repository) { $messages = id(new PhabricatorRepositoryStatusMessage()) ->loadAllWhere('repositoryID = %d', $repository->getID()); $messages = mpull($messages, null, 'getStatusType'); return $messages; } private function getEnvConfigLink() { $config_href = '/config/edit/environment.append-paths/'; return phutil_tag( 'a', array( 'href' => $config_href, ), 'environment.append-paths'); } } diff --git a/src/applications/feed/query/PhabricatorFeedQuery.php b/src/applications/feed/query/PhabricatorFeedQuery.php index 637dea264..51111a4bf 100644 --- a/src/applications/feed/query/PhabricatorFeedQuery.php +++ b/src/applications/feed/query/PhabricatorFeedQuery.php @@ -1,172 +1,177 @@ filterPHIDs = $phids; return $this; } public function withChronologicalKeys(array $keys) { $this->chronologicalKeys = $keys; return $this; } public function withEpochInRange($range_min, $range_max) { $this->rangeMin = $range_min; $this->rangeMax = $range_max; return $this; } public function newResultObject() { return new PhabricatorFeedStoryData(); } protected function loadPage() { // NOTE: We return raw rows from this method, which is a little unusual. return $this->loadStandardPageRows($this->newResultObject()); } protected function willFilterPage(array $data) { return PhabricatorFeedStory::loadAllFromRows($data, $this->getViewer()); } protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { $joins = parent::buildJoinClauseParts($conn); // NOTE: We perform this join unconditionally (even if we have no filter // PHIDs) to omit rows which have no story references. These story data // rows are notifications or realtime alerts. $ref_table = new PhabricatorFeedStoryReference(); $joins[] = qsprintf( $conn, 'JOIN %T ref ON ref.chronologicalKey = story.chronologicalKey', $ref_table->getTableName()); return $joins; } protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); if ($this->filterPHIDs) { // c4science custo $where[] = qsprintf( $conn, 'ref.objectPHID IN (%Ls)', $this->filterPHIDs); } // C4science customization if ($this->filterOutPHIDs !== null) { $where[] = qsprintf( $conn, 'ref.objectPHID NOT IN (%Ls)', $this->filterOutPHIDs); } if ($this->chronologicalKeys !== null) { // NOTE: We can't use "%d" to format these large integers on 32-bit // systems. Historically, we formatted these into integers in an // awkward way because MySQL could sometimes (?) fail to use the proper // keys if the values were formatted as strings instead of integers. // After the "qsprintf()" update to use PhutilQueryString, we can no // longer do this in a sneaky way. However, the MySQL key issue also // no longer appears to reproduce across several systems. So: just use // strings until problems turn up? + // NOTE: We may not have 64-bit PHP, so do the shifts in MySQL instead. + // From EXPLAIN, it appears like MySQL is smart enough to compute the + // result and make use of keys to execute the query. + + if ($this->rangeMin !== null) { $where[] = qsprintf( $conn, 'ref.chronologicalKey IN (%Ls)', $this->chronologicalKeys); } // NOTE: We may not have 64-bit PHP, so do the shifts in MySQL instead. // From EXPLAIN, it appears like MySQL is smart enough to compute the // result and make use of keys to execute the query. if ($this->rangeMin !== null) { $where[] = qsprintf( $conn, 'ref.chronologicalKey >= (%d << 32)', $this->rangeMin); } if ($this->rangeMax !== null) { $where[] = qsprintf( $conn, 'ref.chronologicalKey < (%d << 32)', $this->rangeMax); } return $where; } protected function buildGroupClause(AphrontDatabaseConnection $conn) { if ($this->filterPHIDs !== null) { return qsprintf($conn, 'GROUP BY ref.chronologicalKey'); } else { return qsprintf($conn, 'GROUP BY story.chronologicalKey'); } } protected function getDefaultOrderVector() { return array('key'); } public function getBuiltinOrders() { return array( 'newest' => array( 'vector' => array('key'), 'name' => pht('Creation (Newest First)'), 'aliases' => array('created'), ), 'oldest' => array( 'vector' => array('-key'), 'name' => pht('Creation (Oldest First)'), ), ); } public function getOrderableColumns() { $table = ($this->filterPHIDs ? 'ref' : 'story'); return array( 'key' => array( 'table' => $table, 'column' => 'chronologicalKey', 'type' => 'string', 'unique' => true, ), ); } protected function getPagingValueMap($cursor, array $keys) { return array( 'key' => $cursor, ); } protected function getResultCursor($item) { if ($item instanceof PhabricatorFeedStory) { return $item->getChronologicalKey(); } return $item['chronologicalKey']; } protected function getPrimaryTableAlias() { return 'story'; } public function getQueryApplicationClass() { return 'PhabricatorFeedApplication'; } } diff --git a/webroot/rsrc/css/aphront/tooltip.css b/webroot/rsrc/css/aphront/tooltip.css index 132336aff..754e647d0 100644 --- a/webroot/rsrc/css/aphront/tooltip.css +++ b/webroot/rsrc/css/aphront/tooltip.css @@ -1,131 +1,108 @@ /** * @provides aphront-tooltip-css */ .jx-tooltip-container { position: absolute; padding: 5px; /* In Chrome, moving the cursor into the empty space next to the "caret" on the caret side of the tooltip can cause the tooltip to flicker rapidly because the cursor hits the container. To stop this, prevent cursor events on the container. See T8440. */ pointer-events: none; } .jx-tooltip-appear { animation: 0.5s tooltip-appear; /* Without this, there's a nasty pop-in effect at the end of the animation when Safari changes font smoothing. The text becomes visibly more bold after the last frame of animation. */ -webkit-font-smoothing: subpixel-antialiased; } @keyframes tooltip-appear { 0% { opacity: 0; } 100% { opacity: 1; } } .jx-tooltip-hidden { opacity: 0; } -.jx-tooltip-appear { - animation: 0.5s tooltip-appear; - - /* Without this, there's a nasty pop-in effect at the end of the animation - when Safari changes font smoothing. The text becomes visibly more bold - after the last frame of animation. */ - -webkit-font-smoothing: subpixel-antialiased; -} - -@keyframes tooltip-appear { - 0% { - opacity: 0; - } - - 100% { - opacity: 1; - } -} - -.jx-tooltip-hidden { - opacity: 0; -} - .jx-tooltip-inner { position: relative; background: #000; border-radius: 3px; } .jx-tooltip { color: #fff; font-size: {$normalfontsize}; -webkit-font-smoothing: antialiased; font-weight: bold; padding: 6px 8px; overflow: hidden; white-space: pre-wrap; } .jx-tooltip:after { border: solid transparent; content: " "; height: 0; width: 0; position: absolute; pointer-events: none; border-color: rgba({$alphablack}, 0); border-width: 5px; } .jx-tooltip-align-E { margin-left: 5px; } .jx-tooltip-align-E .jx-tooltip:after { margin-top: -5px; border-right-color: #000; right: 100%; top: 50%; } .jx-tooltip-align-E { margin-right: 5px; } .jx-tooltip-align-W .jx-tooltip:after { margin-top: -5px; border-left-color: #000; left: 100%; top: 50%; } .jx-tooltip-align-N { margin-bottom: 5px; } .jx-tooltip-align-N .jx-tooltip:after { margin-left: -5px; border-top-color: #000; top: 100%; left: 50%; } .jx-tooltip-align-N { margin-top: 5px; } .jx-tooltip-align-S .jx-tooltip:after { margin-left: -5px; border-bottom-color: #000; bottom: 100%; left: 50%; }