public function handleRequest(AphrontRequest $request)
 {
     $viewer = $request->getViewer();
     $id = $request->getURIData('channelID');
     $uri = clone $request->getRequestURI();
     $uri->setQueryParams(array());
     $pager = new AphrontCursorPagerView();
     $pager->setURI($uri);
     $pager->setPageSize(250);
     $query = id(new PhabricatorChatLogQuery())->setViewer($viewer)->withChannelIDs(array($id));
     $channel = id(new PhabricatorChatLogChannelQuery())->setViewer($viewer)->withIDs(array($id))->executeOne();
     if (!$channel) {
         return new Aphront404Response();
     }
     list($after, $before, $map) = $this->getPagingParameters($request, $query);
     $pager->setAfterID($after);
     $pager->setBeforeID($before);
     $logs = $query->executeWithCursorPager($pager);
     // Show chat logs oldest-first.
     $logs = array_reverse($logs);
     // Divide all the logs into blocks, where a block is the same author saying
     // several things in a row. A block ends when another user speaks, or when
     // two minutes pass without the author speaking.
     $blocks = array();
     $block = null;
     $last_author = null;
     $last_epoch = null;
     foreach ($logs as $log) {
         $this_author = $log->getAuthor();
         $this_epoch = $log->getEpoch();
         // Decide whether we should start a new block or not.
         $new_block = $this_author !== $last_author || $this_epoch - 60 * 2 > $last_epoch;
         if ($new_block) {
             if ($block) {
                 $blocks[] = $block;
             }
             $block = array('id' => $log->getID(), 'epoch' => $this_epoch, 'author' => $this_author, 'logs' => array($log));
         } else {
             $block['logs'][] = $log;
         }
         $last_author = $this_author;
         $last_epoch = $this_epoch;
     }
     if ($block) {
         $blocks[] = $block;
     }
     // Figure out CSS classes for the blocks. We alternate colors between
     // lines, and highlight the entire block which contains the target ID or
     // date, if applicable.
     foreach ($blocks as $key => $block) {
         $classes = array();
         if ($key % 2) {
             $classes[] = 'alternate';
         }
         $ids = mpull($block['logs'], 'getID', 'getID');
         if (array_intersect_key($ids, $map)) {
             $classes[] = 'highlight';
         }
         $blocks[$key]['class'] = $classes ? implode(' ', $classes) : null;
     }
     require_celerity_resource('phabricator-chatlog-css');
     $out = array();
     foreach ($blocks as $block) {
         $author = $block['author'];
         $author = id(new PhutilUTF8StringTruncator())->setMaximumGlyphs(18)->truncateString($author);
         $author = phutil_tag('td', array('class' => 'author'), $author);
         $href = $uri->alter('at', $block['id']);
         $timestamp = $block['epoch'];
         $timestamp = phabricator_datetime($timestamp, $viewer);
         $timestamp = phutil_tag('a', array('href' => $href, 'class' => 'timestamp'), $timestamp);
         $message = mpull($block['logs'], 'getMessage');
         $message = implode("\n", $message);
         $message = phutil_tag('td', array('class' => 'message'), array($timestamp, $message));
         $out[] = phutil_tag('tr', array('class' => $block['class']), array($author, $message));
     }
     $links = array();
     $first_uri = $pager->getFirstPageURI();
     if ($first_uri) {
         $links[] = phutil_tag('a', array('href' => $first_uri), "« " . pht('Newest'));
     }
     $prev_uri = $pager->getPrevPageURI();
     if ($prev_uri) {
         $links[] = phutil_tag('a', array('href' => $prev_uri), "‹ " . pht('Newer'));
     }
     $next_uri = $pager->getNextPageURI();
     if ($next_uri) {
         $links[] = phutil_tag('a', array('href' => $next_uri), pht('Older') . " ›");
     }
     $pager_bottom = phutil_tag('div', array('class' => 'phabricator-chat-log-pager-bottom'), $links);
     $crumbs = $this->buildApplicationCrumbs()->addTextCrumb($channel->getChannelName(), $uri);
     $form = id(new AphrontFormView())->setUser($viewer)->setMethod('GET')->setAction($uri)->appendChild(id(new AphrontFormTextControl())->setLabel(pht('Date'))->setName('date')->setValue($request->getStr('date')))->appendChild(id(new AphrontFormSubmitControl())->setValue(pht('Jump')));
     $table = phutil_tag('table', array('class' => 'phabricator-chat-log'), $out);
     $log = phutil_tag('div', array('class' => 'phabricator-chat-log-panel'), $table);
     $jump_link = id(new PHUIButtonView())->setTag('a')->setHref('#latest')->setText(pht('Jump to Bottom'))->setIconFont('fa-arrow-circle-down');
     $jump_target = phutil_tag('div', array('id' => 'latest'));
     $content = phutil_tag('div', array('class' => 'phabricator-chat-log-wrap'), array($log, $jump_target, $pager_bottom));
     $header = id(new PHUIHeaderView())->setHeader($channel->getChannelName())->setSubHeader($channel->getServiceName())->addActionLink($jump_link);
     $box = id(new PHUIObjectBoxView())->setHeader($header)->setCollapsed(true)->appendChild($content);
     $box->setShowHide(pht('Search Dates'), pht('Hide Dates'), $form, '#');
     return $this->buildApplicationPage(array($crumbs, $box), array('title' => pht('Channel Log')));
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     $uri = clone $request->getRequestURI();
     $uri->setQueryParams(array());
     $pager = new AphrontCursorPagerView();
     $pager->setURI($uri);
     $pager->setPageSize(250);
     $query = id(new PhabricatorChatLogQuery())->setViewer($user)->withChannels(array($this->channel));
     list($after, $before, $map) = $this->getPagingParameters($request, $query);
     $pager->setAfterID($after);
     $pager->setBeforeID($before);
     $logs = $query->executeWithCursorPager($pager);
     // Show chat logs oldest-first.
     $logs = array_reverse($logs);
     // Divide all the logs into blocks, where a block is the same author saying
     // several things in a row. A block ends when another user speaks, or when
     // two minutes pass without the author speaking.
     $blocks = array();
     $block = null;
     $last_author = null;
     $last_epoch = null;
     foreach ($logs as $log) {
         $this_author = $log->getAuthor();
         $this_epoch = $log->getEpoch();
         // Decide whether we should start a new block or not.
         $new_block = $this_author !== $last_author || $this_epoch - 60 * 2 > $last_epoch;
         if ($new_block) {
             if ($block) {
                 $blocks[] = $block;
             }
             $block = array('id' => $log->getID(), 'epoch' => $this_epoch, 'author' => $this_author, 'logs' => array($log));
         } else {
             $block['logs'][] = $log;
         }
         $last_author = $this_author;
         $last_epoch = $this_epoch;
     }
     if ($block) {
         $blocks[] = $block;
     }
     // Figure out CSS classes for the blocks. We alternate colors between
     // lines, and highlight the entire block which contains the target ID or
     // date, if applicable.
     foreach ($blocks as $key => $block) {
         $classes = array();
         if ($key % 2) {
             $classes[] = 'alternate';
         }
         $ids = mpull($block['logs'], 'getID', 'getID');
         if (array_intersect_key($ids, $map)) {
             $classes[] = 'highlight';
         }
         $blocks[$key]['class'] = $classes ? implode(' ', $classes) : null;
     }
     require_celerity_resource('phabricator-chatlog-css');
     $out = array();
     $out[] = '<table class="phabricator-chat-log">';
     foreach ($blocks as $block) {
         $author = $block['author'];
         $author = phutil_utf8_shorten($author, 18);
         $author = phutil_escape_html($author);
         $author = phutil_render_tag('td', array('class' => 'author'), $author);
         $message = mpull($block['logs'], 'getMessage');
         $message = implode("\n", $message);
         $message = phutil_escape_html($message);
         $message = phutil_render_tag('td', array('class' => 'message'), $message);
         $href = $uri->alter('at', $block['id']);
         $timestamp = $block['epoch'];
         $timestamp = phabricator_datetime($timestamp, $user);
         $timestamp = phutil_render_tag('a', array('href' => $href), $timestamp);
         $timestamp = phutil_render_tag('td', array('class' => 'timestamp'), $timestamp);
         $out[] = phutil_render_tag('tr', array('class' => $block['class']), $author . $message . $timestamp);
     }
     $out[] = '</table>';
     $form = id(new AphrontFormView())->setUser($user)->setMethod('GET')->setAction($uri)->appendChild(id(new AphrontFormTextControl())->setLabel('Date')->setName('date')->setValue($request->getStr('date')))->appendChild(id(new AphrontFormSubmitControl())->setValue('Jump'));
     return $this->buildStandardPageResponse(array('<div class="phabricator-chat-log-panel">', $form, '<br />', implode("\n", $out), $pager, '</div>'), array('title' => 'Channel Log'));
 }