Page MenuHomestyx hydra

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/src/aphront/console/DarkConsoleCore.php b/src/aphront/console/DarkConsoleCore.php
index 3a886a7a80..2a60c24bd1 100644
--- a/src/aphront/console/DarkConsoleCore.php
+++ b/src/aphront/console/DarkConsoleCore.php
@@ -1,197 +1,197 @@
<?php
/**
* @group console
*/
final class DarkConsoleCore {
const PLUGIN_ERRORLOG = 'ErrorLog';
const PLUGIN_SERVICES = 'Services';
const PLUGIN_EVENT = 'Event';
const PLUGIN_XHPROF = 'XHProf';
const PLUGIN_REQUEST = 'Request';
public static function getPlugins() {
return array(
self::PLUGIN_ERRORLOG,
self::PLUGIN_REQUEST,
self::PLUGIN_SERVICES,
self::PLUGIN_EVENT,
self::PLUGIN_XHPROF,
);
}
private $plugins = array();
private $settings;
private $coredata;
public function getPlugin($plugin_name) {
return idx($this->plugins, $plugin_name);
}
public function __construct() {
foreach (self::getPlugins() as $plugin_name) {
$plugin = self::newPlugin($plugin_name);
if ($plugin->isPermanent() || !isset($disabled[$plugin_name])) {
if ($plugin->shouldStartup()) {
$plugin->didStartup();
$plugin->setConsoleCore($this);
$this->plugins[$plugin_name] = $plugin;
}
}
}
}
public static function newPlugin($plugin) {
$class = 'DarkConsole'.$plugin.'Plugin';
return newv($class, array());
}
public function getEnabledPlugins() {
return $this->plugins;
}
public function render(AphrontRequest $request) {
$user = $request->getUser();
$plugins = $this->getEnabledPlugins();
foreach ($plugins as $plugin) {
$plugin->setRequest($request);
$plugin->willShutdown();
}
foreach ($plugins as $plugin) {
$plugin->didShutdown();
}
foreach ($plugins as $plugin) {
$plugin->setData($plugin->generateData());
}
$selected = $user->getConsoleTab();
$visible = $user->getConsoleVisible();
if (!isset($plugins[$selected])) {
$selected = head_key($plugins);
}
$tabs = array();
foreach ($plugins as $key => $plugin) {
$tabs[$key] = array(
'name' => $plugin->getName(),
'panel' => $plugin->render(),
);
}
$tabs_markup = array();
$panel_markup = array();
foreach ($tabs as $key => $data) {
$is_selected = ($key == $selected);
if ($is_selected) {
$style = null;
$tabclass = 'dark-console-tab-selected';
} else {
$style = 'display: none;';
$tabclass = null;
}
- $tabs_markup[] = javelin_render_tag(
+ $tabs_markup[] = javelin_tag(
'a',
array(
'class' => "dark-console-tab {$tabclass}",
'sigil' => 'dark-console-tab',
'id' => 'dark-console-tab-'.$key,
),
(string)$data['name']);
$panel_markup[] = javelin_render_tag(
'div',
array(
'class' => 'dark-console-panel dark-console-panel-'.$key,
'style' => $style,
'sigil' => 'dark-console-panel',
),
(string)$data['panel']);
}
$console = javelin_render_tag(
'table',
array(
'class' => 'dark-console',
'sigil' => 'dark-console',
'style' => $visible ? '' : 'display: none;',
),
'<tr>'.
'<th class="dark-console-tabs">'.
implode("\n", $tabs_markup).
'</th>'.
'<td>'.implode("\n", $panel_markup).'</td>'.
'</tr>');
if (!empty($_COOKIE['phsid'])) {
$console = str_replace(
$_COOKIE['phsid'],
phutil_escape_html('<session-key>'),
$console);
}
if ($request->isAjax()) {
// for ajax this HTML gets updated on the client
$request_history = null;
} else {
$request_table_header =
'<div class="dark-console-panel-request-log-separator"></div>';
$rows = array();
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
'Sequence',
'Type',
'URI',
));
$table->setColumnClasses(
array(
'',
'',
'wide',
));
$request_table = $request_table_header . $table->render();
$request_history = javelin_render_tag(
'table',
array(
'class' => 'dark-console dark-console-request-log',
'sigil' => 'dark-console-request-log',
'style' => $visible ? '' : 'display: none;',
),
'<tr>'.
'<th class="dark-console-tabs">'.
- javelin_render_tag(
+ phutil_tag(
'a',
array(
'class' => 'dark-console-tab dark-console-tab-selected',
),
'Request Log').
'</th>'.
'<td>'.
javelin_render_tag(
'div',
array(
'class' => 'dark-console-panel dark-console-panel-RequestLog',
),
$request_table).
'</td>'.
'</tr>');
}
return "\n\n\n\n".$console.$request_history."\n\n\n\n";
}
}
diff --git a/src/applications/calendar/view/AphrontCalendarMonthView.php b/src/applications/calendar/view/AphrontCalendarMonthView.php
index 5d175e7594..3367c01d88 100644
--- a/src/applications/calendar/view/AphrontCalendarMonthView.php
+++ b/src/applications/calendar/view/AphrontCalendarMonthView.php
@@ -1,315 +1,315 @@
<?php
final class AphrontCalendarMonthView extends AphrontView {
private $month;
private $year;
private $holidays = array();
private $events = array();
private $browseURI;
public function setBrowseURI($browse_uri) {
$this->browseURI = $browse_uri;
return $this;
}
private function getBrowseURI() {
return $this->browseURI;
}
public function addEvent(AphrontCalendarEventView $event) {
$this->events[] = $event;
return $this;
}
public function setHolidays(array $holidays) {
assert_instances_of($holidays, 'PhabricatorCalendarHoliday');
$this->holidays = mpull($holidays, null, 'getDay');
return $this;
}
public function __construct($month, $year) {
$this->month = $month;
$this->year = $year;
}
public function render() {
if (empty($this->user)) {
throw new Exception("Call setUser() before render()!");
}
$events = msort($this->events, 'getEpochStart');
$days = $this->getDatesInMonth();
require_celerity_resource('aphront-calendar-view-css');
$first = reset($days);
$empty = $first->format('w');
$markup = array();
$empty_box =
'<div class="aphront-calendar-day aphront-calendar-empty">'.
'</div>';
for ($ii = 0; $ii < $empty; $ii++) {
$markup[] = $empty_box;
}
$show_events = array();
foreach ($days as $day) {
$day_number = $day->format('j');
$holiday = idx($this->holidays, $day->format('Y-m-d'));
$class = 'aphront-calendar-day';
$weekday = $day->format('w');
if ($holiday || $weekday == 0 || $weekday == 6) {
$class .= ' aphront-calendar-not-work-day';
}
$day->setTime(0, 0, 0);
$epoch_start = $day->format('U');
$day->modify('+1 day');
$epoch_end = $day->format('U');
if ($weekday == 0) {
$show_events = array();
} else {
$show_events = array_fill_keys(
array_keys($show_events),
'<div class="aphront-calendar-event aphront-calendar-event-empty">'.
'&nbsp;'.
'</div>');
}
foreach ($events as $event) {
if ($event->getEpochStart() >= $epoch_end) {
// This list is sorted, so we can stop looking.
break;
}
if ($event->getEpochStart() < $epoch_end &&
$event->getEpochEnd() > $epoch_start) {
$show_events[$event->getUserPHID()] = $this->renderEvent(
$event,
$epoch_start,
$epoch_end);
}
}
$holiday_markup = null;
if ($holiday) {
$name = phutil_escape_html($holiday->getName());
$holiday_markup =
'<div class="aphront-calendar-holiday" title="'.$name.'">'.
$name.
'</div>';
}
$markup[] =
'<div class="'.$class.'">'.
'<div class="aphront-calendar-date-number">'.
$day_number.
'</div>'.
$holiday_markup.
implode("\n", $show_events).
'</div>';
}
$table = array();
$rows = array_chunk($markup, 7);
foreach ($rows as $row) {
$table[] = '<tr>';
while (count($row) < 7) {
$row[] = $empty_box;
}
foreach ($row as $cell) {
$table[] = '<td>'.$cell.'</td>';
}
$table[] = '</tr>';
}
$table =
'<table class="aphront-calendar-view">'.
$this->renderCalendarHeader($first).
'<tr class="aphront-calendar-day-of-week-header">'.
'<th>Sun</th>'.
'<th>Mon</th>'.
'<th>Tue</th>'.
'<th>Wed</th>'.
'<th>Thu</th>'.
'<th>Fri</th>'.
'<th>Sat</th>'.
'</tr>'.
implode("\n", $table).
'</table>';
return $table;
}
private function renderCalendarHeader(DateTime $date) {
$colspan = 7;
$left_th = '';
$right_th = '';
// check for a browseURI, which means we need "fancy" prev / next UI
$uri = $this->getBrowseURI();
if ($uri) {
$colspan = 5;
$uri = new PhutilURI($uri);
list($prev_year, $prev_month) = $this->getPrevYearAndMonth();
$query = array('year' => $prev_year, 'month' => $prev_month);
$prev_link = phutil_tag(
'a',
array('href' => (string) $uri->setQueryParams($query)),
"\xE2\x86\x90"
);
list($next_year, $next_month) = $this->getNextYearAndMonth();
$query = array('year' => $next_year, 'month' => $next_month);
$next_link = phutil_tag(
'a',
array('href' => (string) $uri->setQueryParams($query)),
"\xE2\x86\x92"
);
$left_th = '<th>'.$prev_link.'</th>';
$right_th = '<th>'.$next_link.'</th>';
}
return
'<tr class="aphront-calendar-month-year-header">'.
$left_th.
'<th colspan="'.$colspan.'">'.$date->format('F Y').'</th>'.
$right_th.
'</tr>';
}
private function getNextYearAndMonth() {
$month = $this->month;
$year = $this->year;
$next_year = $year;
$next_month = $month + 1;
if ($next_month == 13) {
$next_year = $year + 1;
$next_month = 1;
}
return array($next_year, $next_month);
}
private function getPrevYearAndMonth() {
$month = $this->month;
$year = $this->year;
$prev_year = $year;
$prev_month = $month - 1;
if ($prev_month == 0) {
$prev_year = $year - 1;
$prev_month = 12;
}
return array($prev_year, $prev_month);
}
/**
* Return a DateTime object representing the first moment in each day in the
* month, according to the user's locale.
*
* @return list List of DateTimes, one for each day.
*/
private function getDatesInMonth() {
$user = $this->user;
$timezone = new DateTimeZone($user->getTimezoneIdentifier());
$month = $this->month;
$year = $this->year;
// Get the year and month numbers of the following month, so we can
// determine when this month ends.
list($next_year, $next_month) = $this->getNextYearAndMonth();
$end_date = new DateTime("{$next_year}-{$next_month}-01", $timezone);
$end_epoch = $end_date->format('U');
$days = array();
for ($day = 1; $day <= 31; $day++) {
$day_date = new DateTime("{$year}-{$month}-{$day}", $timezone);
$day_epoch = $day_date->format('U');
if ($day_epoch >= $end_epoch) {
break;
} else {
$days[] = $day_date;
}
}
return $days;
}
private function renderEvent(
AphrontCalendarEventView $event,
$epoch_start,
$epoch_end) {
$user = $this->user;
$event_start = $event->getEpochStart();
$event_end = $event->getEpochEnd();
$classes = array();
$when = array();
$classes[] = 'aphront-calendar-event';
if ($event_start < $epoch_start) {
$classes[] = 'aphront-calendar-event-continues-before';
$when[] = 'Started '.phabricator_datetime($event_start, $user);
} else {
$when[] = 'Starts at '.phabricator_time($event_start, $user);
}
if ($event_end > $epoch_end) {
$classes[] = 'aphront-calendar-event-continues-after';
$when[] = 'Ends '.phabricator_datetime($event_end, $user);
} else {
$when[] = 'Ends at '.phabricator_time($event_end, $user);
}
Javelin::initBehavior('phabricator-tooltips');
$info = $event->getName();
if ($event->getDescription()) {
$info .= "\n\n".$event->getDescription();
}
if ($user->getPHID() == $event->getUserPHID()) {
$tag = 'a';
$href = '/calendar/status/edit/'.$event->getEventID().'/';
} else {
$tag = 'div';
$href = null;
}
- $text_div = javelin_render_tag(
+ $text_div = javelin_tag(
$tag,
array(
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $info."\n\n".implode("\n", $when),
'size' => 240,
),
'class' => 'aphront-calendar-event-text',
'href' => $href,
),
- phutil_escape_html(phutil_utf8_shorten($event->getName(), 32)));
+ phutil_utf8_shorten($event->getName(), 32));
- return javelin_render_tag(
+ return javelin_tag(
'div',
array(
'class' => implode(' ', $classes),
),
$text_div);
}
}
diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php
index 925843c45c..a36e1ab766 100644
--- a/src/applications/conpherence/controller/ConpherenceViewController.php
+++ b/src/applications/conpherence/controller/ConpherenceViewController.php
@@ -1,291 +1,295 @@
<?php
/**
* @group conpherence
*/
final class ConpherenceViewController extends
ConpherenceController {
private $conpherenceID;
private $conpherence;
public function setConpherence(ConpherenceThread $conpherence) {
$this->conpherence = $conpherence;
return $this;
}
public function getConpherence() {
return $this->conpherence;
}
public function setConpherenceID($conpherence_id) {
$this->conpherenceID = $conpherence_id;
return $this;
}
public function getConpherenceID() {
return $this->conpherenceID;
}
public function willProcessRequest(array $data) {
$this->setConpherenceID(idx($data, 'id'));
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$conpherence_id = $this->getConpherenceID();
if (!$conpherence_id) {
return new Aphront404Response();
}
if (!$request->isAjax()) {
return id(new AphrontRedirectResponse())
->setURI($this->getApplicationURI($conpherence_id.'/'));
}
$conpherence = id(new ConpherenceThreadQuery())
->setViewer($user)
->withIDs(array($conpherence_id))
->needWidgetData(true)
->executeOne();
$this->setConpherence($conpherence);
$participant = $conpherence->getParticipant($user->getPHID());
$write_guard = AphrontWriteGuard::beginScopedUnguardedWrites();
$participant->markUpToDate();
unset($write_guard);
$header = $this->renderHeaderPaneContent();
$messages = $this->renderMessagePaneContent();
$widgets = $this->renderWidgetPaneContent();
$content = $header + $widgets + $messages;
return id(new AphrontAjaxResponse())->setContent($content);
}
private function renderHeaderPaneContent() {
require_celerity_resource('conpherence-header-pane-css');
$user = $this->getRequest()->getUser();
$conpherence = $this->getConpherence();
$display_data = $conpherence->getDisplayData($user);
$edit_href = $this->getApplicationURI('update/'.$conpherence->getID().'/');
$header =
phutil_render_tag(
'a',
array(
'class' => 'edit',
'href' => $edit_href,
),
pht('edit...')
).
phutil_render_tag(
'div',
array(
'class' => 'header-image',
'style' => 'background-image: url('.$display_data['image'].');'
),
''
).
phutil_render_tag(
'div',
array(
'class' => 'title',
),
phutil_escape_html($display_data['title'])
).
phutil_render_tag(
'div',
array(
'class' => 'subtitle',
),
phutil_escape_html($display_data['subtitle'])
);
return array('header' => $header);
}
private function renderMessagePaneContent() {
require_celerity_resource('conpherence-message-pane-css');
$user = $this->getRequest()->getUser();
$conpherence = $this->getConpherence();
$handles = $conpherence->getHandles();
$rendered_transactions = array();
$transactions = $conpherence->getTransactions();
foreach ($transactions as $transaction) {
$rendered_transactions[] = id(new ConpherenceTransactionView())
->setUser($user)
->setConpherenceTransaction($transaction)
->setHandles($handles)
->render();
}
$transactions = implode(' ', $rendered_transactions);
$form =
id(new AphrontFormView())
->setAction($this->getApplicationURI('update/'.$conpherence->getID().'/'))
->setFlexible(true)
->setUser($user)
->addHiddenInput('action', 'message')
->appendChild(
id(new PhabricatorRemarkupControl())
->setUser($user)
->setName('text')
)
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Pontificate'))
)->render();
return array(
'messages' => $transactions,
'form' => $form
);
}
private function renderWidgetPaneContent() {
require_celerity_resource('conpherence-widget-pane-css');
Javelin::initBehavior(
'conpherence-widget-pane',
array(
'widgetRegistery' => array(
'widgets-files' => 1,
'widgets-tasks' => 1,
'widgets-calendar' => 1,
)
)
);
$conpherence = $this->getConpherence();
$widgets = phutil_render_tag(
'div',
array(
'class' => 'widgets-header'
),
- javelin_render_tag(
- 'a',
- array(
- 'sigil' => 'conpherence-change-widget',
- 'meta' => array('widget' => 'widgets-files')
+ array(
+ javelin_tag(
+ 'a',
+ array(
+ 'sigil' => 'conpherence-change-widget',
+ 'meta' => array('widget' => 'widgets-files')
+ ),
+ pht('Files')
),
- pht('Files')
- ).' | '.
- javelin_render_tag(
- 'a',
- array(
- 'sigil' => 'conpherence-change-widget',
- 'meta' => array('widget' => 'widgets-tasks')
+ ' | ',
+ javelin_tag(
+ 'a',
+ array(
+ 'sigil' => 'conpherence-change-widget',
+ 'meta' => array('widget' => 'widgets-tasks')
+ ),
+ pht('Tasks')
),
- pht('Tasks')
- ).' | '.
- javelin_render_tag(
- 'a',
- array(
- 'sigil' => 'conpherence-change-widget',
- 'meta' => array('widget' => 'widgets-calendar')
+ ' | ',
+ javelin_tag(
+ 'a',
+ array(
+ 'sigil' => 'conpherence-change-widget',
+ 'meta' => array('widget' => 'widgets-calendar')
+ ),
+ pht('Calendar')
),
- pht('Calendar')
)
).
phutil_render_tag(
'div',
array(
'class' => 'widgets-body',
'id' => 'widgets-files',
'style' => 'display: none;'
),
$this->renderFilesWidgetPaneContent()
).
phutil_render_tag(
'div',
array(
'class' => 'widgets-body',
'id' => 'widgets-tasks',
),
$this->renderTaskWidgetPaneContent()
).
phutil_render_tag(
'div',
array(
'class' => 'widgets-body',
'id' => 'widgets-calendar',
'style' => 'display: none;'
),
$this->renderCalendarWidgetPaneContent()
);
return array('widgets' => $widgets);
}
private function renderFilesWidgetPaneContent() {
$conpherence = $this->getConpherence();
$widget_data = $conpherence->getWidgetData();
$files = $widget_data['files'];
$table_data = array();
foreach ($files as $file) {
$thumb = $file->getThumb60x45URI();
$table_data[] = array(
phutil_render_tag(
'img',
array(
'src' => $thumb
),
''
),
$file->getName()
);
}
$header = id(new PhabricatorHeaderView())
->setHeader(pht('Attached Files'));
$table = id(new AphrontTableView($table_data))
->setNoDataString(pht('No files attached to conpherence.'))
->setHeaders(array('', pht('Name')))
->setColumnClasses(array('', 'wide'));
return $header->render() . $table->render();
}
private function renderTaskWidgetPaneContent() {
$conpherence = $this->getConpherence();
$widget_data = $conpherence->getWidgetData();
$tasks = $widget_data['tasks'];
$priority_map = ManiphestTaskPriority::getTaskPriorityMap();
$handles = $conpherence->getHandles();
$content = array();
foreach ($tasks as $owner_phid => $actual_tasks) {
$handle = $handles[$owner_phid];
$content[] = id(new PhabricatorHeaderView())
->setHeader($handle->getName())
->render();
$actual_tasks = msort($actual_tasks, 'getPriority');
$actual_tasks = array_reverse($actual_tasks);
$data = array();
foreach ($actual_tasks as $task) {
$data[] = array(
idx($priority_map, $task->getPriority(), pht('???')),
phutil_render_tag(
'a',
array(
'href' => '/T'.$task->getID()
),
phutil_escape_html($task->getTitle())
)
);
}
$table = id(new AphrontTableView($data))
->setNoDataString(pht('No open tasks.'))
->setHeaders(array(pht('Pri'), pht('Title')))
->setColumnClasses(array('', 'wide'));
$content[] = $table->render();
}
return implode('', $content);
}
private function renderCalendarWidgetPaneContent() {
$header = id(new PhabricatorHeaderView())
->setHeader(pht('Calendar'));
return $header->render() . 'TODO';
}
}
diff --git a/src/applications/countdown/controller/PhabricatorCountdownListController.php b/src/applications/countdown/controller/PhabricatorCountdownListController.php
index b015eb602f..93e4a916b7 100644
--- a/src/applications/countdown/controller/PhabricatorCountdownListController.php
+++ b/src/applications/countdown/controller/PhabricatorCountdownListController.php
@@ -1,95 +1,95 @@
<?php
final class PhabricatorCountdownListController
extends PhabricatorCountdownController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$pager = new AphrontPagerView();
$pager->setOffset($request->getInt('page'));
$pager->setURI($request->getRequestURI(), 'page');
$timers = id(new PhabricatorTimer())->loadAllWhere(
'1 = 1 ORDER BY id DESC LIMIT %d, %d',
$pager->getOffset(),
$pager->getPageSize() + 1);
$timers = $pager->sliceResults($timers);
$phids = mpull($timers, 'getAuthorPHID');
$handles = $this->loadViewerHandles($phids);
$rows = array();
foreach ($timers as $timer) {
$edit_button = null;
$delete_button = null;
if ($user->getIsAdmin() ||
($user->getPHID() == $timer->getAuthorPHID())) {
$edit_button = phutil_tag(
'a',
array(
'class' => 'small button grey',
'href' => '/countdown/edit/'.$timer->getID().'/'
),
'Edit');
- $delete_button = javelin_render_tag(
+ $delete_button = javelin_tag(
'a',
array(
'class' => 'small button grey',
'href' => '/countdown/delete/'.$timer->getID().'/',
'sigil' => 'workflow'
),
'Delete');
}
$rows[] = array(
phutil_escape_html($timer->getID()),
$handles[$timer->getAuthorPHID()]->renderLink(),
phutil_tag(
'a',
array(
'href' => '/countdown/'.$timer->getID().'/',
),
$timer->getTitle()),
phabricator_datetime($timer->getDatepoint(), $user),
$edit_button,
$delete_button,
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
'ID',
'Author',
'Title',
'End Date',
'',
''
));
$table->setColumnClasses(
array(
null,
null,
'wide pri',
null,
'action',
'action',
));
$panel = id(new AphrontPanelView())
->appendChild($table)
->setHeader('Timers')
->setCreateButton('Create Timer', '/countdown/edit/')
->appendChild($pager);
return $this->buildStandardPageResponse($panel,
array(
'title' => 'Countdown',
));
}
}
diff --git a/src/applications/countdown/controller/PhabricatorCountdownViewController.php b/src/applications/countdown/controller/PhabricatorCountdownViewController.php
index 494f918308..6e084a3835 100644
--- a/src/applications/countdown/controller/PhabricatorCountdownViewController.php
+++ b/src/applications/countdown/controller/PhabricatorCountdownViewController.php
@@ -1,79 +1,79 @@
<?php
final class PhabricatorCountdownViewController
extends PhabricatorCountdownController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$timer = id(new PhabricatorTimer())->load($this->id);
if (!$timer) {
return new Aphront404Response();
}
require_celerity_resource('phabricator-countdown-css');
$chrome_visible = $request->getBool('chrome', true);
$chrome_new = $chrome_visible ? false : null;
$chrome_link = phutil_tag(
'a',
array(
'href' => $request->getRequestURI()->alter('chrome', $chrome_new),
'class' => 'phabricator-timer-chrome-link',
),
$chrome_visible ? pht('Disable Chrome') : pht('Enable Chrome'));
$container = celerity_generate_unique_node_id();
$content =
'<div class="phabricator-timer" id="'.$container.'">
<h1 class="phabricator-timer-header">'.
phutil_escape_html($timer->getTitle()).' &middot; '.
phabricator_datetime($timer->getDatePoint(), $user).
'</h1>
<div class="phabricator-timer-pane">
<table class="phabricator-timer-table">
<tr>
<th>Days</th>
<th>Hours</th>
<th>Minutes</th>
<th>Seconds</th>
</tr>
<tr>'.
- javelin_render_tag('td',
+ javelin_tag('td',
array('sigil' => 'phabricator-timer-days'), '').
- javelin_render_tag('td',
+ javelin_tag('td',
array('sigil' => 'phabricator-timer-hours'), '').
- javelin_render_tag('td',
+ javelin_tag('td',
array('sigil' => 'phabricator-timer-minutes'), '').
- javelin_render_tag('td',
+ javelin_tag('td',
array('sigil' => 'phabricator-timer-seconds'), '').
'</tr>
</table>
</div>'.
$chrome_link.
'</div>';
Javelin::initBehavior('countdown-timer', array(
'timestamp' => $timer->getDatepoint(),
'container' => $container,
));
$panel = $content;
return $this->buildStandardPageResponse(
$panel,
array(
'title' => 'Countdown: '.$timer->getTitle(),
'chrome' => $chrome_visible
));
}
}
diff --git a/src/applications/differential/field/specification/DifferentialReviewersFieldSpecification.php b/src/applications/differential/field/specification/DifferentialReviewersFieldSpecification.php
index 98ec21feb3..b519c3e955 100644
--- a/src/applications/differential/field/specification/DifferentialReviewersFieldSpecification.php
+++ b/src/applications/differential/field/specification/DifferentialReviewersFieldSpecification.php
@@ -1,191 +1,191 @@
<?php
final class DifferentialReviewersFieldSpecification
extends DifferentialFieldSpecification {
private $reviewers = array();
private $error;
public function shouldAppearOnRevisionView() {
return true;
}
public function getRequiredHandlePHIDsForRevisionView() {
return $this->getReviewerPHIDs();
}
public function renderLabelForRevisionView() {
return 'Reviewers:';
}
public function renderValueForRevisionView() {
return $this->renderUserList($this->getReviewerPHIDs());
}
private function getReviewerPHIDs() {
$revision = $this->getRevision();
return $revision->getReviewers();
}
public function shouldAppearOnEdit() {
return true;
}
protected function didSetRevision() {
$this->reviewers = $this->getReviewerPHIDs();
}
public function getRequiredHandlePHIDsForRevisionEdit() {
return $this->reviewers;
}
public function setValueFromRequest(AphrontRequest $request) {
$this->reviewers = $request->getArr('reviewers');
return $this;
}
public function validateField() {
if (!$this->hasRevision()) {
return;
}
$self = PhabricatorEnv::getEnvConfig('differential.allow-self-accept');
if ($self) {
return;
}
$author_phid = $this->getRevision()->getAuthorPHID();
if (!in_array($author_phid, $this->reviewers)) {
return;
}
$this->error = 'Invalid';
throw new DifferentialFieldValidationException(
"The owner of a revision may not be a reviewer.");
}
public function renderEditControl() {
$reviewer_map = array();
foreach ($this->reviewers as $phid) {
$reviewer_map[$phid] = $this->getHandle($phid)->getFullName();
}
return id(new AphrontFormTokenizerControl())
->setLabel('Reviewers')
->setName('reviewers')
->setUser($this->getUser())
->setDatasource('/typeahead/common/users/')
->setValue($reviewer_map)
->setError($this->error);
}
public function willWriteRevision(DifferentialRevisionEditor $editor) {
$editor->setReviewers($this->reviewers);
}
public function shouldAppearOnCommitMessage() {
return true;
}
public function getCommitMessageKey() {
return 'reviewerPHIDs';
}
public function setValueFromParsedCommitMessage($value) {
$this->reviewers = nonempty($value, array());
return $this;
}
public function renderLabelForCommitMessage() {
return 'Reviewers';
}
public function getRequiredHandlePHIDsForCommitMessage() {
return $this->reviewers;
}
public function renderValueForCommitMessage($is_edit) {
if (!$this->reviewers) {
return null;
}
$names = array();
foreach ($this->reviewers as $phid) {
$names[] = $this->getHandle($phid)->getName();
}
return implode(', ', $names);
}
public function getSupportedCommitMessageLabels() {
return array(
'Reviewer',
'Reviewers',
);
}
public function parseValueFromCommitMessage($value) {
return $this->parseCommitMessageUserList($value);
}
public function shouldAppearOnRevisionList() {
return true;
}
public function renderHeaderForRevisionList() {
return 'Reviewers';
}
public function renderValueForRevisionList(DifferentialRevision $revision) {
$primary_reviewer = $revision->getPrimaryReviewer();
if ($primary_reviewer) {
$other_reviewers = array_flip($revision->getReviewers());
unset($other_reviewers[$primary_reviewer]);
if ($other_reviewers) {
$names = array();
foreach ($other_reviewers as $reviewer => $_) {
$names[] = phutil_escape_html(
$this->getHandle($reviewer)->getLinkName());
}
- $suffix = ' '.javelin_render_tag(
+ $suffix = ' '.javelin_tag(
'abbr',
array(
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => implode(', ', $names),
'align' => 'E',
),
),
'(+'.(count($names)).')');
} else {
$suffix = null;
}
return $this->getHandle($primary_reviewer)->renderLink().$suffix;
} else {
return '<em>None</em>';
}
}
public function getRequiredHandlePHIDsForRevisionList(
DifferentialRevision $revision) {
return $revision->getReviewers();
}
public function renderValueForMail($phase) {
if ($phase == DifferentialMailPhase::COMMENT) {
return null;
}
if (!$this->reviewers) {
return null;
}
$handles = id(new PhabricatorObjectHandleData($this->reviewers))
->loadHandles();
$handles = array_select_keys(
$handles,
array($this->getRevision()->getPrimaryReviewer())) + $handles;
$names = mpull($handles, 'getName');
return 'Reviewers: '.implode(', ', $names);
}
}
diff --git a/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php b/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php
index 22bdc721e3..1a60831ac9 100644
--- a/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php
@@ -1,388 +1,388 @@
<?php
abstract class DifferentialChangesetHTMLRenderer
extends DifferentialChangesetRenderer {
protected function renderChangeTypeHeader($force) {
$changeset = $this->getChangeset();
$change = $changeset->getChangeType();
$file = $changeset->getFileType();
$message = null;
if ($change == DifferentialChangeType::TYPE_CHANGE &&
$file == DifferentialChangeType::FILE_TEXT) {
if ($force) {
// We have to force something to render because there were no changes
// of other kinds.
$message = pht('This file was not modified.');
} else {
// Default case of changes to a text file, no metadata.
return null;
}
} else {
switch ($change) {
case DifferentialChangeType::TYPE_ADD:
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This file was <strong>added</strong>.');
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This image was <strong>added</strong>.');
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht('This directory was <strong>added</strong>.');
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht('This binary file was <strong>added</strong>.');
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht('This symlink was <strong>added</strong>.');
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht('This submodule was <strong>added</strong>.');
break;
}
break;
case DifferentialChangeType::TYPE_DELETE:
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This file was <strong>deleted</strong>.');
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This image was <strong>deleted</strong>.');
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht('This directory was <strong>deleted</strong>.');
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht('This binary file was <strong>deleted</strong>.');
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht('This symlink was <strong>deleted</strong>.');
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht('This submodule was <strong>deleted</strong>.');
break;
}
break;
case DifferentialChangeType::TYPE_MOVE_HERE:
$from =
"<strong>".
phutil_escape_html($changeset->getOldFile()).
"</strong>";
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This file was moved from %s.', $from);
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This image was moved from %s.', $from);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht('This directory was moved from %s.', $from);
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht('This binary file was moved from %s.', $from);
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht('This symlink was moved from %s.', $from);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht('This submodule was moved from %s.', $from);
break;
}
break;
case DifferentialChangeType::TYPE_COPY_HERE:
$from =
"<strong>".
phutil_escape_html($changeset->getOldFile()).
"</strong>";
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This file was copied from %s.', $from);
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This image was copied from %s.', $from);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht('This directory was copied from %s.', $from);
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht('This binary file was copied from %s.', $from);
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht('This symlink was copied from %s.', $from);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht('This submodule was copied from %s.', $from);
break;
}
break;
case DifferentialChangeType::TYPE_MOVE_AWAY:
$paths =
"<strong>".
phutil_escape_html(implode(', ', $changeset->getAwayPaths())).
"</strong>";
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This file was moved to %s.', $paths);
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This image was moved to %s.', $paths);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht('This directory was moved to %s.', $paths);
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht('This binary file was moved to %s.', $paths);
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht('This symlink was moved to %s.', $paths);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht('This submodule was moved to %s.', $paths);
break;
}
break;
case DifferentialChangeType::TYPE_COPY_AWAY:
$paths =
"<strong>".
phutil_escape_html(implode(', ', $changeset->getAwayPaths())).
"</strong>";
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This file was copied to %s.', $paths);
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This image was copied to %s.', $paths);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht('This directory was copied to %s.', $paths);
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht('This binary file was copied to %s.', $paths);
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht('This symlink was copied to %s.', $paths);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht('This submodule was copied to %s.', $paths);
break;
}
break;
case DifferentialChangeType::TYPE_MULTICOPY:
$paths =
"<strong>".
phutil_escape_html(implode(', ', $changeset->getAwayPaths())).
"</strong>";
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht(
'This file was deleted after being copied to %s.',
$paths);
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht(
'This image was deleted after being copied to %s.',
$paths);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht(
'This directory was deleted after being copied to %s.',
$paths);
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht(
'This binary file was deleted after being copied to %s.',
$paths);
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht(
'This symlink was deleted after being copied to %s.',
$paths);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht(
'This submodule was deleted after being copied to %s.',
$paths);
break;
}
break;
default:
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This is a file.');
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This is an image.');
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht('This is a directory.');
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht('This is a binary file.');
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht('This is a symlink.');
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht('This is a submodule.');
break;
}
break;
}
}
return
'<div class="differential-meta-notice">'.
$message.
'</div>';
}
protected function renderPropertyChangeHeader() {
$changeset = $this->getChangeset();
$old = $changeset->getOldProperties();
$new = $changeset->getNewProperties();
$keys = array_keys($old + $new);
sort($keys);
$rows = array();
foreach ($keys as $key) {
$oval = idx($old, $key);
$nval = idx($new, $key);
if ($oval !== $nval) {
if ($oval === null) {
$oval = '<em>null</em>';
} else {
$oval = nl2br(phutil_escape_html($oval));
}
if ($nval === null) {
$nval = '<em>null</em>';
} else {
$nval = nl2br(phutil_escape_html($nval));
}
$rows[] =
'<tr>'.
'<th>'.phutil_escape_html($key).'</th>'.
'<td class="oval">'.$oval.'</td>'.
'<td class="nval">'.$nval.'</td>'.
'</tr>';
}
}
return
'<table class="differential-property-table">'.
'<tr class="property-table-header">'.
'<th>'.pht('Property Changes').'</th>'.
'<td class="oval">'.pht('Old Value').'</td>'.
'<td class="nval">'.pht('New Value').'</td>'.
'</tr>'.
implode('', $rows).
'</table>';
}
public function renderShield($message, $force = 'default') {
$end = count($this->getOldLines());
$reference = $this->getRenderingReference();
if ($force !== 'text' &&
$force !== 'whitespace' &&
$force !== 'none' &&
$force !== 'default') {
throw new Exception("Invalid 'force' parameter '{$force}'!");
}
$range = "0-{$end}";
if ($force == 'text') {
// If we're forcing text, force the whole file to be rendered.
$range = "{$range}/0-{$end}";
}
$meta = array(
'ref' => $reference,
'range' => $range,
);
if ($force == 'whitespace') {
$meta['whitespace'] = DifferentialChangesetParser::WHITESPACE_SHOW_ALL;
}
$more = null;
if ($force !== 'none') {
- $more = ' '.javelin_render_tag(
+ $more = ' '.javelin_tag(
'a',
array(
'mustcapture' => true,
'sigil' => 'show-more',
'class' => 'complete',
'href' => '#',
'meta' => $meta,
),
pht('Show File Contents'));
}
return $this->wrapChangeInTable(
javelin_render_tag(
'tr',
array(
'sigil' => 'context-target',
),
'<td class="differential-shield" colspan="6">'.
phutil_escape_html($message).
$more.
'</td>'));
}
protected function wrapChangeInTable($content) {
if (!$content) {
return null;
}
return javelin_render_tag(
'table',
array(
'class' => 'differential-diff remarkup-code PhabricatorMonospaced',
'sigil' => 'differential-diff',
),
$content);
}
protected function renderInlineComment(
PhabricatorInlineCommentInterface $comment,
$on_right = false) {
return $this->buildInlineComment($comment, $on_right)->render();
}
protected function buildInlineComment(
PhabricatorInlineCommentInterface $comment,
$on_right = false) {
$user = $this->getUser();
$edit = $user &&
($comment->getAuthorPHID() == $user->getPHID()) &&
($comment->isDraft());
$allow_reply = (bool)$user;
return id(new DifferentialInlineCommentView())
->setInlineComment($comment)
->setOnRight($on_right)
->setHandles($this->getHandles())
->setMarkupEngine($this->getMarkupEngine())
->setEditable($edit)
->setAllowReply($allow_reply);
}
}
diff --git a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
index 20c2920e6e..cb22b44341 100644
--- a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
@@ -1,426 +1,426 @@
<?php
final class DifferentialChangesetTwoUpRenderer
extends DifferentialChangesetHTMLRenderer {
public function isOneUpRenderer() {
return false;
}
public function renderTextChange(
$range_start,
$range_len,
$rows) {
$hunk_starts = $this->getHunkStartLines();
$context_not_available = null;
if ($hunk_starts) {
- $context_not_available = javelin_render_tag(
+ $context_not_available = javelin_tag(
'tr',
array(
'sigil' => 'context-target',
),
phutil_tag(
'td',
array(
'colspan' => 6,
'class' => 'show-more'
),
pht('Context not available.')
)
);
}
$html = array();
$old_lines = $this->getOldLines();
$new_lines = $this->getNewLines();
$gaps = $this->getGaps();
$reference = $this->getRenderingReference();
$left_id = $this->getOldChangesetID();
$right_id = $this->getNewChangesetID();
// "N" stands for 'new' and means the comment should attach to the new file
// when stored, i.e. DifferentialInlineComment->setIsNewFile().
// "O" stands for 'old' and means the comment should attach to the old file.
$left_char = $this->getOldAttachesToNewFile()
? 'N'
: 'O';
$right_char = $this->getNewAttachesToNewFile()
? 'N'
: 'O';
$changeset = $this->getChangeset();
$copy_lines = idx($changeset->getMetadata(), 'copy:lines', array());
$highlight_old = $this->getHighlightOld();
$highlight_new = $this->getHighlightNew();
$old_render = $this->getOldRender();
$new_render = $this->getNewRender();
$original_left = $this->getOriginalOld();
$original_right = $this->getOriginalNew();
$depths = $this->getDepths();
$mask = $this->getMask();
for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) {
if (empty($mask[$ii])) {
// If we aren't going to show this line, we've just entered a gap.
// Pop information about the next gap off the $gaps stack and render
// an appropriate "Show more context" element. This branch eventually
// increments $ii by the entire size of the gap and then continues
// the loop.
$gap = array_pop($gaps);
$top = $gap[0];
$len = $gap[1];
$end = $top + $len - 20;
$contents = array();
if ($len > 40) {
$is_first_block = false;
if ($ii == 0) {
$is_first_block = true;
}
- $contents[] = javelin_render_tag(
+ $contents[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'show-more',
'meta' => array(
'ref' => $reference,
'range' => "{$top}-{$len}/{$top}-20",
),
),
$is_first_block
? pht("Show First 20 Lines")
: pht("\xE2\x96\xB2 Show 20 Lines"));
}
- $contents[] = javelin_render_tag(
+ $contents[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'show-more',
'meta' => array(
'type' => 'all',
'ref' => $reference,
'range' => "{$top}-{$len}/{$top}-{$len}",
),
),
pht('Show All %d Lines', $len));
$is_last_block = false;
if ($ii + $len >= $rows) {
$is_last_block = true;
}
if ($len > 40) {
- $contents[] = javelin_render_tag(
+ $contents[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'show-more',
'meta' => array(
'ref' => $reference,
'range' => "{$top}-{$len}/{$end}-20",
),
),
$is_last_block
? pht("Show Last 20 Lines")
: pht("\xE2\x96\xBC Show 20 Lines"));
}
$context = null;
$context_line = null;
if (!$is_last_block && $depths[$ii + $len]) {
for ($l = $ii + $len - 1; $l >= $ii; $l--) {
$line = $new_lines[$l]['text'];
if ($depths[$l] < $depths[$ii + $len] && trim($line) != '') {
$context = $new_render[$l];
$context_line = $new_lines[$l]['line'];
break;
}
}
}
$container = javelin_render_tag(
'tr',
array(
'sigil' => 'context-target',
),
'<td colspan="2" class="show-more">'.
implode(' &bull; ', $contents).
'</td>'.
'<th class="show-context-line">'.$context_line.'</td>'.
'<td colspan="3" class="show-context">'.$context.'</td>');
$html[] = $container;
$ii += ($len - 1);
continue;
}
$o_num = null;
$o_classes = 'left';
$o_text = null;
if (isset($old_lines[$ii])) {
$o_num = $old_lines[$ii]['line'];
$o_text = isset($old_render[$ii]) ? $old_render[$ii] : null;
if ($old_lines[$ii]['type']) {
if ($old_lines[$ii]['type'] == '\\') {
$o_text = $old_lines[$ii]['text'];
$o_classes .= ' comment';
} else if ($original_left && !isset($highlight_old[$o_num])) {
$o_classes .= ' old-rebase';
} else if (empty($new_lines[$ii])) {
$o_classes .= ' old old-full';
} else {
$o_classes .= ' old';
}
}
}
$n_copy = '<td class="copy" />';
$n_cov = null;
$n_colspan = 2;
$n_classes = '';
$n_num = null;
$n_text = null;
if (isset($new_lines[$ii])) {
$n_num = $new_lines[$ii]['line'];
$n_text = isset($new_render[$ii]) ? $new_render[$ii] : null;
$coverage = $this->getCodeCoverage();
if ($coverage !== null) {
if (empty($coverage[$n_num - 1])) {
$cov_class = 'N';
} else {
$cov_class = $coverage[$n_num - 1];
}
$cov_class = 'cov-'.$cov_class;
$n_cov = '<td class="cov '.$cov_class.'"></td>';
$n_colspan--;
}
if ($new_lines[$ii]['type']) {
if ($new_lines[$ii]['type'] == '\\') {
$n_text = $new_lines[$ii]['text'];
$n_class = 'comment';
} else if ($original_right && !isset($highlight_new[$n_num])) {
$n_class = 'new-rebase';
} else if (empty($old_lines[$ii])) {
$n_class = 'new new-full';
} else {
$n_class = 'new';
}
$n_classes = $n_class;
if ($new_lines[$ii]['type'] == '\\' || !isset($copy_lines[$n_num])) {
$n_copy = '<td class="copy '.$n_class.'"></td>';
} else {
list($orig_file, $orig_line, $orig_type) = $copy_lines[$n_num];
$title = ($orig_type == '-' ? 'Moved' : 'Copied').' from ';
if ($orig_file == '') {
$title .= "line {$orig_line}";
} else {
$title .=
basename($orig_file).
":{$orig_line} in dir ".
dirname('/'.$orig_file);
}
$class = ($orig_type == '-' ? 'new-move' : 'new-copy');
- $n_copy = javelin_render_tag(
+ $n_copy = javelin_tag(
'td',
array(
'meta' => array(
'msg' => $title,
),
'class' => 'copy '.$class,
),
'');
}
}
}
$n_classes .= ' right'.$n_colspan;
if (isset($hunk_starts[$o_num])) {
$html[] = $context_not_available;
}
if ($o_num && $left_id) {
$o_id = ' id="C'.$left_id.$left_char.'L'.$o_num.'"';
} else {
$o_id = null;
}
if ($n_num && $right_id) {
$n_id = ' id="C'.$right_id.$right_char.'L'.$n_num.'"';
} else {
$n_id = null;
}
// NOTE: The Javascript is sensitive to whitespace changes in this
// block!
$html[] =
'<tr>'.
'<th'.$o_id.'>'.$o_num.'</th>'.
'<td class="'.$o_classes.'">'.$o_text.'</td>'.
'<th'.$n_id.'>'.$n_num.'</th>'.
$n_copy.
// NOTE: This is a unicode zero-width space, which we use as a hint
// when intercepting 'copy' events to make sure sensible text ends
// up on the clipboard. See the 'phabricator-oncopy' behavior.
'<td class="'.$n_classes.'" colspan="'.$n_colspan.'">'.
"\xE2\x80\x8B".$n_text.
'</td>'.
$n_cov.
'</tr>';
if ($context_not_available && ($ii == $rows - 1)) {
$html[] = $context_not_available;
}
$old_comments = $this->getOldComments();
$new_comments = $this->getNewComments();
if ($o_num && isset($old_comments[$o_num])) {
foreach ($old_comments[$o_num] as $comment) {
$comment_html = $this->renderInlineComment($comment,
$on_right = false);
$new = '';
if ($n_num && isset($new_comments[$n_num])) {
foreach ($new_comments[$n_num] as $key => $new_comment) {
if ($comment->isCompatible($new_comment)) {
$new = $this->renderInlineComment($new_comment,
$on_right = true);
unset($new_comments[$n_num][$key]);
}
}
}
$html[] =
'<tr class="inline">'.
'<th />'.
'<td class="left">'.$comment_html.'</td>'.
'<th />'.
'<td colspan="3" class="right3">'.$new.'</td>'.
'</tr>';
}
}
if ($n_num && isset($new_comments[$n_num])) {
foreach ($new_comments[$n_num] as $comment) {
$comment_html = $this->renderInlineComment($comment,
$on_right = true);
$html[] =
'<tr class="inline">'.
'<th />'.
'<td class="left" />'.
'<th />'.
'<td colspan="3" class="right3">'.$comment_html.'</td>'.
'</tr>';
}
}
}
return $this->wrapChangeInTable(implode('', $html));
}
public function renderFileChange($old_file = null,
$new_file = null,
$id = 0,
$vs = 0) {
$old = null;
if ($old_file) {
$old = phutil_tag(
'div',
array(
'class' => 'differential-image-stage'
),
phutil_tag(
'img',
array(
'src' => $old_file->getBestURI(),
)
)
);
}
$new = null;
if ($new_file) {
$new = phutil_tag(
'div',
array(
'class' => 'differential-image-stage'
),
phutil_tag(
'img',
array(
'src' => $new_file->getBestURI(),
)
)
);
}
$html_old = array();
$html_new = array();
foreach ($this->getOldComments() as $on_line => $comment_group) {
foreach ($comment_group as $comment) {
$comment_html = $this->renderInlineComment($comment, $on_right = false);
$html_old[] =
'<tr class="inline">'.
'<th />'.
'<td class="left">'.$comment_html.'</td>'.
'<th />'.
'<td class="right3" colspan="3" />'.
'</tr>';
}
}
foreach ($this->getNewComments() as $lin_line => $comment_group) {
foreach ($comment_group as $comment) {
$comment_html = $this->renderInlineComment($comment, $on_right = true);
$html_new[] =
'<tr class="inline">'.
'<th />'.
'<td class="left" />'.
'<th />'.
'<td class="right3" colspan="3">'.$comment_html.'</td>'.
'</tr>';
}
}
if (!$old) {
$th_old = '<th></th>';
} else {
$th_old = '<th id="C'.$vs.'OL1">1</th>';
}
if (!$new) {
$th_new = '<th></th>';
} else {
$th_new = '<th id="C'.$id.'NL1">1</th>';
}
$output =
'<tr class="differential-image-diff">'.
$th_old.
'<td class="left differential-old-image">'.$old.'</td>'.
$th_new.
'<td class="right3 differential-new-image" colspan="3">'.
$new.
'</td>'.
'</tr>'.
implode('', $html_old).
implode('', $html_new);
$output = $this->wrapChangeInTable($output);
return $this->renderChangesetTable($output);
}
}
diff --git a/src/applications/differential/view/DifferentialChangesetFileTreeSideNavBuilder.php b/src/applications/differential/view/DifferentialChangesetFileTreeSideNavBuilder.php
index 1d5be8e265..6ee62cc218 100644
--- a/src/applications/differential/view/DifferentialChangesetFileTreeSideNavBuilder.php
+++ b/src/applications/differential/view/DifferentialChangesetFileTreeSideNavBuilder.php
@@ -1,131 +1,131 @@
<?php
final class DifferentialChangesetFileTreeSideNavBuilder {
private $title;
private $baseURI;
private $anchorName;
public function setAnchorName($anchor_name) {
$this->anchorName = $anchor_name;
return $this;
}
public function getAnchorName() {
return $this->anchorName;
}
public function setBaseURI(PhutilURI $base_uri) {
$this->baseURI = $base_uri;
return $this;
}
public function getBaseURI() {
return $this->baseURI;
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function getTitle() {
return $this->title;
}
public function build(array $changesets) {
assert_instances_of($changesets, 'DifferentialChangeset');
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI($this->getBaseURI());
$nav->setFlexible(true);
$anchor = $this->getAnchorName();
$tree = new PhutilFileTree();
foreach ($changesets as $changeset) {
try {
$tree->addPath($changeset->getFilename(), $changeset);
} catch (Exception $ex) {
// TODO: See T1702. When viewing the versus diff of diffs, we may
// have files with the same filename. For example, if you have a setup
// like this in SVN:
//
// a/
// README
// b/
// README
//
// ...and you run "arc diff" once from a/, and again from b/, you'll
// get two diffs with path README. However, in the versus diff view we
// will compute their absolute repository paths and detect that they
// aren't really the same file. This is correct, but causes us to
// throw when inserting them.
//
// We should probably compute the smallest unique path for each file
// and show these as "a/README" and "b/README" when diffed against
// one another. However, we get this wrong in a lot of places (the
// other TOC shows two "README" files, and we generate the same anchor
// hash for both) so I'm just stopping the bleeding until we can get
// a proper fix in place.
}
}
require_celerity_resource('phabricator-filetree-view-css');
$filetree = array();
$path = $tree;
while (($path = $path->getNextNode())) {
$data = $path->getData();
$name = $path->getName();
$style = 'padding-left: '.(2 + (3 * $path->getDepth())).'px';
$href = null;
if ($data) {
$href = '#'.$data->getAnchorName();
$title = $name;
$icon = 'phabricator-filetree-icon-file';
} else {
$name .= '/';
$title = $path->getFullPath().'/';
$icon = 'phabricator-filetree-icon-dir';
}
$icon = phutil_tag(
'span',
array(
'class' => 'phabricator-filetree-icon '.$icon,
),
'');
$name_element = phutil_tag(
'span',
array(
'class' => 'phabricator-filetree-name',
),
$name);
- $filetree[] = javelin_render_tag(
+ $filetree[] = javelin_tag(
$href ? 'a' : 'span',
array(
'href' => $href,
'style' => $style,
'title' => $title,
'class' => 'phabricator-filetree-item',
),
- $icon.$name_element);
+ array($icon, $name_element));
}
$tree->destroy();
$filetree =
'<div class="phabricator-filetree">'.
implode("\n", $filetree).
'</div>';
$nav->addLabel(pht('Changed Files'));
$nav->addCustomBlock($filetree);
$nav->setActive(true);
$nav->selectFilter(null);
return $nav;
}
}
diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php
index acea5b060d..abbca39906 100644
--- a/src/applications/differential/view/DifferentialChangesetListView.php
+++ b/src/applications/differential/view/DifferentialChangesetListView.php
@@ -1,317 +1,317 @@
<?php
final class DifferentialChangesetListView extends AphrontView {
private $changesets = array();
private $visibleChangesets = array();
private $references = array();
private $inlineURI;
private $renderURI = '/differential/changeset/';
private $whitespace;
private $standaloneURI;
private $leftRawFileURI;
private $rightRawFileURI;
private $symbolIndexes = array();
private $repository;
private $branch;
private $diff;
private $vsMap = array();
private $title;
public function setTitle($title) {
$this->title = $title;
return $this;
}
private function getTitle() {
return $this->title;
}
public function setBranch($branch) {
$this->branch = $branch;
return $this;
}
private function getBranch() {
return $this->branch;
}
public function setChangesets($changesets) {
$this->changesets = $changesets;
return $this;
}
public function setVisibleChangesets($visible_changesets) {
$this->visibleChangesets = $visible_changesets;
return $this;
}
public function setInlineCommentControllerURI($uri) {
$this->inlineURI = $uri;
return $this;
}
public function setRepository(PhabricatorRepository $repository) {
$this->repository = $repository;
return $this;
}
public function setDiff(DifferentialDiff $diff) {
$this->diff = $diff;
return $this;
}
public function setRenderingReferences(array $references) {
$this->references = $references;
return $this;
}
public function setSymbolIndexes(array $indexes) {
$this->symbolIndexes = $indexes;
return $this;
}
public function setRenderURI($render_uri) {
$this->renderURI = $render_uri;
return $this;
}
public function setWhitespace($whitespace) {
$this->whitespace = $whitespace;
return $this;
}
public function setVsMap(array $vs_map) {
$this->vsMap = $vs_map;
return $this;
}
public function getVsMap() {
return $this->vsMap;
}
public function setStandaloneURI($uri) {
$this->standaloneURI = $uri;
return $this;
}
public function setRawFileURIs($l, $r) {
$this->leftRawFileURI = $l;
$this->rightRawFileURI = $r;
return $this;
}
public function render() {
require_celerity_resource('differential-changeset-view-css');
$changesets = $this->changesets;
Javelin::initBehavior('differential-toggle-files', array());
$output = array();
$mapping = array();
foreach ($changesets as $key => $changeset) {
$file = $changeset->getFilename();
$class = 'differential-changeset';
if (!$this->inlineURI) {
$class .= ' differential-changeset-noneditable';
}
$ref = $this->references[$key];
$detail = new DifferentialChangesetDetailView();
$view_options = $this->renderViewOptionsDropdown(
$detail,
$ref,
$changeset);
$prefs = $this->user->loadPreferences();
$pref_symbols = $prefs->getPreference(
PhabricatorUserPreferences::PREFERENCE_DIFFUSION_SYMBOLS);
$detail->setChangeset($changeset);
$detail->addButton($view_options);
if ($pref_symbols != 'disabled') {
$detail->setSymbolIndex(idx($this->symbolIndexes, $key));
}
$detail->setVsChangesetID(idx($this->vsMap, $changeset->getID()));
$detail->setEditable(true);
$uniq_id = 'diff-'.$changeset->getAnchorName();
if (isset($this->visibleChangesets[$key])) {
$load = 'Loading...';
$mapping[$uniq_id] = $ref;
} else {
- $load = javelin_render_tag(
+ $load = javelin_tag(
'a',
array(
'href' => '#'.$uniq_id,
'meta' => array(
'id' => $uniq_id,
'ref' => $ref,
'kill' => true,
),
'sigil' => 'differential-load',
'mustcapture' => true,
),
- 'Load');
+ pht('Load'));
}
$detail->appendChild(
phutil_tag(
'div',
array(
'id' => $uniq_id,
),
phutil_tag('div', array('class' => 'differential-loading'), $load)));
$output[] = $detail->render();
}
require_celerity_resource('aphront-tooltip-css');
Javelin::initBehavior('differential-populate', array(
'registry' => $mapping,
'whitespace' => $this->whitespace,
'uri' => $this->renderURI,
));
Javelin::initBehavior('differential-show-more', array(
'uri' => $this->renderURI,
'whitespace' => $this->whitespace,
));
Javelin::initBehavior('differential-comment-jump', array());
if ($this->inlineURI) {
$undo_templates = $this->renderUndoTemplates();
Javelin::initBehavior('differential-edit-inline-comments', array(
'uri' => $this->inlineURI,
'undo_templates' => $undo_templates,
'stage' => 'differential-review-stage',
));
}
return
id(new PhabricatorHeaderView())
->setHeader($this->getTitle())
->render().
phutil_render_tag(
'div',
array(
'class' => 'differential-review-stage',
'id' => 'differential-review-stage',
),
implode("\n", $output));
}
/**
* Render the "Undo" markup for the inline comment undo feature.
*/
private function renderUndoTemplates() {
- $link = javelin_render_tag(
+ $link = javelin_tag(
'a',
array(
'href' => '#',
'sigil' => 'differential-inline-comment-undo',
),
- 'Undo');
+ pht('Undo'));
$div = phutil_tag(
'div',
array(
'class' => 'differential-inline-undo',
),
array('Changes discarded. ', $link));
$template =
'<table><tr>'.
'<th></th><td>%s</td>'.
'<th></th><td colspan="2">%s</td>'.
'</tr></table>';
return array(
'l' => sprintf($template, $div, ''),
'r' => sprintf($template, '', $div),
);
}
private function renderViewOptionsDropdown(
DifferentialChangesetDetailView $detail,
$ref,
DifferentialChangeset $changeset) {
$meta = array();
$qparams = array(
'ref' => $ref,
'whitespace' => $this->whitespace,
);
if ($this->standaloneURI) {
$uri = new PhutilURI($this->standaloneURI);
$uri->setQueryParams($uri->getQueryParams() + $qparams);
$meta['standaloneURI'] = (string)$uri;
}
$repository = $this->repository;
if ($repository) {
$meta['diffusionURI'] = (string)$repository->getDiffusionBrowseURIForPath(
$changeset->getAbsoluteRepositoryPath($repository, $this->diff),
idx($changeset->getMetadata(), 'line:first'),
$this->getBranch());
}
$change = $changeset->getChangeType();
if ($this->leftRawFileURI) {
if ($change != DifferentialChangeType::TYPE_ADD) {
$uri = new PhutilURI($this->leftRawFileURI);
$uri->setQueryParams($uri->getQueryParams() + $qparams);
$meta['leftURI'] = (string)$uri;
}
}
if ($this->rightRawFileURI) {
if ($change != DifferentialChangeType::TYPE_DELETE &&
$change != DifferentialChangeType::TYPE_MULTICOPY) {
$uri = new PhutilURI($this->rightRawFileURI);
$uri->setQueryParams($uri->getQueryParams() + $qparams);
$meta['rightURI'] = (string)$uri;
}
}
$user = $this->user;
if ($user && $repository) {
$path = ltrim(
$changeset->getAbsoluteRepositoryPath($repository, $this->diff),
'/');
$line = idx($changeset->getMetadata(), 'line:first', 1);
$callsign = $repository->getCallsign();
$editor_link = $user->loadEditorLink($path, $line, $callsign);
if ($editor_link) {
$meta['editor'] = $editor_link;
} else {
$meta['editorConfigure'] = '/settings/panel/display/';
}
}
$meta['containerID'] = $detail->getID();
Javelin::initBehavior(
'differential-dropdown-menus',
array());
- return javelin_render_tag(
+ return javelin_tag(
'a',
array(
'class' => 'button small grey',
'meta' => $meta,
'href' => idx($meta, 'detailURI', '#'),
'target' => '_blank',
'sigil' => 'differential-view-options',
),
- "View Options \xE2\x96\xBC");
+ pht("View Options \xE2\x96\xBC"));
}
}
diff --git a/src/applications/differential/view/DifferentialDiffTableOfContentsView.php b/src/applications/differential/view/DifferentialDiffTableOfContentsView.php
index 818522b2ba..75de304237 100644
--- a/src/applications/differential/view/DifferentialDiffTableOfContentsView.php
+++ b/src/applications/differential/view/DifferentialDiffTableOfContentsView.php
@@ -1,272 +1,270 @@
<?php
final class DifferentialDiffTableOfContentsView extends AphrontView {
private $changesets = array();
private $visibleChangesets = array();
private $references = array();
private $repository;
private $diff;
private $renderURI = '/differential/changeset/';
private $revisionID;
private $whitespace;
private $unitTestData;
public function setChangesets($changesets) {
$this->changesets = $changesets;
return $this;
}
public function setVisibleChangesets($visible_changesets) {
$this->visibleChangesets = $visible_changesets;
return $this;
}
public function setRenderingReferences(array $references) {
$this->references = $references;
return $this;
}
public function setRepository(PhabricatorRepository $repository) {
$this->repository = $repository;
return $this;
}
public function setDiff(DifferentialDiff $diff) {
$this->diff = $diff;
return $this;
}
public function setUnitTestData($unit_test_data) {
$this->unitTestData = $unit_test_data;
return $this;
}
public function setRevisionID($revision_id) {
$this->revisionID = $revision_id;
return $this;
}
public function setWhitespace($whitespace) {
$this->whitespace = $whitespace;
return $this;
}
public function render() {
require_celerity_resource('differential-core-view-css');
require_celerity_resource('differential-table-of-contents-css');
$rows = array();
$coverage = array();
if ($this->unitTestData) {
$coverage_by_file = array();
foreach ($this->unitTestData as $result) {
$test_coverage = idx($result, 'coverage');
if (!$test_coverage) {
continue;
}
foreach ($test_coverage as $file => $results) {
$coverage_by_file[$file][] = $results;
}
}
foreach ($coverage_by_file as $file => $coverages) {
$coverage[$file] = ArcanistUnitTestResult::mergeCoverage($coverages);
}
}
$changesets = $this->changesets;
$paths = array();
foreach ($changesets as $id => $changeset) {
$type = $changeset->getChangeType();
$ftype = $changeset->getFileType();
$ref = idx($this->references, $id);
$link = $this->renderChangesetLink($changeset, $ref);
if (DifferentialChangeType::isOldLocationChangeType($type)) {
$away = $changeset->getAwayPaths();
if (count($away) > 1) {
$meta = array();
if ($type == DifferentialChangeType::TYPE_MULTICOPY) {
$meta[] = pht('Deleted after being copied to multiple locations:');
} else {
$meta[] = pht('Copied to multiple locations:');
}
foreach ($away as $path) {
$meta[] = phutil_escape_html($path);
}
$meta = implode('<br />', $meta);
} else {
if ($type == DifferentialChangeType::TYPE_MOVE_AWAY) {
$meta = pht('Moved to %s', phutil_escape_html(reset($away)));
} else {
$meta = pht('Copied to %s', phutil_escape_html(reset($away)));
}
}
} else if ($type == DifferentialChangeType::TYPE_MOVE_HERE) {
$meta = pht('Moved from %s',
phutil_escape_html($changeset->getOldFile()));
} else if ($type == DifferentialChangeType::TYPE_COPY_HERE) {
$meta = pht('Copied from %s',
phutil_escape_html($changeset->getOldFile()));
} else {
$meta = null;
}
$line_count = $changeset->getAffectedLineCount();
if ($line_count == 0) {
$lines = null;
} else {
$lines = ' '.pht('(%d line(s))', $line_count);
}
$char = DifferentialChangeType::getSummaryCharacterForChangeType($type);
$chartitle = DifferentialChangeType::getFullNameForChangeType($type);
$desc = DifferentialChangeType::getShortNameForFileType($ftype);
if ($desc) {
$desc = '('.$desc.')';
}
$pchar =
($changeset->getOldProperties() === $changeset->getNewProperties())
? null
: '<span title="'.pht('Properties Changed').'">M</span>';
$fname = $changeset->getFilename();
$cov = $this->renderCoverage($coverage, $fname);
if ($cov === null) {
$mcov = $cov = '<em>-</em>';
} else {
$mcov = phutil_tag(
'div',
array(
'id' => 'differential-mcoverage-'.md5($fname),
'class' => 'differential-mcoverage-loading',
),
(isset($this->visibleChangesets[$id]) ? 'Loading...' : '?'));
}
$rows[] =
'<tr>'.
phutil_render_tag(
'td',
array(
'class' => 'differential-toc-char',
'title' => $chartitle,
),
$char).
'<td class="differential-toc-prop">'.$pchar.'</td>'.
'<td class="differential-toc-ftype">'.$desc.'</td>'.
'<td class="differential-toc-file">'.$link.$lines.'</td>'.
'<td class="differential-toc-cov">'.$cov.'</td>'.
'<td class="differential-toc-mcov">'.$mcov.'</td>'.
'</tr>';
if ($meta) {
$rows[] =
'<tr>'.
'<td colspan="3"></td>'.
'<td class="differential-toc-meta">'.$meta.'</td>'.
'</tr>';
}
if ($this->diff && $this->repository) {
$paths[] =
$changeset->getAbsoluteRepositoryPath($this->repository, $this->diff);
}
}
$editor_link = null;
if ($paths && $this->user) {
$editor_link = $this->user->loadEditorLink(
$paths,
1, // line number
$this->repository->getCallsign());
if ($editor_link) {
$editor_link =
phutil_tag(
'a',
array(
'href' => $editor_link,
'class' => 'button differential-toc-edit-all',
),
pht('Open All in Editor'));
}
}
- $reveal_link =
- javelin_render_tag(
+ $reveal_link = javelin_tag(
'a',
array(
'sigil' => 'differential-reveal-all',
'mustcapture' => true,
'class' => 'button differential-toc-reveal-all',
),
- pht('Show All Context')
- );
+ pht('Show All Context'));
$buttons =
'<tr><td colspan="7">'.
$editor_link.$reveal_link.
'</td></tr>';
return
id(new PhabricatorAnchorView())
->setAnchorName('toc')
->setNavigationMarker(true)
->render().
id(new PhabricatorHeaderView())
->setHeader(pht('Table of Contents'))
->render().
'<div class="differential-toc differential-panel">'.
'<table>'.
'<tr>'.
'<th></th>'.
'<th></th>'.
'<th></th>'.
'<th>Path</th>'.
'<th class="differential-toc-cov">'.
pht('Coverage (All)').
'</th>'.
'<th class="differential-toc-mcov">'.
pht('Coverage (Touched)').
'</th>'.
'</tr>'.
implode("\n", $rows).
$buttons.
'</table>'.
'</div>';
}
private function renderCoverage(array $coverage, $file) {
$info = idx($coverage, $file);
if (!$info) {
return null;
}
$not_covered = substr_count($info, 'U');
$covered = substr_count($info, 'C');
if (!$not_covered && !$covered) {
return null;
}
return sprintf('%d%%', 100 * ($covered / ($covered + $not_covered)));
}
private function renderChangesetLink(DifferentialChangeset $changeset, $ref) {
$display_file = $changeset->getDisplayFilename();
- return javelin_render_tag(
+ return javelin_tag(
'a',
array(
'href' => '#'.$changeset->getAnchorName(),
'meta' => array(
'id' => 'diff-'.$changeset->getAnchorName(),
'ref' => $ref,
),
'sigil' => 'differential-load',
),
- phutil_escape_html($display_file));
+ $display_file);
}
}
diff --git a/src/applications/differential/view/DifferentialInlineCommentEditView.php b/src/applications/differential/view/DifferentialInlineCommentEditView.php
index 86a2706ce6..9471325c34 100644
--- a/src/applications/differential/view/DifferentialInlineCommentEditView.php
+++ b/src/applications/differential/view/DifferentialInlineCommentEditView.php
@@ -1,141 +1,141 @@
<?php
final class DifferentialInlineCommentEditView extends AphrontView {
private $inputs = array();
private $uri;
private $title;
private $onRight;
private $number;
private $length;
public function addHiddenInput($key, $value) {
$this->inputs[] = array($key, $value);
return $this;
}
public function setSubmitURI($uri) {
$this->uri = $uri;
return $this;
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function setOnRight($on_right) {
$this->onRight = $on_right;
$this->addHiddenInput('on_right', $on_right);
return $this;
}
public function setNumber($number) {
$this->number = $number;
return $this;
}
public function setLength($length) {
$this->length = $length;
return $this;
}
public function render() {
if (!$this->uri) {
throw new Exception("Call setSubmitURI() before render()!");
}
if (!$this->user) {
throw new Exception("Call setUser() before render()!");
}
$content = phabricator_render_form(
$this->user,
array(
'action' => $this->uri,
'method' => 'POST',
'sigil' => 'inline-edit-form',
),
$this->renderInputs().
$this->renderBody());
if ($this->onRight) {
$core =
'<th></th>'.
'<td class="left"></td>'.
'<th></th>'.
'<td colspan="3" class="right3">'.$content.'</td>';
} else {
$core =
'<th></th>'.
'<td class="left">'.$content.'</td>'.
'<th></th>'.
'<td colspan="3" class="right3"></td>';
}
return '<table><tr class="inline-comment-splint">'.$core.'</tr></table>';
}
private function renderInputs() {
$out = array();
foreach ($this->inputs as $input) {
list($name, $value) = $input;
$out[] = phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => $name,
'value' => $value,
));
}
return implode('', $out);
}
private function renderBody() {
$buttons = array();
$buttons[] = '<button>Ready</button>';
- $buttons[] = javelin_render_tag(
+ $buttons[] = javelin_tag(
'button',
array(
'sigil' => 'inline-edit-cancel',
'class' => 'grey',
),
pht('Cancel'));
$buttons = implode('', $buttons);
$formatting = phutil_tag(
'a',
array(
'href' => PhabricatorEnv::getDoclink(
'article/Remarkup_Reference.html'),
'tabindex' => '-1',
'target' => '_blank',
),
pht('Formatting Reference'));
return javelin_render_tag(
'div',
array(
'class' => 'differential-inline-comment-edit',
'sigil' => 'differential-inline-comment',
'meta' => array(
'on_right' => $this->onRight,
'number' => $this->number,
'length' => $this->length,
),
),
'<div class="differential-inline-comment-edit-title">'.
phutil_escape_html($this->title).
'</div>'.
'<div class="differential-inline-comment-edit-body">'.
$this->renderChildren().
'</div>'.
'<div class="differential-inline-comment-edit-buttons">'.
$formatting.
$buttons.
'<div style="clear: both;"></div>'.
'</div>');
}
}
diff --git a/src/applications/differential/view/DifferentialInlineCommentView.php b/src/applications/differential/view/DifferentialInlineCommentView.php
index 09ac5a906b..0340d3287a 100644
--- a/src/applications/differential/view/DifferentialInlineCommentView.php
+++ b/src/applications/differential/view/DifferentialInlineCommentView.php
@@ -1,261 +1,261 @@
<?php
final class DifferentialInlineCommentView extends AphrontView {
private $inlineComment;
private $onRight;
private $buildScaffolding;
private $handles;
private $markupEngine;
private $editable;
private $preview;
private $allowReply;
public function setInlineComment(PhabricatorInlineCommentInterface $comment) {
$this->inlineComment = $comment;
return $this;
}
public function setOnRight($on_right) {
$this->onRight = $on_right;
return $this;
}
public function setBuildScaffolding($scaffold) {
$this->buildScaffolding = $scaffold;
return $this;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function setMarkupEngine(PhabricatorMarkupEngine $engine) {
$this->markupEngine = $engine;
return $this;
}
public function setEditable($editable) {
$this->editable = $editable;
return $this;
}
public function setPreview($preview) {
$this->preview = $preview;
return $this;
}
public function setAllowReply($allow_reply) {
$this->allowReply = $allow_reply;
return $this;
}
public function render() {
$inline = $this->inlineComment;
$start = $inline->getLineNumber();
$length = $inline->getLineLength();
if ($length) {
$end = $start + $length;
$line = 'Lines '.number_format($start).'-'.number_format($end);
} else {
$line = 'Line '.number_format($start);
}
$metadata = array(
'id' => $inline->getID(),
'number' => $inline->getLineNumber(),
'length' => $inline->getLineLength(),
'on_right' => $this->onRight,
'original' => $inline->getContent(),
);
$sigil = 'differential-inline-comment';
if ($this->preview) {
$sigil = $sigil . ' differential-inline-comment-preview';
}
$content = $inline->getContent();
$handles = $this->handles;
$links = array();
$is_synthetic = false;
if ($inline->getSyntheticAuthor()) {
$is_synthetic = true;
}
$is_draft = false;
if ($inline->isDraft() && !$is_synthetic) {
$links[] = pht('Not Submitted Yet');
$is_draft = true;
}
if (!$this->preview) {
- $links[] = javelin_render_tag(
+ $links[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'differential-inline-prev',
),
pht('Previous'));
- $links[] = javelin_render_tag(
+ $links[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'differential-inline-next',
),
pht('Next'));
if ($this->allowReply) {
if (!$is_synthetic) {
// NOTE: No product reason why you can't reply to these, but the reply
// mechanism currently sends the inline comment ID to the server, not
// file/line information, and synthetic comments don't have an inline
// comment ID.
- $links[] = javelin_render_tag(
+ $links[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'differential-inline-reply',
),
pht('Reply'));
}
}
}
$anchor_name = 'inline-'.$inline->getID();
if ($this->editable && !$this->preview) {
- $links[] = javelin_render_tag(
+ $links[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'differential-inline-edit',
),
pht('Edit'));
- $links[] = javelin_render_tag(
+ $links[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'differential-inline-delete',
),
pht('Delete'));
} else if ($this->preview) {
- $links[] = javelin_render_tag(
+ $links[] = javelin_tag(
'a',
array(
'meta' => array(
'anchor' => $anchor_name,
),
'sigil' => 'differential-inline-preview-jump',
),
pht('Not Visible'));
- $links[] = javelin_render_tag(
+ $links[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'differential-inline-delete',
),
pht('Delete'));
}
if ($links) {
$links =
'<span class="differential-inline-comment-links">'.
implode(' &middot; ', $links).
'</span>';
} else {
$links = null;
}
$content = $this->markupEngine->getOutput(
$inline,
PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
if ($this->preview) {
$anchor = null;
} else {
$anchor = phutil_tag(
'a',
array(
'name' => $anchor_name,
'id' => $anchor_name,
'class' => 'differential-inline-comment-anchor',
),
'');
}
$classes = array(
'differential-inline-comment',
);
if ($is_draft) {
$classes[] = 'differential-inline-comment-unsaved-draft';
}
if ($is_synthetic) {
$classes[] = 'differential-inline-comment-synthetic';
}
$classes = implode(' ', $classes);
if ($is_synthetic) {
$author = $inline->getSyntheticAuthor();
} else {
$author = $handles[$inline->getAuthorPHID()]->getName();
}
$markup = javelin_render_tag(
'div',
array(
'class' => $classes,
'sigil' => $sigil,
'meta' => $metadata,
),
'<div class="differential-inline-comment-head">'.
$anchor.
$links.
' <span class="differential-inline-comment-line">'.$line.'</span> '.
phutil_escape_html($author).
'</div>'.
'<div class="differential-inline-comment-content">'.
'<div class="phabricator-remarkup">'.
$content.
'</div>'.
'</div>');
return $this->scaffoldMarkup($markup);
}
private function scaffoldMarkup($markup) {
if (!$this->buildScaffolding) {
return $markup;
}
$left_markup = !$this->onRight ? $markup : '';
$right_markup = $this->onRight ? $markup : '';
return
'<table>'.
'<tr class="inline">'.
'<th></th>'.
'<td class="left">'.$left_markup.'</td>'.
'<th></th>'.
'<td class="right3" colspan="3">'.$right_markup.'</td>'.
'</tr>'.
'</table>';
}
}
diff --git a/src/applications/differential/view/DifferentialRevisionCommentListView.php b/src/applications/differential/view/DifferentialRevisionCommentListView.php
index 48d085a75d..8edeaeea81 100644
--- a/src/applications/differential/view/DifferentialRevisionCommentListView.php
+++ b/src/applications/differential/view/DifferentialRevisionCommentListView.php
@@ -1,192 +1,192 @@
<?php
final class DifferentialRevisionCommentListView extends AphrontView {
private $comments;
private $handles;
private $inlines;
private $changesets;
private $target;
private $versusDiffID;
private $id;
public function setComments(array $comments) {
assert_instances_of($comments, 'DifferentialComment');
$this->comments = $comments;
return $this;
}
public function setInlineComments(array $inline_comments) {
assert_instances_of($inline_comments, 'PhabricatorInlineCommentInterface');
$this->inlines = $inline_comments;
return $this;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function setChangesets(array $changesets) {
assert_instances_of($changesets, 'DifferentialChangeset');
$this->changesets = $changesets;
return $this;
}
public function setTargetDiff(DifferentialDiff $target) {
$this->target = $target;
return $this;
}
public function setVersusDiffID($diff_vs) {
$this->versusDiffID = $diff_vs;
return $this;
}
public function getID() {
if (!$this->id) {
$this->id = celerity_generate_unique_node_id();
}
return $this->id;
}
public function render() {
require_celerity_resource('differential-revision-comment-list-css');
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($this->user);
foreach ($this->comments as $comment) {
$comment->giveFacebookSomeArbitraryDiff($this->target);
$engine->addObject(
$comment,
DifferentialComment::MARKUP_FIELD_BODY);
}
foreach ($this->inlines as $inline) {
$engine->addObject(
$inline,
PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
}
$engine->process();
$inlines = mgroup($this->inlines, 'getCommentID');
$num = 1;
$html = array();
foreach ($this->comments as $comment) {
$view = new DifferentialRevisionCommentView();
$view->setComment($comment);
$view->setUser($this->user);
$view->setHandles($this->handles);
$view->setMarkupEngine($engine);
$view->setInlineComments(idx($inlines, $comment->getID(), array()));
$view->setChangesets($this->changesets);
$view->setTargetDiff($this->target);
$view->setVersusDiffID($this->versusDiffID);
if ($comment->getAction() == DifferentialAction::ACTION_SUMMARIZE) {
$view->setAnchorName('summary');
} else if ($comment->getAction() == DifferentialAction::ACTION_TESTPLAN) {
$view->setAnchorName('test-plan');
} else {
$view->setAnchorName('comment-'.$num);
$num++;
}
$html[] = $view->render();
}
$objs = array_reverse(array_values($this->comments));
$html = array_reverse(array_values($html));
$user = $this->user;
$last_comment = null;
// Find the most recent comment by the viewer.
foreach ($objs as $position => $comment) {
if ($user && ($comment->getAuthorPHID() == $user->getPHID())) {
if ($last_comment === null) {
$last_comment = $position;
} else if ($last_comment == $position - 1) {
// If the viewer made several comments in a row, show them all. This
// is a spaz rule for epriestley.
$last_comment = $position;
}
}
}
$header = array();
$hidden = array();
if ($last_comment !== null) {
foreach ($objs as $position => $comment) {
if (!$comment->getID()) {
// These are synthetic comments with summary/test plan information.
$header[] = $html[$position];
unset($html[$position]);
continue;
}
if ($position <= $last_comment) {
// Always show comments after the viewer's last comment.
continue;
}
if ($position < 3) {
// Always show the 3 most recent comments.
continue;
}
$hidden[] = $position;
}
}
if (count($hidden) <= 3) {
// Don't hide if there's not much to hide.
$hidden = array();
}
$header = array_reverse($header);
$hidden = array_select_keys($html, $hidden);
$visible = array_diff_key($html, $hidden);
$hidden = array_reverse($hidden);
$visible = array_reverse($visible);
if ($hidden) {
Javelin::initBehavior(
'differential-show-all-comments',
array(
'markup' => implode("\n", $hidden),
));
$hidden = javelin_render_tag(
'div',
array(
'sigil' => "differential-all-comments-container",
),
'<div class="differential-older-comments-are-hidden">'.
pht('%d older comments are hidden. ', number_format(count($hidden))).
- javelin_render_tag(
+ javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'differential-show-all-comments',
),
pht('Show all comments.')).
'</div>');
} else {
$hidden = null;
}
return javelin_render_tag(
'div',
array(
'class' => 'differential-comment-list',
'id' => $this->getID(),
),
implode("\n", $header).
$hidden.
implode("\n", $visible));
}
}
diff --git a/src/applications/differential/view/DifferentialRevisionListView.php b/src/applications/differential/view/DifferentialRevisionListView.php
index 3d8a9cb89a..b0306deec5 100644
--- a/src/applications/differential/view/DifferentialRevisionListView.php
+++ b/src/applications/differential/view/DifferentialRevisionListView.php
@@ -1,205 +1,205 @@
<?php
/**
* Render a table of Differential revisions.
*/
final class DifferentialRevisionListView extends AphrontView {
private $revisions;
private $flags = array();
private $drafts = array();
private $handles;
private $fields;
private $highlightAge;
public function setFields(array $fields) {
assert_instances_of($fields, 'DifferentialFieldSpecification');
$this->fields = $fields;
return $this;
}
public function setRevisions(array $revisions) {
assert_instances_of($revisions, 'DifferentialRevision');
$this->revisions = $revisions;
return $this;
}
public function setHighlightAge($bool) {
$this->highlightAge = $bool;
return $this;
}
public function getRequiredHandlePHIDs() {
$phids = array();
foreach ($this->fields as $field) {
foreach ($this->revisions as $revision) {
$phids[] = $field->getRequiredHandlePHIDsForRevisionList($revision);
}
}
return array_mergev($phids);
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function loadAssets() {
$user = $this->user;
if (!$user) {
throw new Exception("Call setUser() before loadAssets()!");
}
if ($this->revisions === null) {
throw new Exception("Call setRevisions() before loadAssets()!");
}
$this->flags = id(new PhabricatorFlagQuery())
->withOwnerPHIDs(array($user->getPHID()))
->withObjectPHIDs(mpull($this->revisions, 'getPHID'))
->execute();
$this->drafts = id(new DifferentialRevisionQuery())
->withIDs(mpull($this->revisions, 'getID'))
->withDraftRepliesByAuthors(array($user->getPHID()))
->execute();
return $this;
}
public function render() {
$user = $this->user;
if (!$user) {
throw new Exception("Call setUser() before render()!");
}
$fresh = null;
$stale = null;
if ($this->highlightAge) {
$fresh = PhabricatorEnv::getEnvConfig('differential.days-fresh');
if ($fresh) {
$fresh = PhabricatorCalendarHoliday::getNthBusinessDay(
time(),
-$fresh);
}
$stale = PhabricatorEnv::getEnvConfig('differential.days-stale');
if ($stale) {
$stale = PhabricatorCalendarHoliday::getNthBusinessDay(
time(),
-$stale);
}
}
Javelin::initBehavior('phabricator-tooltips', array());
require_celerity_resource('aphront-tooltip-css');
$flagged = mpull($this->flags, null, 'getObjectPHID');
foreach ($this->fields as $field) {
$field->setUser($this->user);
$field->setHandles($this->handles);
}
$cell_classes = array();
$rows = array();
foreach ($this->revisions as $revision) {
$phid = $revision->getPHID();
$flag = '';
if (isset($flagged[$phid])) {
$class = PhabricatorFlagColor::getCSSClass($flagged[$phid]->getColor());
$note = $flagged[$phid]->getNote();
- $flag = javelin_render_tag(
+ $flag = javelin_tag(
'div',
$note ? array(
'class' => 'phabricator-flag-icon '.$class,
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $note,
'align' => 'N',
'size' => 240,
),
) : array(
'class' => 'phabricator-flag-icon '.$class,
),
'');
} else if (array_key_exists($revision->getID(), $this->drafts)) {
$src = '/rsrc/image/icon/fatcow/page_white_edit.png';
$flag =
'<a href="/D'.$revision->getID().'#comment-preview">'.
phutil_tag(
'img',
array(
'src' => celerity_get_resource_uri($src),
'width' => 16,
'height' => 16,
'alt' => 'Draft',
'title' => pht('Draft Comment'),
)).
'</a>';
}
$row = array($flag);
$modified = $revision->getDateModified();
foreach ($this->fields as $field) {
if (($fresh || $stale) &&
$field instanceof DifferentialDateModifiedFieldSpecification) {
if ($stale && $modified < $stale) {
$class = 'revision-age-old';
} else if ($fresh && $modified < $fresh) {
$class = 'revision-age-stale';
} else {
$class = 'revision-age-fresh';
}
$cell_classes[count($rows)][count($row)] = $class;
}
$row[] = $field->renderValueForRevisionList($revision);
}
$rows[] = $row;
}
$headers = array('');
$classes = array('');
foreach ($this->fields as $field) {
$headers[] = $field->renderHeaderForRevisionList();
$classes[] = $field->getColumnClassForRevisionList();
}
$table = new AphrontTableView($rows);
$table->setHeaders($headers);
$table->setColumnClasses($classes);
$table->setCellClasses($cell_classes);
$table->setNoDataString(pht('No revisions found.'));
require_celerity_resource('differential-revision-history-css');
return $table->render();
}
public static function getDefaultFields() {
$selector = DifferentialFieldSelector::newSelector();
$fields = $selector->getFieldSpecifications();
foreach ($fields as $key => $field) {
if (!$field->shouldAppearOnRevisionList()) {
unset($fields[$key]);
}
}
if (!$fields) {
throw new Exception(
"Phabricator configuration has no fields that appear on the list ".
"interface!");
}
return $selector->sortFieldsForRevisionList($fields);
}
}
diff --git a/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php b/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php
index 8e7e9f926c..718d837f53 100644
--- a/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php
+++ b/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php
@@ -1,323 +1,323 @@
<?php
final class DifferentialRevisionUpdateHistoryView extends AphrontView {
private $diffs = array();
private $selectedVersusDiffID;
private $selectedDiffID;
private $selectedWhitespace;
public function setDiffs(array $diffs) {
assert_instances_of($diffs, 'DifferentialDiff');
$this->diffs = $diffs;
return $this;
}
public function setSelectedVersusDiffID($id) {
$this->selectedVersusDiffID = $id;
return $this;
}
public function setSelectedDiffID($id) {
$this->selectedDiffID = $id;
return $this;
}
public function setSelectedWhitespace($whitespace) {
$this->selectedWhitespace = $whitespace;
return $this;
}
public function render() {
require_celerity_resource('differential-core-view-css');
require_celerity_resource('differential-revision-history-css');
$data = array(
array(
'name' => 'Base',
'id' => null,
'desc' => 'Base',
'age' => null,
'obj' => null,
),
);
$seq = 0;
foreach ($this->diffs as $diff) {
$data[] = array(
'name' => 'Diff '.(++$seq),
'id' => $diff->getID(),
'desc' => $diff->getDescription(),
'age' => $diff->getDateCreated(),
'obj' => $diff,
);
}
$max_id = $diff->getID();
$idx = 0;
$rows = array();
$disable = false;
$radios = array();
$last_base = null;
foreach ($data as $row) {
$diff = $row['obj'];
$name = $row['name'];
$id = $row['id'];
$old_class = null;
$new_class = null;
if ($id) {
$new_checked = ($this->selectedDiffID == $id);
- $new = javelin_render_tag(
+ $new = javelin_tag(
'input',
array(
'type' => 'radio',
'name' => 'id',
'value' => $id,
'checked' => $new_checked ? 'checked' : null,
'sigil' => 'differential-new-radio',
));
if ($new_checked) {
$new_class = " revhistory-new-now";
$disable = true;
}
} else {
$new = null;
}
if ($max_id != $id) {
$uniq = celerity_generate_unique_node_id();
$old_checked = ($this->selectedVersusDiffID == $id);
$old = phutil_tag(
'input',
array(
'type' => 'radio',
'name' => 'vs',
'value' => $id,
'id' => $uniq,
'checked' => $old_checked ? 'checked' : null,
'disabled' => $disable ? 'disabled' : null,
));
$radios[] = $uniq;
if ($old_checked) {
$old_class = " revhistory-old-now";
}
} else {
$old = null;
}
$desc = $row['desc'];
if ($row['age']) {
$age = phabricator_datetime($row['age'], $this->getUser());
} else {
$age = null;
}
if (++$idx % 2) {
$class = ' class="alt"';
} else {
$class = null;
}
if ($diff) {
$lint = self::renderDiffLintStar($row['obj']);
$unit = self::renderDiffUnitStar($row['obj']);
$lint_message = self::getDiffLintMessage($diff);
$unit_message = self::getDiffUnitMessage($diff);
$lint_title = ' title="'.phutil_escape_html($lint_message).'"';
$unit_title = ' title="'.phutil_escape_html($unit_message).'"';
$base = $this->renderBaseRevision($diff);
} else {
$lint = null;
$unit = null;
$lint_title = null;
$unit_title = null;
$base = null;
}
if ($last_base !== null && $base !== $last_base) {
// TODO: Render some kind of notice about rebases.
}
$last_base = $base;
$id_link = phutil_tag(
'a',
array('href' => '/differential/diff/'.$id.'/'),
$id);
$rows[] =
'<tr'.$class.'>'.
'<td class="revhistory-name">'.phutil_escape_html($name).'</td>'.
'<td class="revhistory-id">'.$id_link.'</td>'.
'<td class="revhistory-base">'.phutil_escape_html($base).'</td>'.
'<td class="revhistory-desc">'.phutil_escape_html($desc).'</td>'.
'<td class="revhistory-age">'.$age.'</td>'.
'<td class="revhistory-star"'.$lint_title.'>'.$lint.'</td>'.
'<td class="revhistory-star"'.$unit_title.'>'.$unit.'</td>'.
'<td class="revhistory-old'.$old_class.'">'.$old.'</td>'.
'<td class="revhistory-new'.$new_class.'">'.$new.'</td>'.
'</tr>';
}
Javelin::initBehavior(
'differential-diff-radios',
array(
'radios' => $radios,
));
$options = array(
DifferentialChangesetParser::WHITESPACE_IGNORE_FORCE => 'Ignore All',
DifferentialChangesetParser::WHITESPACE_IGNORE_ALL => 'Ignore Most',
DifferentialChangesetParser::WHITESPACE_IGNORE_TRAILING =>
'Ignore Trailing',
DifferentialChangesetParser::WHITESPACE_SHOW_ALL => 'Show All',
);
$select = '<select name="whitespace">';
foreach ($options as $value => $label) {
$select .= phutil_tag(
'option',
array(
'value' => $value,
'selected' => ($value == $this->selectedWhitespace)
? 'selected'
: null,
),
$label);
}
$select .= '</select>';
return
id(new PhabricatorHeaderView())
->setHeader(pht('Revision Update History'))
->render() .
'<div class="differential-revision-history differential-panel">'.
'<form action="#toc">'.
'<table class="differential-revision-history-table">'.
'<tr>'.
'<th>'.pht('Diff').'</th>'.
'<th>'.pht('ID').'</th>'.
'<th>'.pht('Base').'</th>'.
'<th>'.pht('Description').'</th>'.
'<th>'.pht('Created').'</th>'.
'<th>'.pht('Lint').'</th>'.
'<th>'.pht('Unit').'</th>'.
'</tr>'.
implode("\n", $rows).
'<tr>'.
'<td colspan="9" class="diff-differ-submit">'.
'<label>'.pht('Whitespace Changes: %s', $select).'</label>'.
'<button>'.pht('Show Diff').'</button>'.
'</td>'.
'</tr>'.
'</table>'.
'</form>'.
'</div>';
}
const STAR_NONE = 'none';
const STAR_OKAY = 'okay';
const STAR_WARN = 'warn';
const STAR_FAIL = 'fail';
const STAR_SKIP = 'skip';
public static function renderDiffLintStar(DifferentialDiff $diff) {
static $map = array(
DifferentialLintStatus::LINT_NONE => self::STAR_NONE,
DifferentialLintStatus::LINT_OKAY => self::STAR_OKAY,
DifferentialLintStatus::LINT_WARN => self::STAR_WARN,
DifferentialLintStatus::LINT_FAIL => self::STAR_FAIL,
DifferentialLintStatus::LINT_SKIP => self::STAR_SKIP,
DifferentialLintStatus::LINT_POSTPONED => self::STAR_SKIP
);
$star = idx($map, $diff->getLintStatus(), self::STAR_FAIL);
return self::renderDiffStar($star);
}
public static function renderDiffUnitStar(DifferentialDiff $diff) {
static $map = array(
DifferentialUnitStatus::UNIT_NONE => self::STAR_NONE,
DifferentialUnitStatus::UNIT_OKAY => self::STAR_OKAY,
DifferentialUnitStatus::UNIT_WARN => self::STAR_WARN,
DifferentialUnitStatus::UNIT_FAIL => self::STAR_FAIL,
DifferentialUnitStatus::UNIT_SKIP => self::STAR_SKIP,
DifferentialUnitStatus::UNIT_POSTPONED => self::STAR_SKIP,
);
$star = idx($map, $diff->getUnitStatus(), self::STAR_FAIL);
return self::renderDiffStar($star);
}
public static function getDiffLintMessage(DifferentialDiff $diff) {
switch ($diff->getLintStatus()) {
case DifferentialLintStatus::LINT_NONE:
return 'No Linters Available';
case DifferentialLintStatus::LINT_OKAY:
return 'Lint OK';
case DifferentialLintStatus::LINT_WARN:
return 'Lint Warnings';
case DifferentialLintStatus::LINT_FAIL:
return 'Lint Errors';
case DifferentialLintStatus::LINT_SKIP:
return 'Lint Skipped';
case DifferentialLintStatus::LINT_POSTPONED:
return 'Lint Postponed';
}
return '???';
}
public static function getDiffUnitMessage(DifferentialDiff $diff) {
switch ($diff->getUnitStatus()) {
case DifferentialUnitStatus::UNIT_NONE:
return 'No Unit Test Coverage';
case DifferentialUnitStatus::UNIT_OKAY:
return 'Unit Tests OK';
case DifferentialUnitStatus::UNIT_WARN:
return 'Unit Test Warnings';
case DifferentialUnitStatus::UNIT_FAIL:
return 'Unit Test Errors';
case DifferentialUnitStatus::UNIT_SKIP:
return 'Unit Tests Skipped';
case DifferentialUnitStatus::UNIT_POSTPONED:
return 'Unit Tests Postponed';
}
return '???';
}
private static function renderDiffStar($star) {
$class = 'diff-star-'.$star;
return
'<span class="'.$class.'">'.
"\xE2\x98\x85".
'</span>';
}
private function renderBaseRevision(DifferentialDiff $diff) {
switch ($diff->getSourceControlSystem()) {
case 'git':
$base = $diff->getSourceControlBaseRevision();
if (strpos($base, '@') === false) {
return substr($base, 0, 7);
} else {
// The diff is from git-svn
$base = explode('@', $base);
$base = last($base);
return $base;
}
case 'svn':
$base = $diff->getSourceControlBaseRevision();
$base = explode('@', $base);
$base = last($base);
return $base;
default:
return null;
}
}
}
diff --git a/src/applications/diffusion/controller/DiffusionBrowseFileController.php b/src/applications/diffusion/controller/DiffusionBrowseFileController.php
index 36bc8443a3..624dbdb9ac 100644
--- a/src/applications/diffusion/controller/DiffusionBrowseFileController.php
+++ b/src/applications/diffusion/controller/DiffusionBrowseFileController.php
@@ -1,945 +1,945 @@
<?php
final class DiffusionBrowseFileController extends DiffusionController {
private $corpusType = 'text';
private $lintCommit;
private $lintMessages;
public function processRequest() {
$request = $this->getRequest();
$drequest = $this->getDiffusionRequest();
$before = $request->getStr('before');
if ($before) {
return $this->buildBeforeResponse($before);
}
$path = $drequest->getPath();
$selected = $request->getStr('view');
$preferences = $request->getUser()->loadPreferences();
if (!$selected) {
$selected = $preferences->getPreference(
PhabricatorUserPreferences::PREFERENCE_DIFFUSION_VIEW,
'highlighted');
} else if ($request->isFormPost() && $selected != 'raw') {
$preferences->setPreference(
PhabricatorUserPreferences::PREFERENCE_DIFFUSION_VIEW,
$selected);
$preferences->save();
return id(new AphrontRedirectResponse())
->setURI($request->getRequestURI()->alter('view', $selected));
}
$needs_blame = ($selected == 'blame' || $selected == 'plainblame');
$file_query = DiffusionFileContentQuery::newFromDiffusionRequest(
$this->diffusionRequest);
$file_query->setViewer($request->getUser());
$file_query->setNeedsBlame($needs_blame);
$file_query->loadFileContent();
$data = $file_query->getRawData();
if ($selected === 'raw') {
return $this->buildRawResponse($path, $data);
}
$this->loadLintMessages();
// Build the content of the file.
$corpus = $this->buildCorpus(
$selected,
$file_query,
$needs_blame,
$drequest,
$path,
$data);
require_celerity_resource('diffusion-source-css');
if ($this->corpusType == 'text') {
$view_select_panel = $this->renderViewSelectPanel($selected);
} else {
$view_select_panel = null;
}
// Render the page.
$content = array();
$follow = $request->getStr('follow');
if ($follow) {
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$notice->setTitle('Unable to Continue');
switch ($follow) {
case 'first':
$notice->appendChild(
"Unable to continue tracing the history of this file because ".
"this commit is the first commit in the repository.");
break;
case 'created':
$notice->appendChild(
"Unable to continue tracing the history of this file because ".
"this commit created the file.");
break;
}
$content[] = $notice;
}
$renamed = $request->getStr('renamed');
if ($renamed) {
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$notice->setTitle('File Renamed');
$notice->appendChild(
"File history passes through a rename from '".
phutil_escape_html($drequest->getPath())."' to '".
phutil_escape_html($renamed)."'.");
$content[] = $notice;
}
$content[] = $view_select_panel;
$content[] = $corpus;
$content[] = $this->buildOpenRevisions();
$nav = $this->buildSideNav('browse', true);
$nav->appendChild($content);
$crumbs = $this->buildCrumbs(
array(
'branch' => true,
'path' => true,
'view' => 'browse',
));
$nav->setCrumbs($crumbs);
$basename = basename($this->getDiffusionRequest()->getPath());
return $this->buildApplicationPage(
$nav,
array(
'title' => $basename,
));
}
private function loadLintMessages() {
$drequest = $this->getDiffusionRequest();
$branch = $drequest->loadBranch();
if (!$branch || !$branch->getLintCommit()) {
return;
}
$this->lintCommit = $branch->getLintCommit();
$conn = id(new PhabricatorRepository())->establishConnection('r');
$where = '';
if ($drequest->getLint()) {
$where = qsprintf(
$conn,
'AND code = %s',
$drequest->getLint());
}
$this->lintMessages = queryfx_all(
$conn,
'SELECT * FROM %T WHERE branchID = %d %Q AND path = %s',
PhabricatorRepository::TABLE_LINTMESSAGE,
$branch->getID(),
$where,
'/'.$drequest->getPath());
}
private function buildCorpus($selected,
DiffusionFileContentQuery $file_query,
$needs_blame,
DiffusionRequest $drequest,
$path,
$data) {
if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) {
$file = $this->loadFileForData($path, $data);
$file_uri = $file->getBestURI();
if ($file->isViewableImage()) {
$this->corpusType = 'image';
return $this->buildImageCorpus($file_uri);
} else {
$this->corpusType = 'binary';
return $this->buildBinaryCorpus($file_uri, $data);
}
}
switch ($selected) {
case 'plain':
$style =
"margin: 1em 2em; width: 90%; height: 80em; font-family: monospace";
$corpus = phutil_tag(
'textarea',
array(
'style' => $style,
),
$file_query->getRawData());
break;
case 'plainblame':
$style =
"margin: 1em 2em; width: 90%; height: 80em; font-family: monospace";
list($text_list, $rev_list, $blame_dict) =
$file_query->getBlameData();
$rows = array();
foreach ($text_list as $k => $line) {
$rev = $rev_list[$k];
if (isset($blame_dict[$rev]['handle'])) {
$author = $blame_dict[$rev]['handle']->getName();
} else {
$author = $blame_dict[$rev]['author'];
}
$rows[] =
sprintf("%-10s %-20s %s", substr($rev, 0, 7), $author, $line);
}
$corpus = phutil_tag(
'textarea',
array(
'style' => $style,
),
implode("\n", $rows));
break;
case 'highlighted':
case 'blame':
default:
require_celerity_resource('syntax-highlighting-css');
list($text_list, $rev_list, $blame_dict) = $file_query->getBlameData();
$text_list = implode("\n", $text_list);
$text_list = PhabricatorSyntaxHighlighter::highlightWithFilename(
$path,
$text_list);
$text_list = explode("\n", $text_list);
$rows = $this->buildDisplayRows($text_list, $rev_list, $blame_dict,
$needs_blame, $drequest, $file_query, $selected);
$id = celerity_generate_unique_node_id();
$projects = $drequest->loadArcanistProjects();
$langs = array();
foreach ($projects as $project) {
$ls = $project->getSymbolIndexLanguages();
if (!$ls) {
continue;
}
$dep_projects = $project->getSymbolIndexProjects();
$dep_projects[] = $project->getPHID();
foreach ($ls as $lang) {
if (!isset($langs[$lang])) {
$langs[$lang] = array();
}
$langs[$lang] += $dep_projects + array($project);
}
}
$lang = last(explode('.', $drequest->getPath()));
$prefs = $this->getRequest()->getUser()->loadPreferences();
$pref_symbols = $prefs->getPreference(
PhabricatorUserPreferences::PREFERENCE_DIFFUSION_SYMBOLS);
if (isset($langs[$lang]) && $pref_symbols != 'disabled') {
Javelin::initBehavior(
'repository-crossreference',
array(
'container' => $id,
'lang' => $lang,
'projects' => $langs[$lang],
));
}
$corpus_table = javelin_render_tag(
'table',
array(
'class' => "diffusion-source remarkup-code PhabricatorMonospaced",
'sigil' => 'diffusion-source',
),
implode("\n", $rows));
$corpus = phutil_tag(
'div',
array(
'style' => 'padding: 0 2em;',
'id' => $id,
),
$corpus_table);
break;
}
return $corpus;
}
private function renderViewSelectPanel($selected) {
$toggle_blame = array(
'highlighted' => 'blame',
'blame' => 'highlighted',
'plain' => 'plainblame',
'plainblame' => 'plain',
'raw' => 'raw', // not a real case.
);
$toggle_highlight = array(
'highlighted' => 'plain',
'blame' => 'plainblame',
'plain' => 'highlighted',
'plainblame' => 'blame',
'raw' => 'raw', // not a real case.
);
$user = $this->getRequest()->getUser();
$base_uri = $this->getRequest()->getRequestURI();
$blame_on = ($selected == 'blame' || $selected == 'plainblame');
if ($blame_on) {
$blame_text = pht('Disable Blame');
} else {
$blame_text = pht('Enable Blame');
}
$blame_button = $this->createViewAction(
$blame_text,
$base_uri->alter('view', $toggle_blame[$selected]),
$user);
$highlight_on = ($selected == 'blame' || $selected == 'highlighted');
if ($highlight_on) {
$highlight_text = pht('Disable Highlighting');
} else {
$highlight_text = pht('Enable Highlighting');
}
$highlight_button = $this->createViewAction(
$highlight_text,
$base_uri->alter('view', $toggle_highlight[$selected]),
$user);
$href = null;
if ($this->getRequest()->getStr('lint') !== null) {
$lint_text = pht('Hide %d Lint Message(s)', count($this->lintMessages));
$href = $base_uri->alter('lint', null);
} else if ($this->lintCommit === null) {
$lint_text = pht('Lint not Available');
} else {
$lint_text = pht(
'Show %d Lint Message(s)',
count($this->lintMessages));
$href = $this->getDiffusionRequest()->generateURI(array(
'action' => 'browse',
'commit' => $this->lintCommit,
))->alter('lint', '');
}
$lint_button = $this->createViewAction(
$lint_text,
$href,
$user);
if (!$href) {
$lint_button->setDisabled(true);
}
$raw_button = $this->createViewAction(
pht('View Raw File'),
$base_uri->alter('view', 'raw'),
$user,
'file');
$edit_button = $this->createEditAction();
return id(new PhabricatorActionListView())
->setUser($user)
->addAction($blame_button)
->addAction($highlight_button)
->addAction($lint_button)
->addAction($raw_button)
->addAction($edit_button);
}
private function createViewAction(
$localized_text,
$href,
$user,
$icon = null) {
return id(new PhabricatorActionView())
->setName($localized_text)
->setIcon($icon)
->setUser($user)
->setRenderAsForm(true)
->setHref($href);
}
private function createEditAction() {
$request = $this->getRequest();
$user = $request->getUser();
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$path = $drequest->getPath();
$line = nonempty((int)$drequest->getLine(), 1);
$callsign = $repository->getCallsign();
$editor_link = $user->loadEditorLink($path, $line, $callsign);
$action = id(new PhabricatorActionView())
->setName(pht('Open in Editor'))
->setIcon('edit');
$action->setHref($editor_link);
$action->setDisabled(!$editor_link);
return $action;
}
private function buildDisplayRows(
array $text_list,
array $rev_list,
array $blame_dict,
$needs_blame,
DiffusionRequest $drequest,
DiffusionFileContentQuery $file_query,
$selected) {
if ($blame_dict) {
$epoch_list = ipull(ifilter($blame_dict, 'epoch'), 'epoch');
$epoch_min = min($epoch_list);
$epoch_max = max($epoch_list);
$epoch_range = ($epoch_max - $epoch_min) + 1;
}
$line_arr = array();
$line_str = $drequest->getLine();
$ranges = explode(',', $line_str);
foreach ($ranges as $range) {
if (strpos($range, '-') !== false) {
list($min, $max) = explode('-', $range, 2);
$line_arr[] = array(
'min' => min($min, $max),
'max' => max($min, $max),
);
} else if (strlen($range)) {
$line_arr[] = array(
'min' => $range,
'max' => $range,
);
}
}
$display = array();
$line_number = 1;
$last_rev = null;
$color = null;
foreach ($text_list as $k => $line) {
$display_line = array(
'color' => null,
'epoch' => null,
'commit' => null,
'author' => null,
'target' => null,
'highlighted' => null,
'line' => $line_number,
'data' => $line,
);
if ($needs_blame) {
// If the line's rev is same as the line above, show empty content
// with same color; otherwise generate blame info. The newer a change
// is, the more saturated the color.
$rev = idx($rev_list, $k, $last_rev);
if ($last_rev == $rev) {
$display_line['color'] = $color;
} else {
$blame = $blame_dict[$rev];
if (!isset($blame['epoch'])) {
$color = '#ffd'; // Render as warning.
} else {
$color_ratio = ($blame['epoch'] - $epoch_min) / $epoch_range;
$color_value = 0xF6 * (1.0 - $color_ratio);
$color = sprintf(
'#%02x%02x%02x',
$color_value,
0xF6,
$color_value);
}
$display_line['epoch'] = idx($blame, 'epoch');
$display_line['color'] = $color;
$display_line['commit'] = $rev;
if (isset($blame['handle'])) {
$author_link = $blame['handle']->renderLink();
} else {
$author_link = phutil_tag(
'span',
array(
),
$blame['author']);
}
$display_line['author'] = $author_link;
$last_rev = $rev;
}
}
if ($line_arr) {
if ($line_number == $line_arr[0]['min']) {
$display_line['target'] = true;
}
foreach ($line_arr as $range) {
if ($line_number >= $range['min'] &&
$line_number <= $range['max']) {
$display_line['highlighted'] = true;
}
}
}
$display[] = $display_line;
++$line_number;
}
$commits = array_filter(ipull($display, 'commit'));
if ($commits) {
$commits = id(new PhabricatorAuditCommitQuery())
->withIdentifiers($drequest->getRepository()->getID(), $commits)
->needCommitData(true)
->execute();
$commits = mpull($commits, null, 'getCommitIdentifier');
}
$revision_ids = id(new DifferentialRevision())
->loadIDsByCommitPHIDs(mpull($commits, 'getPHID'));
$revisions = array();
if ($revision_ids) {
$revisions = id(new DifferentialRevision())->loadAllWhere(
'id IN (%Ld)',
$revision_ids);
}
$request = $this->getRequest();
$user = $request->getUser();
Javelin::initBehavior('phabricator-oncopy', array());
$engine = null;
$inlines = array();
if ($this->getRequest()->getStr('lint') !== null && $this->lintMessages) {
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($user);
foreach ($this->lintMessages as $message) {
$inline = id(new PhabricatorAuditInlineComment())
->setID($message['id'])
->setSyntheticAuthor(
ArcanistLintSeverity::getStringForSeverity($message['severity']).
' '.$message['code'].' ('.$message['name'].')')
->setLineNumber($message['line'])
->setContent($message['description']);
$inlines[$message['line']][] = $inline;
$engine->addObject(
$inline,
PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
}
$engine->process();
require_celerity_resource('differential-changeset-view-css');
}
$rows = $this->renderInlines(
idx($inlines, 0, array()),
$needs_blame,
$engine);
foreach ($display as $line) {
$line_href = $drequest->generateURI(
array(
'action' => 'browse',
'line' => $line['line'],
'stable' => true,
));
$blame = array();
if ($line['color']) {
$color = $line['color'];
$before_link = null;
$commit_link = null;
$revision_link = null;
if (idx($line, 'commit')) {
$commit = $line['commit'];
$summary = 'Unknown';
if (idx($commits, $commit)) {
$summary = $commits[$commit]->getCommitData()->getSummary();
}
$tooltip = phabricator_date(
$line['epoch'],
$user)." \xC2\xB7 ".$summary;
Javelin::initBehavior('phabricator-tooltips', array());
require_celerity_resource('aphront-tooltip-css');
- $commit_link = javelin_render_tag(
+ $commit_link = javelin_tag(
'a',
array(
'href' => $drequest->generateURI(
array(
'action' => 'commit',
'commit' => $line['commit'],
)),
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $tooltip,
'align' => 'E',
'size' => 600,
),
),
- phutil_escape_html(phutil_utf8_shorten($line['commit'], 9, '')));
+ phutil_utf8_shorten($line['commit'], 9, ''));
$revision_id = null;
if (idx($commits, $commit)) {
$revision_id = idx($revision_ids, $commits[$commit]->getPHID());
}
if ($revision_id) {
$revision = idx($revisions, $revision_id);
if (!$revision) {
$tooltip = '(Invalid revision)';
} else {
$tooltip =
phabricator_date($revision->getDateModified(), $user).
" \xC2\xB7 ".
$revision->getTitle();
}
- $revision_link = javelin_render_tag(
+ $revision_link = javelin_tag(
'a',
array(
'href' => '/D'.$revision_id,
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $tooltip,
'align' => 'E',
'size' => 600,
),
),
'D'.$revision_id);
}
$uri = $line_href->alter('before', $commit);
- $before_link = javelin_render_tag(
+ $before_link = javelin_tag(
'a',
array(
'href' => $uri->setQueryParam('view', 'blame'),
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => 'Skip Past This Commit',
'align' => 'E',
'size' => 300,
),
),
"\xC2\xAB");
}
$blame[] = phutil_tag(
'th',
array(
'class' => 'diffusion-blame-link',
'style' => 'background: '.$color,
),
$before_link);
$blame[] = phutil_tag(
'th',
array(
'class' => 'diffusion-rev-link',
'style' => 'background: '.$color,
),
$commit_link);
$blame[] = phutil_tag(
'th',
array(
'class' => 'diffusion-rev-link',
'style' => 'background: '.$color,
),
$revision_link);
$blame[] = phutil_tag(
'th',
array(
'class' => 'diffusion-author-link',
'style' => 'background: '.$color,
),
idx($line, 'author'));
}
$line_link = phutil_tag(
'a',
array(
'href' => $line_href,
),
$line['line']);
- $blame[] = javelin_render_tag(
+ $blame[] = javelin_tag(
'th',
array(
'class' => 'diffusion-line-link',
'sigil' => 'diffusion-line-link',
'style' => isset($color) ? 'background: '.$color : null,
),
$line_link);
Javelin::initBehavior('diffusion-line-linker');
if ($line['target']) {
Javelin::initBehavior(
'diffusion-jump-to',
array(
'target' => 'scroll_target',
));
$anchor_text = '<a id="scroll_target"></a>';
} else {
$anchor_text = null;
}
$blame[] = phutil_render_tag(
'td',
array(
),
$anchor_text.
"\xE2\x80\x8B". // NOTE: See phabricator-oncopy behavior.
$line['data']);
$rows[] = phutil_tag(
'tr',
array(
'class' => ($line['highlighted'] ? 'highlighted' : null),
),
$blame);
$rows = array_merge($rows, $this->renderInlines(
idx($inlines, $line['line'], array()),
$needs_blame,
$engine));
}
return $rows;
}
private function renderInlines(array $inlines, $needs_blame, $engine) {
$rows = array();
foreach ($inlines as $inline) {
$inline_view = id(new DifferentialInlineCommentView())
->setMarkupEngine($engine)
->setInlineComment($inline)
->render();
$rows[] =
'<tr class="inline">'.
str_repeat('<th></th>', ($needs_blame ? 5 : 1)).
'<td>'.$inline_view.'</td>'.
'</tr>';
}
return $rows;
}
private function loadFileForData($path, $data) {
return PhabricatorFile::buildFromFileDataOrHash(
$data,
array(
'name' => basename($path),
));
}
private function buildRawResponse($path, $data) {
$file = $this->loadFileForData($path, $data);
return id(new AphrontRedirectResponse())->setURI($file->getBestURI());
}
private function buildImageCorpus($file_uri) {
$properties = new PhabricatorPropertyListView();
$properties->addProperty(
pht('Image'),
phutil_tag(
'img',
array(
'src' => $file_uri,
)));
$actions = id(new PhabricatorActionListView())
->setUser($this->getRequest()->getUser())
->addAction($this->createEditAction());
return array($actions, $properties);
}
private function buildBinaryCorpus($file_uri, $data) {
$properties = new PhabricatorPropertyListView();
$size = strlen($data);
$properties->addTextContent(
pht('This is a binary file. It is %2$s byte(s) in length.',
$size,
PhutilTranslator::getInstance()->formatNumber($size))
);
$actions = id(new PhabricatorActionListView())
->setUser($this->getRequest()->getUser())
->addAction($this->createEditAction())
->addAction(id(new PhabricatorActionView())
->setName(pht('Download Binary File...'))
->setIcon('download')
->setHref($file_uri));
return array($actions, $properties);
}
private function buildBeforeResponse($before) {
$request = $this->getRequest();
$drequest = $this->getDiffusionRequest();
// NOTE: We need to get the grandparent so we can capture filename changes
// in the parent.
$parent = $this->loadParentRevisionOf($before);
$old_filename = null;
$was_created = false;
if ($parent) {
$grandparent = $this->loadParentRevisionOf(
$parent->getCommitIdentifier());
if ($grandparent) {
$rename_query = new DiffusionRenameHistoryQuery();
$rename_query->setRequest($drequest);
$rename_query->setOldCommit($grandparent->getCommitIdentifier());
$old_filename = $rename_query->loadOldFilename();
$was_created = $rename_query->getWasCreated();
}
}
$follow = null;
if ($was_created) {
// If the file was created in history, that means older commits won't
// have it. Since we know it existed at 'before', it must have been
// created then; jump there.
$target_commit = $before;
$follow = 'created';
} else if ($parent) {
// If we found a parent, jump to it. This is the normal case.
$target_commit = $parent->getCommitIdentifier();
} else {
// If there's no parent, this was probably created in the initial commit?
// And the "was_created" check will fail because we can't identify the
// grandparent. Keep the user at 'before'.
$target_commit = $before;
$follow = 'first';
}
$path = $drequest->getPath();
$renamed = null;
if ($old_filename !== null &&
$old_filename !== '/'.$path) {
$renamed = $path;
$path = $old_filename;
}
$line = null;
// If there's a follow error, drop the line so the user sees the message.
if (!$follow) {
$line = $this->getBeforeLineNumber($target_commit);
}
$before_uri = $drequest->generateURI(
array(
'action' => 'browse',
'commit' => $target_commit,
'line' => $line,
'path' => $path,
));
$before_uri->setQueryParams($request->getRequestURI()->getQueryParams());
$before_uri = $before_uri->alter('before', null);
$before_uri = $before_uri->alter('renamed', $renamed);
$before_uri = $before_uri->alter('follow', $follow);
return id(new AphrontRedirectResponse())->setURI($before_uri);
}
private function getBeforeLineNumber($target_commit) {
$drequest = $this->getDiffusionRequest();
$line = $drequest->getLine();
if (!$line) {
return null;
}
$diff_query = DiffusionRawDiffQuery::newFromDiffusionRequest($drequest);
$diff_query->setAgainstCommit($target_commit);
try {
$raw_diff = $diff_query->loadRawDiff();
$old_line = 0;
$new_line = 0;
foreach (explode("\n", $raw_diff) as $text) {
if ($text[0] == '-' || $text[0] == ' ') {
$old_line++;
}
if ($text[0] == '+' || $text[0] == ' ') {
$new_line++;
}
if ($new_line == $line) {
return $old_line;
}
}
// We didn't find the target line.
return $line;
} catch (Exception $ex) {
return $line;
}
}
private function loadParentRevisionOf($commit) {
$drequest = $this->getDiffusionRequest();
$before_req = DiffusionRequest::newFromDictionary(
array(
'repository' => $drequest->getRepository(),
'commit' => $commit,
));
$query = DiffusionCommitParentsQuery::newFromDiffusionRequest($before_req);
$parents = $query->loadParents();
return head($parents);
}
}
diff --git a/src/applications/diffusion/controller/DiffusionCommitEditController.php b/src/applications/diffusion/controller/DiffusionCommitEditController.php
index fcfb0cd42e..c70b47260d 100644
--- a/src/applications/diffusion/controller/DiffusionCommitEditController.php
+++ b/src/applications/diffusion/controller/DiffusionCommitEditController.php
@@ -1,96 +1,96 @@
<?php
final class DiffusionCommitEditController extends DiffusionController {
public function willProcessRequest(array $data) {
$this->diffusionRequest = DiffusionRequest::newFromDictionary($data);
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$drequest = $this->getDiffusionRequest();
$callsign = $drequest->getRepository()->getCallsign();
$repository = $drequest->getRepository();
$commit = $drequest->loadCommit();
$page_title = 'Edit Diffusion Commit';
if (!$commit) {
return new Aphront404Response();
}
$commit_phid = $commit->getPHID();
$edge_type = PhabricatorEdgeConfig::TYPE_COMMIT_HAS_PROJECT;
$current_proj_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$commit_phid,
$edge_type
);
$handles = $this->loadViewerHandles($current_proj_phids);
$proj_t_values = mpull($handles, 'getFullName', 'getPHID');
if ($request->isFormPost()) {
$proj_phids = $request->getArr('projects');
$new_proj_phids = array_values($proj_phids);
$rem_proj_phids = array_diff($current_proj_phids,
$new_proj_phids);
$editor = id(new PhabricatorEdgeEditor());
$editor->setActor($user);
foreach ($rem_proj_phids as $phid) {
$editor->removeEdge($commit_phid, $edge_type, $phid);
}
foreach ($new_proj_phids as $phid) {
$editor->addEdge($commit_phid, $edge_type, $phid);
}
$editor->save();
id(new PhabricatorSearchIndexer())
->indexDocumentByPHID($commit->getPHID());
return id(new AphrontRedirectResponse())
->setURI('/r'.$callsign.$commit->getCommitIdentifier());
}
$tokenizer_id = celerity_generate_unique_node_id();
$form = id(new AphrontFormView())
->setUser($user)
->setAction($request->getRequestURI()->getPath())
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel('Projects')
->setName('projects')
->setValue($proj_t_values)
->setID($tokenizer_id)
->setCaption(
- javelin_render_tag(
+ javelin_tag(
'a',
array(
'href' => '/project/create/',
'mustcapture' => true,
'sigil' => 'project-create',
),
- 'Create New Project'))
- ->setDatasource('/typeahead/common/projects/'));;
+ pht('Create New Project')))
+ ->setDatasource('/typeahead/common/projects/'));;
Javelin::initBehavior('project-create', array(
'tokenizerID' => $tokenizer_id,
));
$submit = id(new AphrontFormSubmitControl())
->setValue('Save')
->addCancelButton('/r'.$callsign.$commit->getCommitIdentifier());
$form->appendChild($submit);
$panel = id(new AphrontPanelView())
->setHeader('Edit Diffusion Commit')
->appendChild($form)
->setWidth(AphrontPanelView::WIDTH_FORM);
return $this->buildStandardPageResponse(
$panel,
array(
'title' => $page_title,
));
}
}
diff --git a/src/applications/diffusion/view/DiffusionCommitChangeTableView.php b/src/applications/diffusion/view/DiffusionCommitChangeTableView.php
index c3ec644cd7..3d6d5eb7b1 100644
--- a/src/applications/diffusion/view/DiffusionCommitChangeTableView.php
+++ b/src/applications/diffusion/view/DiffusionCommitChangeTableView.php
@@ -1,103 +1,103 @@
<?php
final class DiffusionCommitChangeTableView extends DiffusionView {
private $pathChanges;
private $ownersPaths = array();
private $renderingReferences;
public function setPathChanges(array $path_changes) {
assert_instances_of($path_changes, 'DiffusionPathChange');
$this->pathChanges = $path_changes;
return $this;
}
public function setOwnersPaths(array $owners_paths) {
assert_instances_of($owners_paths, 'PhabricatorOwnersPath');
$this->ownersPaths = $owners_paths;
return $this;
}
public function setRenderingReferences(array $value) {
$this->renderingReferences = $value;
return $this;
}
public function render() {
$rows = array();
$rowc = array();
// TODO: Experiment with path stack rendering.
// TODO: Copy Away and Move Away are rendered junkily still.
foreach ($this->pathChanges as $id => $change) {
$path = $change->getPath();
$hash = substr(md5($path), 0, 8);
if ($change->getFileType() == DifferentialChangeType::FILE_DIRECTORY) {
$path .= '/';
}
if (isset($this->renderingReferences[$id])) {
- $path_column = javelin_render_tag(
+ $path_column = javelin_tag(
'a',
array(
'href' => '#'.$hash,
'meta' => array(
'id' => 'diff-'.$hash,
'ref' => $this->renderingReferences[$id],
),
'sigil' => 'differential-load',
),
- phutil_escape_html($path));
+ $path);
} else {
$path_column = phutil_escape_html($path);
}
$rows[] = array(
$this->linkHistory($change->getPath()),
$this->linkBrowse($change->getPath()),
$this->linkChange(
$change->getChangeType(),
$change->getFileType(),
$change->getPath()),
$path_column,
);
$row_class = null;
foreach ($this->ownersPaths as $owners_path) {
$excluded = $owners_path->getExcluded();
$owners_path = $owners_path->getPath();
if (strncmp('/'.$path, $owners_path, strlen($owners_path)) == 0) {
if ($excluded) {
$row_class = null;
break;
}
$row_class = 'highlighted';
}
}
$rowc[] = $row_class;
}
$view = new AphrontTableView($rows);
$view->setHeaders(
array(
'History',
'Browse',
'Change',
'Path',
));
$view->setColumnClasses(
array(
'',
'',
'',
'wide',
));
$view->setRowClasses($rowc);
$view->setNoDataString('This change has not been fully parsed yet.');
return $view->render();
}
}
diff --git a/src/applications/diffusion/view/DiffusionHistoryTableView.php b/src/applications/diffusion/view/DiffusionHistoryTableView.php
index 73dd317a3d..08784f71f7 100644
--- a/src/applications/diffusion/view/DiffusionHistoryTableView.php
+++ b/src/applications/diffusion/view/DiffusionHistoryTableView.php
@@ -1,333 +1,333 @@
<?php
final class DiffusionHistoryTableView extends DiffusionView {
private $history;
private $revisions = array();
private $handles = array();
private $isHead;
private $parents;
public function setHistory(array $history) {
assert_instances_of($history, 'DiffusionPathChange');
$this->history = $history;
return $this;
}
public function loadRevisions() {
$commit_phids = array();
foreach ($this->history as $item) {
if ($item->getCommit()) {
$commit_phids[] = $item->getCommit()->getPHID();
}
}
$this->revisions = id(new DifferentialRevision())
->loadIDsByCommitPHIDs($commit_phids);
return $this;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function getRequiredHandlePHIDs() {
$phids = array();
foreach ($this->history as $item) {
$data = $item->getCommitData();
if ($data) {
if ($data->getCommitDetail('authorPHID')) {
$phids[$data->getCommitDetail('authorPHID')] = true;
}
if ($data->getCommitDetail('committerPHID')) {
$phids[$data->getCommitDetail('committerPHID')] = true;
}
}
}
return array_keys($phids);
}
public function setParents(array $parents) {
$this->parents = $parents;
return $this;
}
public function setIsHead($is_head) {
$this->isHead = $is_head;
return $this;
}
public function render() {
$drequest = $this->getDiffusionRequest();
$handles = $this->handles;
$graph = null;
if ($this->parents) {
$graph = $this->renderGraph();
}
$rows = array();
$ii = 0;
foreach ($this->history as $history) {
$epoch = $history->getEpoch();
if ($epoch) {
$date = phabricator_date($epoch, $this->user);
$time = phabricator_time($epoch, $this->user);
} else {
$date = null;
$time = null;
}
$data = $history->getCommitData();
$author_phid = $committer = $committer_phid = null;
if ($data) {
$author_phid = $data->getCommitDetail('authorPHID');
$committer_phid = $data->getCommitDetail('committerPHID');
$committer = $data->getCommitDetail('committer');
}
if ($author_phid && isset($handles[$author_phid])) {
$author = $handles[$author_phid]->renderLink();
} else {
$author = self::renderName($history->getAuthorName());
}
$different_committer = false;
if ($committer_phid) {
$different_committer = ($committer_phid != $author_phid);
} else if ($committer != '') {
$different_committer = ($committer != $history->getAuthorName());
}
if ($different_committer) {
if ($committer_phid && isset($handles[$committer_phid])) {
$committer = $handles[$committer_phid]->renderLink();
} else {
$committer = self::renderName($committer);
}
$author .= '/'.$committer;
}
$commit = $history->getCommit();
if ($commit && !$commit->getIsUnparsed() && $data) {
$change = $this->linkChange(
$history->getChangeType(),
$history->getFileType(),
$path = null,
$history->getCommitIdentifier());
} else {
$change = "<em>Importing\xE2\x80\xA6</em>";
}
$rows[] = array(
$this->linkBrowse(
$drequest->getPath(),
array(
'commit' => $history->getCommitIdentifier(),
)),
$graph ? $graph[$ii++] : null,
self::linkCommit(
$drequest->getRepository(),
$history->getCommitIdentifier()),
($commit ?
self::linkRevision(idx($this->revisions, $commit->getPHID())) :
null),
$change,
$date,
$time,
$author,
AphrontTableView::renderSingleDisplayLine(
phutil_escape_html($history->getSummary())),
// TODO: etc etc
);
}
$view = new AphrontTableView($rows);
$view->setHeaders(
array(
'Browse',
'',
'Commit',
'Revision',
'Change',
'Date',
'Time',
'Author/Committer',
'Details',
));
$view->setColumnClasses(
array(
'',
'threads',
'n',
'n',
'',
'',
'right',
'',
'wide',
));
$view->setColumnVisibility(
array(
true,
$graph ? true : false,
));
return $view->render();
}
/**
* Draw a merge/branch graph from the parent revision data. We're basically
* building up a bunch of strings like this:
*
* ^
* |^
* o|
* |o
* o
*
* ...which form an ASCII representation of the graph we eventaully want to
* draw.
*
* NOTE: The actual implementation is black magic.
*/
private function renderGraph() {
// This keeps our accumulated information about each line of the
// merge/branch graph.
$graph = array();
// This holds the next commit we're looking for in each column of the
// graph.
$threads = array();
// This is the largest number of columns any row has, i.e. the width of
// the graph.
$count = 0;
foreach ($this->history as $key => $history) {
$joins = array();
$splits = array();
$parent_list = $this->parents[$history->getCommitIdentifier()];
// Look for some thread which has this commit as the next commit. If
// we find one, this commit goes on that thread. Otherwise, this commit
// goes on a new thread.
$line = '';
$found = false;
$pos = count($threads);
for ($n = 0; $n < $count; $n++) {
if (empty($threads[$n])) {
$line .= ' ';
continue;
}
if ($threads[$n] == $history->getCommitIdentifier()) {
if ($found) {
$line .= ' ';
$joins[] = $n;
unset($threads[$n]);
} else {
$line .= 'o';
$found = true;
$pos = $n;
}
} else {
// We render a "|" for any threads which have a commit that we haven't
// seen yet, this is later drawn as a vertical line.
$line .= '|';
}
}
// If we didn't find the thread this commit goes on, start a new thread.
// We use "o" to mark the commit for the rendering engine, or "^" to
// indicate that there's nothing after it so the line from the commit
// upward should not be drawn.
if (!$found) {
if ($this->isHead) {
$line .= '^';
} else {
$line .= 'o';
foreach ($graph as $k => $meta) {
// Go back across all the lines we've already drawn and add a
// "|" to the end, since this is connected to some future commit
// we don't know about.
for ($jj = strlen($meta['line']); $jj <= $count; $jj++) {
$graph[$k]['line'] .= '|';
}
}
}
}
// Update the next commit on this thread to the commit's first parent.
// This might have the effect of making a new thread.
$threads[$pos] = head($parent_list);
// If we made a new thread, increase the thread count.
$count = max($pos + 1, $count);
// Now, deal with splits (merges). I picked this terms opposite to the
// underlying repository term to confuse you.
foreach (array_slice($parent_list, 1) as $parent) {
$found = false;
// Try to find the other parent(s) in our existing threads. If we find
// them, split to that thread.
foreach ($threads as $idx => $thread_commit) {
if ($thread_commit == $parent) {
$found = true;
$splits[] = $idx;
}
}
// If we didn't find the parent, we don't know about it yet. Find the
// first free thread and add it as the "next" commit in that thread.
// This might create a new thread.
if (!$found) {
for ($n = 0; $n < $count; $n++) {
if (empty($threads[$n])) {
break;
}
}
$threads[$n] = $parent;
$splits[] = $n;
$count = max($n + 1, $count);
}
}
$graph[] = array(
'line' => $line,
'split' => $splits,
'join' => $joins,
);
}
// Render into tags for the behavior.
foreach ($graph as $k => $meta) {
- $graph[$k] = javelin_render_tag(
+ $graph[$k] = javelin_tag(
'div',
array(
'sigil' => 'commit-graph',
'meta' => $meta,
),
'');
}
Javelin::initBehavior(
'diffusion-commit-graph',
array(
'count' => $count,
));
return $graph;
}
}
diff --git a/src/applications/diffusion/view/DiffusionView.php b/src/applications/diffusion/view/DiffusionView.php
index 83dff57748..c36ddc1c46 100644
--- a/src/applications/diffusion/view/DiffusionView.php
+++ b/src/applications/diffusion/view/DiffusionView.php
@@ -1,162 +1,162 @@
<?php
abstract class DiffusionView extends AphrontView {
private $diffusionRequest;
final public function setDiffusionRequest(DiffusionRequest $request) {
$this->diffusionRequest = $request;
return $this;
}
final public function getDiffusionRequest() {
return $this->diffusionRequest;
}
final public function linkChange($change_type, $file_type, $path = null,
$commit_identifier = null) {
$text = DifferentialChangeType::getFullNameForChangeType($change_type);
if ($change_type == DifferentialChangeType::TYPE_CHILD) {
// TODO: Don't link COPY_AWAY without a direct change.
return $text;
}
if ($file_type == DifferentialChangeType::FILE_DIRECTORY) {
return $text;
}
$href = $this->getDiffusionRequest()->generateURI(
array(
'action' => 'change',
'path' => $path,
'commit' => $commit_identifier,
));
return phutil_tag(
'a',
array(
'href' => $href,
),
$text);
}
final public function linkHistory($path) {
$href = $this->getDiffusionRequest()->generateURI(
array(
'action' => 'history',
'path' => $path,
));
return phutil_tag(
'a',
array(
'href' => $href,
),
'History');
}
final public function linkBrowse($path, array $details = array()) {
$href = $this->getDiffusionRequest()->generateURI(
$details + array(
'action' => 'browse',
'path' => $path,
));
if (isset($details['text'])) {
$text = $details['text'];
} else {
$text = 'Browse';
}
return phutil_tag(
'a',
array(
'href' => $href,
),
$text);
}
final public function linkExternal($hash, $uri, $text) {
$href = id(new PhutilURI('/diffusion/external/'))
->setQueryParams(
array(
'uri' => $uri,
'id' => $hash,
));
return phutil_tag(
'a',
array(
'href' => $href,
),
$text);
}
final public static function nameCommit(
PhabricatorRepository $repository,
$commit) {
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$commit_name = substr($commit, 0, 12);
break;
default:
$commit_name = $commit;
break;
}
$callsign = $repository->getCallsign();
return "r{$callsign}{$commit_name}";
}
final public static function linkCommit(
PhabricatorRepository $repository,
$commit) {
$commit_name = self::nameCommit($repository, $commit);
$callsign = $repository->getCallsign();
return phutil_tag(
'a',
array(
'href' => "/r{$callsign}{$commit}",
),
$commit_name);
}
final public static function linkRevision($id) {
if (!$id) {
return null;
}
return phutil_tag(
'a',
array(
'href' => "/D{$id}",
),
"D{$id}");
}
final protected static function renderName($name) {
$email = new PhutilEmailAddress($name);
if ($email->getDisplayName() && $email->getDomainName()) {
Javelin::initBehavior('phabricator-tooltips', array());
require_celerity_resource('aphront-tooltip-css');
- return javelin_render_tag(
+ return javelin_tag(
'span',
array(
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $email->getAddress(),
'align' => 'E',
'size' => 'auto',
),
),
- phutil_escape_html($email->getDisplayName()));
+ $email->getDisplayName());
}
return phutil_escape_html($name);
}
}
diff --git a/src/applications/herald/controller/HeraldRuleController.php b/src/applications/herald/controller/HeraldRuleController.php
index 998e31cca7..8b32bd6ae7 100644
--- a/src/applications/herald/controller/HeraldRuleController.php
+++ b/src/applications/herald/controller/HeraldRuleController.php
@@ -1,558 +1,558 @@
<?php
final class HeraldRuleController extends HeraldController {
private $id;
private $filter;
public function willProcessRequest(array $data) {
$this->id = (int)idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$content_type_map = HeraldContentTypeConfig::getContentTypeMap();
$rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap();
if ($this->id) {
$rule = id(new HeraldRule())->load($this->id);
if (!$rule) {
return new Aphront404Response();
}
if (!$this->canEditRule($rule, $user)) {
throw new Exception("You don't own this rule and can't edit it.");
}
} else {
$rule = new HeraldRule();
$rule->setAuthorPHID($user->getPHID());
$rule->setMustMatchAll(true);
$content_type = $request->getStr('content_type');
if (!isset($content_type_map[$content_type])) {
$content_type = HeraldContentTypeConfig::CONTENT_TYPE_DIFFERENTIAL;
}
$rule->setContentType($content_type);
$rule_type = $request->getStr('rule_type');
if (!isset($rule_type_map[$rule_type])) {
$rule_type = HeraldRuleTypeConfig::RULE_TYPE_GLOBAL;
}
$rule->setRuleType($rule_type);
}
$local_version = id(new HeraldRule())->getConfigVersion();
if ($rule->getConfigVersion() > $local_version) {
throw new Exception(
"This rule was created with a newer version of Herald. You can not ".
"view or edit it in this older version. Upgrade your Phabricator ".
"deployment.");
}
// Upgrade rule version to our version, since we might add newly-defined
// conditions, etc.
$rule->setConfigVersion($local_version);
$rule_conditions = $rule->loadConditions();
$rule_actions = $rule->loadActions();
$rule->attachConditions($rule_conditions);
$rule->attachActions($rule_actions);
$e_name = true;
$errors = array();
if ($request->isFormPost() && $request->getStr('save')) {
list($e_name, $errors) = $this->saveRule($rule, $request);
if (!$errors) {
$uri = '/herald/view/'.
$rule->getContentType().'/'.
$rule->getRuleType().'/';
return id(new AphrontRedirectResponse())->setURI($uri);
}
}
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setTitle('Form Errors');
$error_view->setErrors($errors);
} else {
$error_view = null;
}
$must_match_selector = $this->renderMustMatchSelector($rule);
$repetition_selector = $this->renderRepetitionSelector($rule);
$handles = $this->loadHandlesForRule($rule);
require_celerity_resource('herald-css');
$content_type_name = $content_type_map[$rule->getContentType()];
$rule_type_name = $rule_type_map[$rule->getRuleType()];
$form = id(new AphrontFormView())
->setUser($user)
->setID('herald-rule-edit-form')
->addHiddenInput('content_type', $rule->getContentType())
->addHiddenInput('rule_type', $rule->getRuleType())
->addHiddenInput('save', 1)
->appendChild(
// Build this explicitly (instead of using addHiddenInput())
// so we can add a sigil to it.
- javelin_render_tag(
+ javelin_tag(
'input',
array(
'type' => 'hidden',
'name' => 'rule',
'sigil' => 'rule',
)))
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Rule Name')
->setName('name')
->setError($e_name)
->setValue($rule->getName()));
$form
->appendChild(
id(new AphrontFormMarkupControl())
->setValue(
"This <strong>${rule_type_name}</strong> rule triggers for " .
"<strong>${content_type_name}</strong>."))
->appendChild(
id(new AphrontFormInsetView())
->setTitle('Conditions')
- ->setRightButton(javelin_render_tag(
+ ->setRightButton(javelin_tag(
'a',
array(
'href' => '#',
'class' => 'button green',
'sigil' => 'create-condition',
'mustcapture' => true
),
'Create New Condition'))
->setDescription(
'When '.$must_match_selector .
' these conditions are met:')
- ->setContent(javelin_render_tag(
+ ->setContent(javelin_tag(
'table',
array(
'sigil' => 'rule-conditions',
'class' => 'herald-condition-table'
),
'')))
->appendChild(
id(new AphrontFormInsetView())
->setTitle('Action')
- ->setRightButton(javelin_render_tag(
+ ->setRightButton(javelin_tag(
'a',
array(
'href' => '#',
'class' => 'button green',
'sigil' => 'create-action',
'mustcapture' => true,
),
'Create New Action'))
->setDescription('Take these actions '.$repetition_selector.
' this rule matches:')
- ->setContent(javelin_render_tag(
+ ->setContent(javelin_tag(
'table',
array(
'sigil' => 'rule-actions',
'class' => 'herald-action-table',
),
'')))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Save Rule')
->addCancelButton('/herald/view/'.$rule->getContentType().'/'));
$this->setupEditorBehavior($rule, $handles);
$panel = new AphrontPanelView();
$panel->setHeader(
$rule->getID()
? pht('Edit Herald Rule')
: pht('Create Herald Rule'));
$panel->appendChild($form);
$panel->setNoBackground();
$nav = $this->renderNav();
$nav->selectFilter(
'view/'.$rule->getContentType().'/'.$rule->getRuleType());
$nav->appendChild(
array(
$error_view,
$panel,
));
return $this->buildStandardPageResponse(
$nav,
array(
'title' => 'Edit Rule',
));
}
private function canEditRule($rule, $user) {
return
($user->getIsAdmin()) ||
($rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL) ||
($rule->getAuthorPHID() == $user->getPHID());
}
private function saveRule($rule, $request) {
$rule->setName($request->getStr('name'));
$rule->setMustMatchAll(($request->getStr('must_match') == 'all'));
$repetition_policy_param = $request->getStr('repetition_policy');
$rule->setRepetitionPolicy(
HeraldRepetitionPolicyConfig::toInt($repetition_policy_param)
);
$e_name = true;
$errors = array();
if (!strlen($rule->getName())) {
$e_name = "Required";
$errors[] = "Rule must have a name.";
}
$data = json_decode($request->getStr('rule'), true);
if (!is_array($data) ||
!$data['conditions'] ||
!$data['actions']) {
throw new Exception("Failed to decode rule data.");
}
$conditions = array();
foreach ($data['conditions'] as $condition) {
if ($condition === null) {
// We manage this as a sparse array on the client, so may receive
// NULL if conditions have been removed.
continue;
}
$obj = new HeraldCondition();
$obj->setFieldName($condition[0]);
$obj->setFieldCondition($condition[1]);
if (is_array($condition[2])) {
$obj->setValue(array_keys($condition[2]));
} else {
$obj->setValue($condition[2]);
}
$cond_type = $obj->getFieldCondition();
if ($cond_type == HeraldConditionConfig::CONDITION_REGEXP) {
if (@preg_match($obj->getValue(), '') === false) {
$errors[] =
'The regular expression "'.$obj->getValue().'" is not valid. '.
'Regular expressions must have enclosing characters (e.g. '.
'"@/path/to/file@", not "/path/to/file") and be syntactically '.
'correct.';
}
}
if ($cond_type == HeraldConditionConfig::CONDITION_REGEXP_PAIR) {
$json = json_decode($obj->getValue(), true);
if (!is_array($json)) {
$errors[] =
'The regular expression pair "'.$obj->getValue().'" is not '.
'valid JSON. Enter a valid JSON array with two elements.';
} else {
if (count($json) != 2) {
$errors[] =
'The regular expression pair "'.$obj->getValue().'" must have '.
'exactly two elements.';
} else {
$key_regexp = array_shift($json);
$val_regexp = array_shift($json);
if (@preg_match($key_regexp, '') === false) {
$errors[] =
'The first regexp, "'.$key_regexp.'" in the regexp pair '.
'is not a valid regexp.';
}
if (@preg_match($val_regexp, '') === false) {
$errors[] =
'The second regexp, "'.$val_regexp.'" in the regexp pair '.
'is not a valid regexp.';
}
}
}
}
$conditions[] = $obj;
}
$actions = array();
foreach ($data['actions'] as $action) {
if ($action === null) {
// Sparse on the client; removals can give us NULLs.
continue;
}
if (!isset($action[1])) {
// Legitimate for any action which doesn't need a target, like
// "Do nothing".
$action[1] = null;
}
$actions[] = HeraldActionConfig::willSaveAction($rule->getRuleType(),
$rule->getAuthorPHID(),
$action);
}
$rule->attachConditions($conditions);
$rule->attachActions($actions);
if (!$errors) {
try {
$edit_action = $rule->getID() ? 'edit' : 'create';
$rule->openTransaction();
$rule->save();
$rule->saveConditions($conditions);
$rule->saveActions($actions);
$rule->logEdit($request->getUser()->getPHID(), $edit_action);
$rule->saveTransaction();
} catch (AphrontQueryDuplicateKeyException $ex) {
$e_name = "Not Unique";
$errors[] = "Rule name is not unique. Choose a unique name.";
}
}
return array($e_name, $errors);
}
private function setupEditorBehavior($rule, $handles) {
$serial_conditions = array(
array('default', 'default', ''),
);
if ($rule->getConditions()) {
$serial_conditions = array();
foreach ($rule->getConditions() as $condition) {
$value = $condition->getValue();
if (is_array($value)) {
$value_map = array();
foreach ($value as $k => $fbid) {
$value_map[$fbid] = $handles[$fbid]->getName();
}
$value = $value_map;
}
$serial_conditions[] = array(
$condition->getFieldName(),
$condition->getFieldCondition(),
$value,
);
}
}
$serial_actions = array(
array('default', ''),
);
if ($rule->getActions()) {
$serial_actions = array();
foreach ($rule->getActions() as $action) {
switch ($action->getAction()) {
case HeraldActionConfig::ACTION_FLAG:
$current_value = $action->getTarget();
break;
default:
$target_map = array();
foreach ((array)$action->getTarget() as $fbid) {
$target_map[$fbid] = $handles[$fbid]->getName();
}
$current_value = $target_map;
break;
}
$serial_actions[] = array(
$action->getAction(),
$current_value,
);
}
}
$all_rules = $this->loadRulesThisRuleMayDependUpon($rule);
$all_rules = mpull($all_rules, 'getName', 'getID');
asort($all_rules);
$config_info = array();
$config_info['fields']
= HeraldFieldConfig::getFieldMapForContentType($rule->getContentType());
$config_info['conditions'] = HeraldConditionConfig::getConditionMap();
foreach ($config_info['fields'] as $field => $name) {
$config_info['conditionMap'][$field] = array_keys(
HeraldConditionConfig::getConditionMapForField($field));
}
foreach ($config_info['fields'] as $field => $fname) {
foreach ($config_info['conditions'] as $condition => $cname) {
$config_info['values'][$field][$condition] =
HeraldValueTypeConfig::getValueTypeForFieldAndCondition(
$field,
$condition);
}
}
$config_info['actions'] =
HeraldActionConfig::getActionMessageMap($rule->getContentType(),
$rule->getRuleType());
$config_info['rule_type'] = $rule->getRuleType();
foreach ($config_info['actions'] as $action => $name) {
$config_info['targets'][$action] =
HeraldValueTypeConfig::getValueTypeForAction($action,
$rule->getRuleType());
}
Javelin::initBehavior(
'herald-rule-editor',
array(
'root' => 'herald-rule-edit-form',
'conditions' => (object)$serial_conditions,
'actions' => (object)$serial_actions,
'template' => $this->buildTokenizerTemplates() + array(
'rules' => $all_rules,
'colors' => PhabricatorFlagColor::getColorNameMap(),
'defaultColor' => PhabricatorFlagColor::COLOR_BLUE,
),
'author' => array($rule->getAuthorPHID() =>
$handles[$rule->getAuthorPHID()]->getName()),
'info' => $config_info,
));
}
private function loadHandlesForRule($rule) {
$phids = array();
foreach ($rule->getActions() as $action) {
if (!is_array($action->getTarget())) {
continue;
}
foreach ($action->getTarget() as $target) {
$target = (array)$target;
foreach ($target as $phid) {
$phids[] = $phid;
}
}
}
foreach ($rule->getConditions() as $condition) {
$value = $condition->getValue();
if (is_array($value)) {
foreach ($value as $phid) {
$phids[] = $phid;
}
}
}
$phids[] = $rule->getAuthorPHID();
return $this->loadViewerHandles($phids);
}
/**
* Render the selector for the "When (all of | any of) these conditions are
* met:" element.
*/
private function renderMustMatchSelector($rule) {
return AphrontFormSelectControl::renderSelectTag(
$rule->getMustMatchAll() ? 'all' : 'any',
array(
'all' => 'all of',
'any' => 'any of',
),
array(
'name' => 'must_match',
));
}
/**
* Render the selector for "Take these actions (every time | only the first
* time) this rule matches..." element.
*/
private function renderRepetitionSelector($rule) {
// Make the selector for choosing how often this rule should be repeated
$repetition_policy = HeraldRepetitionPolicyConfig::toString(
$rule->getRepetitionPolicy());
$repetition_options = HeraldRepetitionPolicyConfig::getMapForContentType(
$rule->getContentType());
if (empty($repetition_options)) {
// default option is 'every time'
$repetition_selector = idx(
HeraldRepetitionPolicyConfig::getMap(),
HeraldRepetitionPolicyConfig::EVERY);
return $repetition_selector;
} else if (count($repetition_options) == 1) {
// if there's only 1 option, just pick it for the user
$repetition_selector = reset($repetition_options);
return $repetition_selector;
} else {
return AphrontFormSelectControl::renderSelectTag(
$repetition_policy,
$repetition_options,
array(
'name' => 'repetition_policy',
));
}
}
protected function buildTokenizerTemplates() {
$template = new AphrontTokenizerTemplateView();
$template = $template->render();
return array(
'source' => array(
'email' => '/typeahead/common/mailable/',
'user' => '/typeahead/common/users/',
'repository' => '/typeahead/common/repositories/',
'package' => '/typeahead/common/packages/',
'project' => '/typeahead/common/projects/',
),
'markup' => $template,
);
}
/**
* Load rules for the "Another Herald rule..." condition dropdown, which
* allows one rule to depend upon the success or failure of another rule.
*/
private function loadRulesThisRuleMayDependUpon(HeraldRule $rule) {
// Any rule can depend on a global rule.
$all_rules = id(new HeraldRuleQuery())
->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_GLOBAL))
->withContentTypes(array($rule->getContentType()))
->execute();
if ($rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) {
// Personal rules may depend upon your other personal rules.
$all_rules += id(new HeraldRuleQuery())
->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_PERSONAL))
->withContentTypes(array($rule->getContentType()))
->withAuthorPHIDs(array($rule->getAuthorPHID()))
->execute();
}
// A rule can not depend upon itself.
unset($all_rules[$rule->getID()]);
return $all_rules;
}
}
diff --git a/src/applications/herald/view/HeraldRuleListView.php b/src/applications/herald/view/HeraldRuleListView.php
index 18aa093b61..d95765ecd7 100644
--- a/src/applications/herald/view/HeraldRuleListView.php
+++ b/src/applications/herald/view/HeraldRuleListView.php
@@ -1,109 +1,109 @@
<?php
final class HeraldRuleListView extends AphrontView {
private $rules;
private $handles;
private $showAuthor;
private $showRuleType;
public function setRules(array $rules) {
assert_instances_of($rules, 'HeraldRule');
$this->rules = $rules;
return $this;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function setShowAuthor($show_author) {
$this->showAuthor = $show_author;
return $this;
}
public function setShowRuleType($show_rule_type) {
$this->showRuleType = $show_rule_type;
return $this;
}
public function render() {
$type_map = HeraldRuleTypeConfig::getRuleTypeMap();
$rows = array();
foreach ($this->rules as $rule) {
if ($rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL) {
$author = null;
} else {
$author = $this->handles[$rule->getAuthorPHID()]->renderLink();
}
$name = phutil_tag(
'a',
array(
'href' => '/herald/rule/'.$rule->getID().'/',
),
$rule->getName());
$edit_log = phutil_tag(
'a',
array(
'href' => '/herald/history/'.$rule->getID().'/',
),
'View Edit Log');
- $delete = javelin_render_tag(
+ $delete = javelin_tag(
'a',
array(
'href' => '/herald/delete/'.$rule->getID().'/',
'sigil' => 'workflow',
'class' => 'button small grey',
),
'Delete');
$rows[] = array(
$type_map[$rule->getRuleType()],
$author,
$name,
$edit_log,
$delete,
);
}
$table = new AphrontTableView($rows);
$table->setNoDataString("No matching rules.");
$table->setHeaders(
array(
'Rule Type',
'Author',
'Rule Name',
'Edit Log',
'',
));
$table->setColumnClasses(
array(
'',
'',
'wide pri',
'',
'action'
));
$table->setColumnVisibility(
array(
$this->showRuleType,
$this->showAuthor,
true,
true,
true,
));
return $table->render();
}
}
diff --git a/src/applications/maniphest/controller/ManiphestBatchEditController.php b/src/applications/maniphest/controller/ManiphestBatchEditController.php
index 01b656539b..6e1b533e45 100644
--- a/src/applications/maniphest/controller/ManiphestBatchEditController.php
+++ b/src/applications/maniphest/controller/ManiphestBatchEditController.php
@@ -1,294 +1,294 @@
<?php
/**
* @group maniphest
*/
final class ManiphestBatchEditController extends ManiphestController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$task_ids = $request->getArr('batch');
$tasks = id(new ManiphestTask())->loadAllWhere(
'id IN (%Ld)',
$task_ids);
$actions = $request->getStr('actions');
if ($actions) {
$actions = json_decode($actions, true);
}
if ($request->isFormPost() && is_array($actions)) {
foreach ($tasks as $task) {
$xactions = $this->buildTransactions($actions, $task);
if ($xactions) {
$editor = new ManiphestTransactionEditor();
$editor->setActor($user);
$editor->applyTransactions($task, $xactions);
}
}
$task_ids = implode(',', mpull($tasks, 'getID'));
return id(new AphrontRedirectResponse())
->setURI('/maniphest/view/custom/?s=oc&tasks='.$task_ids);
}
$panel = new AphrontPanelView();
$panel->setHeader(pht('Maniphest Batch Editor'));
$panel->setNoBackground();
$handle_phids = mpull($tasks, 'getOwnerPHID');
$handles = $this->loadViewerHandles($handle_phids);
$list = new ManiphestTaskListView();
$list->setTasks($tasks);
$list->setUser($user);
$list->setHandles($handles);
$template = new AphrontTokenizerTemplateView();
$template = $template->render();
require_celerity_resource('maniphest-batch-editor');
Javelin::initBehavior(
'maniphest-batch-editor',
array(
'root' => 'maniphest-batch-edit-form',
'tokenizerTemplate' => $template,
'sources' => array(
'project' => array(
'src' => '/typeahead/common/projects/',
'placeholder' => 'Type a project name...',
),
'owner' => array(
'src' => '/typeahead/common/searchowner/',
'placeholder' => 'Type a user name...',
'limit' => 1,
),
),
'input' => 'batch-form-actions',
'priorityMap' => ManiphestTaskPriority::getTaskPriorityMap(),
'statusMap' => ManiphestTaskStatus::getTaskStatusMap(),
));
$form = new AphrontFormView();
$form->setUser($user);
$form->setID('maniphest-batch-edit-form');
foreach ($tasks as $task) {
$form->appendChild(
phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => 'batch[]',
'value' => $task->getID(),
)));
}
$form->appendChild(
phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => 'actions',
'id' => 'batch-form-actions',
)));
$form->appendChild('<p>These tasks will be edited:</p>');
$form->appendChild($list);
$form->appendChild(
id(new AphrontFormInsetView())
->setTitle('Actions')
- ->setRightButton(javelin_render_tag(
+ ->setRightButton(javelin_tag(
'a',
array(
'href' => '#',
'class' => 'button green',
'sigil' => 'add-action',
'mustcapture' => true,
),
'Add Another Action'))
- ->setContent(javelin_render_tag(
+ ->setContent(javelin_tag(
'table',
array(
'sigil' => 'maniphest-batch-actions',
'class' => 'maniphest-batch-actions-table',
),
'')))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Update Tasks')
->addCancelButton('/maniphest/', 'Done'));
$panel->appendChild($form);
return $this->buildStandardPageResponse(
$panel,
array(
'title' => 'Batch Editor',
));
}
private function buildTransactions($actions, ManiphestTask $task) {
$value_map = array();
$type_map = array(
'add_comment' => ManiphestTransactionType::TYPE_NONE,
'assign' => ManiphestTransactionType::TYPE_OWNER,
'status' => ManiphestTransactionType::TYPE_STATUS,
'priority' => ManiphestTransactionType::TYPE_PRIORITY,
'add_project' => ManiphestTransactionType::TYPE_PROJECTS,
'remove_project' => ManiphestTransactionType::TYPE_PROJECTS,
);
$edge_edit_types = array(
'add_project' => true,
'remove_project' => true,
);
$xactions = array();
foreach ($actions as $action) {
if (empty($type_map[$action['action']])) {
throw new Exception("Unknown batch edit action '{$action}'!");
}
$type = $type_map[$action['action']];
// Figure out the current value, possibly after modifications by other
// batch actions of the same type. For example, if the user chooses to
// "Add Comment" twice, we should add both comments. More notably, if the
// user chooses "Remove Project..." and also "Add Project...", we should
// avoid restoring the removed project in the second transaction.
if (array_key_exists($type, $value_map)) {
$current = $value_map[$type];
} else {
switch ($type) {
case ManiphestTransactionType::TYPE_NONE:
$current = null;
break;
case ManiphestTransactionType::TYPE_OWNER:
$current = $task->getOwnerPHID();
break;
case ManiphestTransactionType::TYPE_STATUS:
$current = $task->getStatus();
break;
case ManiphestTransactionType::TYPE_PRIORITY:
$current = $task->getPriority();
break;
case ManiphestTransactionType::TYPE_PROJECTS:
$current = $task->getProjectPHIDs();
break;
}
}
// Check if the value is meaningful / provided, and normalize it if
// necessary. This discards, e.g., empty comments and empty owner
// changes.
$value = $action['value'];
switch ($type) {
case ManiphestTransactionType::TYPE_NONE:
if (!strlen($value)) {
continue 2;
}
break;
case ManiphestTransactionType::TYPE_OWNER:
if (empty($value)) {
continue 2;
}
$value = head($value);
if ($value === ManiphestTaskOwner::OWNER_UP_FOR_GRABS) {
$value = null;
}
break;
case ManiphestTransactionType::TYPE_PROJECTS:
if (empty($value)) {
continue 2;
}
break;
}
// If the edit doesn't change anything, go to the next action. This
// check is only valid for changes like "owner", "status", etc, not
// for edge edits, because we should still apply an edit like
// "Remove Projects: A, B" to a task with projects "A, B".
if (empty($edge_edit_types[$action['action']])) {
if ($value == $current) {
continue;
}
}
// Apply the value change; for most edits this is just replacement, but
// some need to merge the current and edited values (add/remove project).
switch ($type) {
case ManiphestTransactionType::TYPE_NONE:
if (strlen($current)) {
$value = $current."\n\n".$value;
}
break;
case ManiphestTransactionType::TYPE_PROJECTS:
$is_remove = ($action['action'] == 'remove_project');
$current = array_fill_keys($current, true);
$value = array_fill_keys($value, true);
$new = $current;
$did_something = false;
if ($is_remove) {
foreach ($value as $phid => $ignored) {
if (isset($new[$phid])) {
unset($new[$phid]);
$did_something = true;
}
}
} else {
foreach ($value as $phid => $ignored) {
if (empty($new[$phid])) {
$new[$phid] = true;
$did_something = true;
}
}
}
if (!$did_something) {
continue 2;
}
$value = array_keys($new);
break;
}
$value_map[$type] = $value;
}
$template = new ManiphestTransaction();
$template->setAuthorPHID($this->getRequest()->getUser()->getPHID());
// TODO: Set content source to "batch edit".
foreach ($value_map as $type => $value) {
$xaction = clone $template;
$xaction->setTransactionType($type);
switch ($type) {
case ManiphestTransactionType::TYPE_NONE:
$xaction->setComments($value);
break;
default:
$xaction->setNewValue($value);
break;
}
$xactions[] = $xaction;
}
return $xactions;
}
}
diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php
index 4b1bf10775..b8ce9c7e5d 100644
--- a/src/applications/maniphest/controller/ManiphestReportController.php
+++ b/src/applications/maniphest/controller/ManiphestReportController.php
@@ -1,763 +1,763 @@
<?php
/**
* @group maniphest
*/
final class ManiphestReportController extends ManiphestController {
private $view;
public function willProcessRequest(array $data) {
$this->view = idx($data, 'view');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
if ($request->isFormPost()) {
$uri = $request->getRequestURI();
$project = head($request->getArr('set_project'));
$project = nonempty($project, null);
$uri = $uri->alter('project', $project);
$window = $request->getStr('set_window');
$uri = $uri->alter('window', $window);
return id(new AphrontRedirectResponse())->setURI($uri);
}
$base_nav = $this->buildBaseSideNav();
$base_nav->selectFilter('report', 'report');
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI('/maniphest/report/'));
$nav->addLabel('Open Tasks');
$nav->addFilter('user', 'By User');
$nav->addFilter('project', 'By Project');
$nav->addLabel('Burnup');
$nav->addFilter('burn', 'Burnup Rate');
$this->view = $nav->selectFilter($this->view, 'user');
require_celerity_resource('maniphest-report-css');
switch ($this->view) {
case 'burn':
$core = $this->renderBurn();
break;
case 'user':
case 'project':
$core = $this->renderOpenTasks();
break;
default:
return new Aphront404Response();
}
$nav->appendChild($core);
$base_nav->appendChild($nav);
return $this->buildStandardPageResponse(
$base_nav,
array(
'title' => 'Maniphest Reports',
));
}
public function renderBurn() {
$request = $this->getRequest();
$user = $request->getUser();
$handle = null;
$project_phid = $request->getStr('project');
if ($project_phid) {
$phids = array($project_phid);
$handles = $this->loadViewerHandles($phids);
$handle = $handles[$project_phid];
}
$table = new ManiphestTransaction();
$conn = $table->establishConnection('r');
$joins = '';
if ($project_phid) {
$joins = qsprintf(
$conn,
'JOIN %T t ON x.taskID = t.id
JOIN %T p ON p.taskPHID = t.phid AND p.projectPHID = %s',
id(new ManiphestTask())->getTableName(),
id(new ManiphestTaskProject())->getTableName(),
$project_phid);
}
$data = queryfx_all(
$conn,
'SELECT x.oldValue, x.newValue, x.dateCreated FROM %T x %Q
WHERE transactionType = %s
ORDER BY x.dateCreated ASC',
$table->getTableName(),
$joins,
ManiphestTransactionType::TYPE_STATUS);
$stats = array();
$day_buckets = array();
$open_tasks = array();
foreach ($data as $key => $row) {
// NOTE: Hack to avoid json_decode().
$oldv = trim($row['oldValue'], '"');
$newv = trim($row['newValue'], '"');
$old_is_open = ($oldv === (string)ManiphestTaskStatus::STATUS_OPEN);
$new_is_open = ($newv === (string)ManiphestTaskStatus::STATUS_OPEN);
$is_open = ($new_is_open && !$old_is_open);
$is_close = ($old_is_open && !$new_is_open);
$data[$key]['_is_open'] = $is_open;
$data[$key]['_is_close'] = $is_close;
if (!$is_open && !$is_close) {
// This is either some kind of bogus event, or a resolution change
// (e.g., resolved -> invalid). Just skip it.
continue;
}
$day_bucket = phabricator_format_local_time(
$row['dateCreated'],
$user,
'Yz');
$day_buckets[$day_bucket] = $row['dateCreated'];
if (empty($stats[$day_bucket])) {
$stats[$day_bucket] = array(
'open' => 0,
'close' => 0,
);
}
$stats[$day_bucket][$is_close ? 'close' : 'open']++;
}
$template = array(
'open' => 0,
'close' => 0,
);
$rows = array();
$rowc = array();
$last_month = null;
$last_month_epoch = null;
$last_week = null;
$last_week_epoch = null;
$week = null;
$month = null;
$last = last_key($stats) - 1;
$period = $template;
foreach ($stats as $bucket => $info) {
$epoch = $day_buckets[$bucket];
$week_bucket = phabricator_format_local_time(
$epoch,
$user,
'YW');
if ($week_bucket != $last_week) {
if ($week) {
$rows[] = $this->formatBurnRow(
'Week of '.phabricator_date($last_week_epoch, $user),
$week);
$rowc[] = 'week';
}
$week = $template;
$last_week = $week_bucket;
$last_week_epoch = $epoch;
}
$month_bucket = phabricator_format_local_time(
$epoch,
$user,
'Ym');
if ($month_bucket != $last_month) {
if ($month) {
$rows[] = $this->formatBurnRow(
phabricator_format_local_time($last_month_epoch, $user, 'F, Y'),
$month);
$rowc[] = 'month';
}
$month = $template;
$last_month = $month_bucket;
$last_month_epoch = $epoch;
}
$rows[] = $this->formatBurnRow(phabricator_date($epoch, $user), $info);
$rowc[] = null;
$week['open'] += $info['open'];
$week['close'] += $info['close'];
$month['open'] += $info['open'];
$month['close'] += $info['close'];
$period['open'] += $info['open'];
$period['close'] += $info['close'];
}
if ($week) {
$rows[] = $this->formatBurnRow(
'Week To Date',
$week);
$rowc[] = 'week';
}
if ($month) {
$rows[] = $this->formatBurnRow(
'Month To Date',
$month);
$rowc[] = 'month';
}
$rows[] = $this->formatBurnRow(
'All Time',
$period);
$rowc[] = 'aggregate';
$rows = array_reverse($rows);
$rowc = array_reverse($rowc);
$table = new AphrontTableView($rows);
$table->setRowClasses($rowc);
$table->setHeaders(
array(
'Period',
'Opened',
'Closed',
'Change',
));
$table->setColumnClasses(
array(
'right wide',
'n',
'n',
'n',
));
if ($handle) {
$header = "Task Burn Rate for Project ".$handle->renderLink();
$caption = "<p>NOTE: This table reflects tasks <em>currently</em> in ".
"the project. If a task was opened in the past but added to ".
"the project recently, it is counted on the day it was ".
"opened, not the day it was categorized. If a task was part ".
"of this project in the past but no longer is, it is not ".
"counted at all.</p>";
} else {
$header = "Task Burn Rate for All Tasks";
$caption = null;
}
$panel = new AphrontPanelView();
$panel->setHeader($header);
$panel->setCaption($caption);
$panel->appendChild($table);
$tokens = array();
if ($handle) {
$tokens = array(
$handle->getPHID() => $handle->getFullName(),
);
}
$filter = $this->renderReportFilters($tokens, $has_window = false);
$id = celerity_generate_unique_node_id();
$chart = phutil_tag(
'div',
array(
'id' => $id,
'style' => 'border: 1px solid #6f6f6f; '.
'margin: 1em 2em; '.
'height: 400px; ',
),
'');
list($burn_x, $burn_y) = $this->buildSeries($data);
require_celerity_resource('raphael-core');
require_celerity_resource('raphael-g');
require_celerity_resource('raphael-g-line');
Javelin::initBehavior('line-chart', array(
'hardpoint' => $id,
'x' => array(
$burn_x,
),
'y' => array(
$burn_y,
),
'xformat' => 'epoch',
));
return array($filter, $chart, $panel);
}
private function renderReportFilters(array $tokens, $has_window) {
$request = $this->getRequest();
$user = $request->getUser();
$form = id(new AphrontFormView())
->setUser($user)
->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/searchproject/')
->setLabel('Project')
->setLimit(1)
->setName('set_project')
->setValue($tokens));
if ($has_window) {
list($window_str, $ignored, $window_error) = $this->getWindow();
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel('"Recently" Means')
->setName('set_window')
->setCaption(
'Configure the cutoff for the "Recently Closed" column.')
->setValue($window_str)
->setError($window_error));
}
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Filter By Project'));
$filter = new AphrontListFilterView();
$filter->appendChild($form);
return $filter;
}
private function buildSeries(array $data) {
$out = array();
$counter = 0;
foreach ($data as $row) {
$t = (int)$row['dateCreated'];
if ($row['_is_close']) {
--$counter;
$out[$t] = $counter;
} else if ($row['_is_open']) {
++$counter;
$out[$t] = $counter;
}
}
return array(array_keys($out), array_values($out));
}
private function formatBurnRow($label, $info) {
$delta = $info['open'] - $info['close'];
$fmt = number_format($delta);
if ($delta > 0) {
$fmt = '+'.$fmt;
$fmt = '<span class="red">'.$fmt.'</span>';
} else {
$fmt = '<span class="green">'.$fmt.'</span>';
}
return array(
$label,
number_format($info['open']),
number_format($info['close']),
$fmt);
}
public function renderOpenTasks() {
$request = $this->getRequest();
$user = $request->getUser();
$query = id(new ManiphestTaskQuery())
->withStatus(ManiphestTaskQuery::STATUS_OPEN);
$project_phid = $request->getStr('project');
$project_handle = null;
if ($project_phid) {
$phids = array($project_phid);
$handles = $this->loadViewerHandles($phids);
$project_handle = $handles[$project_phid];
$query->withAnyProjects($phids);
}
$tasks = $query->execute();
$recently_closed = $this->loadRecentlyClosedTasks();
$date = phabricator_date(time(), $user);
switch ($this->view) {
case 'user':
$result = mgroup($tasks, 'getOwnerPHID');
$leftover = idx($result, '', array());
unset($result['']);
$result_closed = mgroup($recently_closed, 'getOwnerPHID');
$leftover_closed = idx($result_closed, '', array());
unset($result_closed['']);
$base_link = '/maniphest/?users=';
$leftover_name = phutil_tag(
'a',
array(
'href' => $base_link.ManiphestTaskOwner::OWNER_UP_FOR_GRABS,
),
phutil_tag('em', array(), '(Up For Grabs)'));
$col_header = 'User';
$header = 'Open Tasks by User and Priority ('.$date.')';
break;
case 'project':
$result = array();
$leftover = array();
foreach ($tasks as $task) {
$phids = $task->getProjectPHIDs();
if ($phids) {
foreach ($phids as $project_phid) {
$result[$project_phid][] = $task;
}
} else {
$leftover[] = $task;
}
}
$result_closed = array();
$leftover_closed = array();
foreach ($recently_closed as $task) {
$phids = $task->getProjectPHIDs();
if ($phids) {
foreach ($phids as $project_phid) {
$result_closed[$project_phid][] = $task;
}
} else {
$leftover_closed[] = $task;
}
}
$base_link = '/maniphest/view/all/?projects=';
$leftover_name = phutil_tag(
'a',
array(
'href' => $base_link.ManiphestTaskOwner::PROJECT_NO_PROJECT,
),
phutil_tag('em', array(), '(No Project)'));
$col_header = 'Project';
$header = 'Open Tasks by Project and Priority ('.$date.')';
break;
}
$phids = array_keys($result);
$handles = $this->loadViewerHandles($phids);
$handles = msort($handles, 'getName');
$order = $request->getStr('order', 'name');
list($order, $reverse) = AphrontTableView::parseSort($order);
require_celerity_resource('aphront-tooltip-css');
Javelin::initBehavior('phabricator-tooltips', array());
$rows = array();
$pri_total = array();
foreach (array_merge($handles, array(null)) as $handle) {
if ($handle) {
if (($project_handle) &&
($project_handle->getPHID() == $handle->getPHID())) {
// If filtering by, e.g., "bugs", don't show a "bugs" group.
continue;
}
$tasks = idx($result, $handle->getPHID(), array());
$name = phutil_tag(
'a',
array(
'href' => $base_link.$handle->getPHID(),
),
$handle->getName());
$closed = idx($result_closed, $handle->getPHID(), array());
} else {
$tasks = $leftover;
$name = $leftover_name;
$closed = $leftover_closed;
}
$taskv = $tasks;
$tasks = mgroup($tasks, 'getPriority');
$row = array();
$row[] = $name;
$total = 0;
foreach (ManiphestTaskPriority::getTaskPriorityMap() as $pri => $label) {
$n = count(idx($tasks, $pri, array()));
if ($n == 0) {
$row[] = '-';
} else {
$row[] = number_format($n);
}
$total += $n;
}
$row[] = number_format($total);
list($link, $oldest_all) = $this->renderOldest($taskv);
$row[] = $link;
$normal_or_better = array();
foreach ($taskv as $id => $task) {
if ($task->getPriority() < ManiphestTaskPriority::PRIORITY_NORMAL) {
continue;
}
$normal_or_better[$id] = $task;
}
list($link, $oldest_pri) = $this->renderOldest($normal_or_better);
$row[] = $link;
if ($closed) {
$task_ids = implode(',', mpull($closed, 'getID'));
$row[] = phutil_tag(
'a',
array(
'href' => '/maniphest/view/custom/?s=oc&tasks='.$task_ids,
'target' => '_blank',
),
number_format(count($closed)));
} else {
$row[] = '-';
}
switch ($order) {
case 'total':
$row['sort'] = $total;
break;
case 'oldest-all':
$row['sort'] = $oldest_all;
break;
case 'oldest-pri':
$row['sort'] = $oldest_pri;
break;
case 'closed':
$row['sort'] = count($closed);
break;
case 'name':
default:
$row['sort'] = $handle ? $handle->getName() : '~';
break;
}
$rows[] = $row;
}
$rows = isort($rows, 'sort');
foreach ($rows as $k => $row) {
unset($rows[$k]['sort']);
}
if ($reverse) {
$rows = array_reverse($rows);
}
$cname = array($col_header);
$cclass = array('pri right wide');
$pri_map = ManiphestTaskPriority::getTaskBriefPriorityMap();
foreach ($pri_map as $pri => $label) {
$cname[] = $label;
$cclass[] = 'n';
}
$cname[] = 'Total';
$cclass[] = 'n';
- $cname[] = javelin_render_tag(
+ $cname[] = javelin_tag(
'span',
array(
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => 'Oldest open task.',
'size' => 200,
),
),
'Oldest (All)');
$cclass[] = 'n';
- $cname[] = javelin_render_tag(
+ $cname[] = javelin_tag(
'span',
array(
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => 'Oldest open task, excluding those with Low or Wishlist '.
'priority.',
'size' => 200,
),
),
'Oldest (Pri)');
$cclass[] = 'n';
list($ignored, $window_epoch) = $this->getWindow();
- $cname[] = javelin_render_tag(
+ $cname[] = javelin_tag(
'span',
array(
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => 'Closed after '.phabricator_datetime($window_epoch, $user),
'size' => 260
),
),
'Recently Closed');
$cclass[] = 'n';
$table = new AphrontTableView($rows);
$table->setHeaders($cname);
$table->setColumnClasses($cclass);
$table->makeSortable(
$request->getRequestURI(),
'order',
$order,
$reverse,
array(
'name',
null,
null,
null,
null,
null,
null,
'total',
'oldest-all',
'oldest-pri',
'closed',
));
$panel = new AphrontPanelView();
$panel->setHeader($header);
$panel->appendChild($table);
$tokens = array();
if ($project_handle) {
$tokens = array(
$project_handle->getPHID() => $project_handle->getFullName(),
);
}
$filter = $this->renderReportFilters($tokens, $has_window = true);
return array($filter, $panel);
}
/**
* Load all the tasks that have been recently closed.
*/
private function loadRecentlyClosedTasks() {
list($ignored, $window_epoch) = $this->getWindow();
$table = new ManiphestTask();
$xtable = new ManiphestTransaction();
$conn_r = $table->establishConnection('r');
$tasks = queryfx_all(
$conn_r,
'SELECT t.* FROM %T t JOIN %T x ON x.taskID = t.id
WHERE t.status != 0
AND x.oldValue IN (null, %s, %s)
AND x.newValue NOT IN (%s, %s)
AND t.dateModified >= %d
AND x.dateCreated >= %d',
$table->getTableName(),
$xtable->getTableName(),
// TODO: Gross. This table is not meant to be queried like this. Build
// real stats tables.
json_encode((int)ManiphestTaskStatus::STATUS_OPEN),
json_encode((string)ManiphestTaskStatus::STATUS_OPEN),
json_encode((int)ManiphestTaskStatus::STATUS_OPEN),
json_encode((string)ManiphestTaskStatus::STATUS_OPEN),
$window_epoch,
$window_epoch);
return id(new ManiphestTask())->loadAllFromArray($tasks);
}
/**
* Parse the "Recently Means" filter into:
*
* - A string representation, like "12 AM 7 days ago" (default);
* - a locale-aware epoch representation; and
* - a possible error.
*/
private function getWindow() {
$request = $this->getRequest();
$user = $request->getUser();
$window_str = $this->getRequest()->getStr('window', '12 AM 7 days ago');
$error = null;
$window_epoch = null;
// Do locale-aware parsing so that the user's timezone is assumed for
// time windows like "3 PM", rather than assuming the server timezone.
$timezone = new DateTimeZone($user->getTimezoneIdentifier());
try {
$date = new DateTime($window_str, $timezone);
$window_epoch = $date->format('U');
} catch (Exception $e) {
$error = 'Invalid';
$window_epoch = time() - (60 * 60 * 24 * 7);
}
// If the time ends up in the future, convert it to the corresponding time
// and equal distance in the past. This is so users can type "6 days" (which
// means "6 days from now") and get the behavior of "6 days ago", rather
// than no results (because the window epoch is in the future). This might
// be a little confusing because it casues "tomorrow" to mean "yesterday"
// and "2022" (or whatever) to mean "ten years ago", but these inputs are
// nonsense anyway.
if ($window_epoch > time()) {
$window_epoch = time() - ($window_epoch - time());
}
return array($window_str, $window_epoch, $error);
}
private function renderOldest(array $tasks) {
assert_instances_of($tasks, 'ManiphestTask');
$oldest = null;
foreach ($tasks as $id => $task) {
if (($oldest === null) ||
($task->getDateCreated() < $tasks[$oldest]->getDateCreated())) {
$oldest = $id;
}
}
if ($oldest === null) {
return array('-', 0);
}
$oldest = $tasks[$oldest];
$raw_age = (time() - $oldest->getDateCreated());
$age = number_format($raw_age / (24 * 60 * 60)).' d';
- $link = javelin_render_tag(
+ $link = javelin_tag(
'a',
array(
'href' => '/T'.$oldest->getID(),
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => 'T'.$oldest->getID().': '.$oldest->getTitle(),
),
'target' => '_blank',
),
- phutil_escape_html($age));
+ $age);
return array($link, $raw_age);
}
}
diff --git a/src/applications/maniphest/controller/ManiphestSavedQueryListController.php b/src/applications/maniphest/controller/ManiphestSavedQueryListController.php
index ba8fcb1e85..d6377c10b7 100644
--- a/src/applications/maniphest/controller/ManiphestSavedQueryListController.php
+++ b/src/applications/maniphest/controller/ManiphestSavedQueryListController.php
@@ -1,132 +1,132 @@
<?php
/**
* @group maniphest
*/
final class ManiphestSavedQueryListController extends ManiphestController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$nav = $this->buildBaseSideNav();
$queries = id(new ManiphestSavedQuery())->loadAllWhere(
'userPHID = %s ORDER BY name ASC',
$user->getPHID());
$default = null;
if ($request->isFormPost()) {
$new_default = null;
foreach ($queries as $query) {
if ($query->getID() == $request->getInt('default')) {
$new_default = $query;
}
}
if ($this->getDefaultQuery()) {
$this->getDefaultQuery()->setIsDefault(0)->save();
}
if ($new_default) {
$new_default->setIsDefault(1)->save();
}
return id(new AphrontRedirectResponse())->setURI('/maniphest/custom/');
}
$rows = array();
foreach ($queries as $query) {
if ($query->getIsDefault()) {
$default = $query;
}
$rows[] = array(
phutil_tag(
'input',
array(
'type' => 'radio',
'name' => 'default',
'value' => $query->getID(),
'checked' => ($query->getIsDefault() ? 'checked' : null),
)),
phutil_tag(
'a',
array(
'href' => '/maniphest/view/custom/?key='.$query->getQueryKey(),
),
$query->getName()),
phutil_tag(
'a',
array(
'href' => '/maniphest/custom/edit/'.$query->getID().'/',
'class' => 'grey small button',
),
'Edit'),
- javelin_render_tag(
+ javelin_tag(
'a',
array(
'href' => '/maniphest/custom/delete/'.$query->getID().'/',
'class' => 'grey small button',
'sigil' => 'workflow',
),
'Delete'),
);
}
$rows[] = array(
phutil_tag(
'input',
array(
'type' => 'radio',
'name' => 'default',
'value' => 0,
'checked' => ($default === null ? 'checked' : null),
)),
'<em>No Default</em>',
'',
'',
);
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
'Default',
'Name',
'Edit',
'Delete',
));
$table->setColumnClasses(
array(
'radio',
'wide pri',
'action',
'action',
));
$panel = new AphrontPanelView();
$panel->setHeader('Saved Custom Queries');
$panel->addButton(
phutil_tag(
'button',
array(),
'Save Default Query'));
$panel->appendChild($table);
$form = phabricator_render_form(
$user,
array(
'method' => 'POST',
'action' => $request->getRequestURI(),
),
$panel->render());
$nav->selectFilter('saved', 'saved');
$nav->appendChild($form);
return $this->buildStandardPageResponse(
$nav,
array(
'title' => 'Saved Queries',
));
}
}
diff --git a/src/applications/maniphest/controller/ManiphestTaskEditController.php b/src/applications/maniphest/controller/ManiphestTaskEditController.php
index b2b8fc8f0c..103e342842 100644
--- a/src/applications/maniphest/controller/ManiphestTaskEditController.php
+++ b/src/applications/maniphest/controller/ManiphestTaskEditController.php
@@ -1,555 +1,555 @@
<?php
/**
* @group maniphest
*/
final class ManiphestTaskEditController extends ManiphestController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$files = array();
$parent_task = null;
$template_id = null;
if ($this->id) {
$task = id(new ManiphestTask())->load($this->id);
if (!$task) {
return new Aphront404Response();
}
} else {
$task = new ManiphestTask();
$task->setPriority(ManiphestTaskPriority::getDefaultPriority());
$task->setAuthorPHID($user->getPHID());
// These allow task creation with defaults.
if (!$request->isFormPost()) {
$task->setTitle($request->getStr('title'));
$default_projects = $request->getStr('projects');
if ($default_projects) {
$task->setProjectPHIDs(explode(';', $default_projects));
}
}
$file_phids = $request->getArr('files', array());
if (!$file_phids) {
// Allow a single 'file' key instead, mostly since Mac OS X urlencodes
// square brackets in URLs when passed to 'open', so you can't 'open'
// a URL like '?files[]=xyz' and have PHP interpret it correctly.
$phid = $request->getStr('file');
if ($phid) {
$file_phids = array($phid);
}
}
if ($file_phids) {
$files = id(new PhabricatorFile())->loadAllWhere(
'phid IN (%Ls)',
$file_phids);
}
$template_id = $request->getInt('template');
// You can only have a parent task if you're creating a new task.
$parent_id = $request->getInt('parent');
if ($parent_id) {
$parent_task = id(new ManiphestTask())->load($parent_id);
}
}
$errors = array();
$e_title = true;
$extensions = ManiphestTaskExtensions::newExtensions();
$aux_fields = $extensions->getAuxiliaryFieldSpecifications();
if ($request->isFormPost()) {
$changes = array();
$new_title = $request->getStr('title');
$new_desc = $request->getStr('description');
$new_status = $request->getStr('status');
$workflow = '';
if ($task->getID()) {
if ($new_title != $task->getTitle()) {
$changes[ManiphestTransactionType::TYPE_TITLE] = $new_title;
}
if ($new_desc != $task->getDescription()) {
$changes[ManiphestTransactionType::TYPE_DESCRIPTION] = $new_desc;
}
if ($new_status != $task->getStatus()) {
$changes[ManiphestTransactionType::TYPE_STATUS] = $new_status;
}
} else {
$task->setTitle($new_title);
$task->setDescription($new_desc);
$changes[ManiphestTransactionType::TYPE_STATUS] =
ManiphestTaskStatus::STATUS_OPEN;
$workflow = 'create';
}
$owner_tokenizer = $request->getArr('assigned_to');
$owner_phid = reset($owner_tokenizer);
if (!strlen($new_title)) {
$e_title = pht('Required');
$errors[] = pht('Title is required.');
}
foreach ($aux_fields as $aux_field) {
$aux_field->setValueFromRequest($request);
if ($aux_field->isRequired() && !strlen($aux_field->getValue())) {
$errors[] = $aux_field->getLabel() . ' is required.';
$aux_field->setError('Required');
}
if (strlen($aux_field->getValue())) {
try {
$aux_field->validate();
} catch (Exception $e) {
$errors[] = $e->getMessage();
$aux_field->setError('Invalid');
}
}
}
if ($errors) {
$task->setPriority($request->getInt('priority'));
$task->setOwnerPHID($owner_phid);
$task->setCCPHIDs($request->getArr('cc'));
$task->setProjectPHIDs($request->getArr('projects'));
} else {
if ($request->getInt('priority') != $task->getPriority()) {
$changes[ManiphestTransactionType::TYPE_PRIORITY] =
$request->getInt('priority');
}
if ($owner_phid != $task->getOwnerPHID()) {
$changes[ManiphestTransactionType::TYPE_OWNER] = $owner_phid;
}
if ($request->getArr('cc') != $task->getCCPHIDs()) {
$changes[ManiphestTransactionType::TYPE_CCS] = $request->getArr('cc');
}
$new_proj_arr = $request->getArr('projects');
$new_proj_arr = array_values($new_proj_arr);
sort($new_proj_arr);
$cur_proj_arr = $task->getProjectPHIDs();
$cur_proj_arr = array_values($cur_proj_arr);
sort($cur_proj_arr);
if ($new_proj_arr != $cur_proj_arr) {
$changes[ManiphestTransactionType::TYPE_PROJECTS] = $new_proj_arr;
}
if ($files) {
$file_map = mpull($files, 'getPHID');
$file_map = array_fill_keys($file_map, array());
$changes[ManiphestTransactionType::TYPE_ATTACH] = array(
PhabricatorPHIDConstants::PHID_TYPE_FILE => $file_map,
);
}
$content_source = PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_WEB,
array(
'ip' => $request->getRemoteAddr(),
));
$template = new ManiphestTransaction();
$template->setAuthorPHID($user->getPHID());
$template->setContentSource($content_source);
$transactions = array();
foreach ($changes as $type => $value) {
$transaction = clone $template;
$transaction->setTransactionType($type);
$transaction->setNewValue($value);
$transactions[] = $transaction;
}
if ($aux_fields) {
$task->loadAndAttachAuxiliaryAttributes();
foreach ($aux_fields as $aux_field) {
$transaction = clone $template;
$transaction->setTransactionType(
ManiphestTransactionType::TYPE_AUXILIARY);
$aux_key = $aux_field->getAuxiliaryKey();
$transaction->setMetadataValue('aux:key', $aux_key);
$transaction->setNewValue($aux_field->getValueForStorage());
$transactions[] = $transaction;
}
}
if ($transactions) {
$is_new = !$task->getID();
$event = new PhabricatorEvent(
PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK,
array(
'task' => $task,
'new' => $is_new,
'transactions' => $transactions,
));
$event->setUser($user);
$event->setAphrontRequest($request);
PhutilEventEngine::dispatchEvent($event);
$task = $event->getValue('task');
$transactions = $event->getValue('transactions');
$editor = new ManiphestTransactionEditor();
$editor->setActor($user);
$editor->setAuxiliaryFields($aux_fields);
$editor->applyTransactions($task, $transactions);
$event = new PhabricatorEvent(
PhabricatorEventType::TYPE_MANIPHEST_DIDEDITTASK,
array(
'task' => $task,
'new' => $is_new,
'transactions' => $transactions,
));
$event->setUser($user);
$event->setAphrontRequest($request);
PhutilEventEngine::dispatchEvent($event);
}
if ($parent_task) {
id(new PhabricatorEdgeEditor())
->setActor($user)
->addEdge(
$parent_task->getPHID(),
PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK,
$task->getPHID())
->save();
$workflow = $parent_task->getID();
}
$redirect_uri = '/T'.$task->getID();
if ($workflow) {
$redirect_uri .= '?workflow='.$workflow;
}
return id(new AphrontRedirectResponse())
->setURI($redirect_uri);
}
} else {
if ($aux_fields) {
$task->loadAndAttachAuxiliaryAttributes();
foreach ($aux_fields as $aux_field) {
$aux_key = $aux_field->getAuxiliaryKey();
$value = $task->getAuxiliaryAttribute($aux_key);
$aux_field->setValueFromStorage($value);
}
}
if (!$task->getID()) {
$task->setCCPHIDs(array(
$user->getPHID(),
));
if ($template_id) {
$template_task = id(new ManiphestTask())->load($template_id);
if ($template_task) {
$task->setCCPHIDs($template_task->getCCPHIDs());
$task->setProjectPHIDs($template_task->getProjectPHIDs());
$task->setOwnerPHID($template_task->getOwnerPHID());
$task->setPriority($template_task->getPriority());
if ($aux_fields) {
$template_task->loadAndAttachAuxiliaryAttributes();
foreach ($aux_fields as $aux_field) {
if (!$aux_field->shouldCopyWhenCreatingSimilarTask()) {
continue;
}
$aux_key = $aux_field->getAuxiliaryKey();
$value = $template_task->getAuxiliaryAttribute($aux_key);
$aux_field->setValueFromStorage($value);
}
}
}
}
}
}
$phids = array_merge(
array($task->getOwnerPHID()),
$task->getCCPHIDs(),
$task->getProjectPHIDs());
if ($parent_task) {
$phids[] = $parent_task->getPHID();
}
$phids = array_filter($phids);
$phids = array_unique($phids);
$handles = $this->loadViewerHandles($phids);
$tvalues = mpull($handles, 'getFullName', 'getPHID');
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setErrors($errors);
$error_view->setTitle(pht('Form Errors'));
}
$priority_map = ManiphestTaskPriority::getTaskPriorityMap();
if ($task->getOwnerPHID()) {
$assigned_value = array(
$task->getOwnerPHID() => $handles[$task->getOwnerPHID()]->getFullName(),
);
} else {
$assigned_value = array();
}
if ($task->getCCPHIDs()) {
$cc_value = array_select_keys($tvalues, $task->getCCPHIDs());
} else {
$cc_value = array();
}
if ($task->getProjectPHIDs()) {
$projects_value = array_select_keys($tvalues, $task->getProjectPHIDs());
} else {
$projects_value = array();
}
$cancel_id = nonempty($task->getID(), $template_id);
if ($cancel_id) {
$cancel_uri = '/T'.$cancel_id;
} else {
$cancel_uri = '/maniphest/';
}
if ($task->getID()) {
$button_name = pht('Save Task');
$header_name = pht('Edit Task');
} else if ($parent_task) {
$cancel_uri = '/T'.$parent_task->getID();
$button_name = pht('Create Task');
$header_name = pht('Create New Subtask');
} else {
$button_name = pht('Create Task');
$header_name = pht('Create New Task');
}
require_celerity_resource('maniphest-task-edit-css');
$project_tokenizer_id = celerity_generate_unique_node_id();
$form = new AphrontFormView();
$form
->setUser($user)
->setAction($request->getRequestURI()->getPath())
->addHiddenInput('template', $template_id);
if ($parent_task) {
$form
->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Parent Task'))
->setValue($handles[$parent_task->getPHID()]->getFullName()))
->addHiddenInput('parent', $parent_task->getID());
}
$form
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel(pht('Title'))
->setName('title')
->setError($e_title)
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT)
->setValue($task->getTitle()));
if ($task->getID()) {
// Only show this in "edit" mode, not "create" mode, since creating a
// non-open task is kind of silly and it would just clutter up the
// "create" interface.
$form
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Status'))
->setName('status')
->setValue($task->getStatus())
->setOptions(ManiphestTaskStatus::getTaskStatusMap()));
}
$form
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Assigned To'))
->setName('assigned_to')
->setValue($assigned_value)
->setUser($user)
->setDatasource('/typeahead/common/users/')
->setLimit(1))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('CC'))
->setName('cc')
->setValue($cc_value)
->setUser($user)
->setDatasource('/typeahead/common/mailable/'))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Priority'))
->setName('priority')
->setOptions($priority_map)
->setValue($task->getPriority()))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Projects'))
->setName('projects')
->setValue($projects_value)
->setID($project_tokenizer_id)
->setCaption(
- javelin_render_tag(
+ javelin_tag(
'a',
array(
'href' => '/project/create/',
'mustcapture' => true,
'sigil' => 'project-create',
),
pht('Create New Project')))
->setDatasource('/typeahead/common/projects/'));
if ($aux_fields) {
foreach ($aux_fields as $aux_field) {
if ($aux_field->isRequired() &&
!$aux_field->getError() &&
!$aux_field->getValue()) {
$aux_field->setError(true);
}
$aux_control = $aux_field->renderControl();
$form->appendChild($aux_control);
}
}
require_celerity_resource('aphront-error-view-css');
Javelin::initBehavior('project-create', array(
'tokenizerID' => $project_tokenizer_id,
));
if ($files) {
$file_display = array();
foreach ($files as $file) {
$file_display[] = phutil_escape_html($file->getName());
}
$file_display = implode('<br />', $file_display);
$form->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Files'))
->setValue($file_display));
foreach ($files as $ii => $file) {
$form->addHiddenInput('files['.$ii.']', $file->getPHID());
}
}
$description_control = new PhabricatorRemarkupControl();
// "Upsell" creating tasks via email in create flows if the instance is
// configured for this awesomeness.
$email_create = PhabricatorEnv::getEnvConfig(
'metamta.maniphest.public-create-email');
if (!$task->getID() && $email_create) {
$email_hint = pht('You can also create tasks by sending an email to: ').
'<tt>'.phutil_escape_html($email_create).'</tt>';
$description_control->setCaption($email_hint);
}
$description_control
->setLabel(pht('Description'))
->setName('description')
->setID('description-textarea')
->setValue($task->getDescription())
->setUser($user);
$form
->appendChild($description_control);
if (!$task->getID()) {
$form
->appendChild(
id(new AphrontFormDragAndDropUploadControl())
->setLabel(pht('Attached Files'))
->setName('files')
->setActivatedClass('aphront-panel-view-drag-and-drop'));
}
$form
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
->setValue($button_name));
$panel = new AphrontPanelView();
$panel->setWidth(AphrontPanelView::WIDTH_FULL);
$panel->setHeader($header_name);
$panel->appendChild($form);
$panel->setNoBackground();
$description_preview_panel =
'<div class="aphront-panel-preview aphront-panel-preview-full">
<div class="maniphest-description-preview-header">
Description Preview
</div>
<div id="description-preview">
<div class="aphront-panel-preview-loading-text">
Loading preview...
</div>
</div>
</div>';
Javelin::initBehavior(
'maniphest-description-preview',
array(
'preview' => 'description-preview',
'textarea' => 'description-textarea',
'uri' => '/maniphest/task/descriptionpreview/',
));
if ($task->getID()) {
$page_objects = array( $task->getPHID() );
} else {
$page_objects = array();
}
return $this->buildApplicationPage(
array(
$error_view,
$panel,
$description_preview_panel
),
array(
'title' => $header_name,
'pageObjects' => $page_objects,
'device' => true
));
}
}
diff --git a/src/applications/maniphest/controller/ManiphestTaskListController.php b/src/applications/maniphest/controller/ManiphestTaskListController.php
index 1508690729..32952ce5af 100644
--- a/src/applications/maniphest/controller/ManiphestTaskListController.php
+++ b/src/applications/maniphest/controller/ManiphestTaskListController.php
@@ -1,926 +1,926 @@
<?php
/**
* @group maniphest
*/
final class ManiphestTaskListController extends ManiphestController {
const DEFAULT_PAGE_SIZE = 1000;
private $view;
public function willProcessRequest(array $data) {
$this->view = idx($data, 'view');
}
private function getArrToStrList($key) {
$arr = $this->getRequest()->getArr($key);
$arr = implode(',', $arr);
return nonempty($arr, null);
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
if ($request->isFormPost()) {
// Redirect to GET so URIs can be copy/pasted.
$task_ids = $request->getStr('set_tasks');
$task_ids = nonempty($task_ids, null);
$search_text = $request->getStr('set_search');
$min_priority = $request->getInt('set_lpriority');
$max_priority = $request->getInt('set_hpriority');
$uri = $request->getRequestURI()
->alter('users', $this->getArrToStrList('set_users'))
->alter('projects', $this->getArrToStrList('set_projects'))
->alter('aprojects', $this->getArrToStrList('set_aprojects'))
->alter('xprojects', $this->getArrToStrList('set_xprojects'))
->alter('owners', $this->getArrToStrList('set_owners'))
->alter('authors', $this->getArrToStrList('set_authors'))
->alter('lpriority', $min_priority)
->alter('hpriority', $max_priority)
->alter('tasks', $task_ids)
->alter('search', $search_text);
return id(new AphrontRedirectResponse())->setURI($uri);
}
$nav = $this->buildBaseSideNav();
$has_filter = array(
'action' => true,
'created' => true,
'subscribed' => true,
'triage' => true,
'projecttriage' => true,
'projectall' => true,
);
$query = null;
$key = $request->getStr('key');
if (!$key && !$this->view) {
if ($this->getDefaultQuery()) {
$key = $this->getDefaultQuery()->getQueryKey();
}
}
if ($key) {
$query = id(new PhabricatorSearchQuery())->loadOneWhere(
'queryKey = %s',
$key);
}
// If the user is running a saved query, load query parameters from that
// query. Otherwise, build a new query object from the HTTP request.
if ($query) {
$nav->selectFilter('Q:'.$query->getQueryKey(), 'custom');
$this->view = 'custom';
} else {
$this->view = $nav->selectFilter($this->view, 'action');
$query = $this->buildQueryFromRequest();
}
// Execute the query.
list($tasks, $handles, $total_count) = self::loadTasks($query);
// Extract information we need to render the filters from the query.
$search_text = $query->getParameter('fullTextSearch');
$user_phids = $query->getParameter('userPHIDs', array());
$task_ids = $query->getParameter('taskIDs', array());
$owner_phids = $query->getParameter('ownerPHIDs', array());
$author_phids = $query->getParameter('authorPHIDs', array());
$project_phids = $query->getParameter('projectPHIDs', array());
$any_project_phids = $query->getParameter(
'anyProjectPHIDs',
array());
$exclude_project_phids = $query->getParameter(
'excludeProjectPHIDs',
array());
$low_priority = $query->getParameter('lowPriority');
$high_priority = $query->getParameter('highPriority');
$page_size = $query->getParameter('limit');
$page = $query->getParameter('offset');
$q_status = $query->getParameter('status');
$q_group = $query->getParameter('group');
$q_order = $query->getParameter('order');
$form = id(new AphrontFormView())
->setUser($user)
->setAction(
$request->getRequestURI()
->alter('key', null)
->alter(
$this->getStatusRequestKey(),
$this->getStatusRequestValue($q_status))
->alter(
$this->getOrderRequestKey(),
$this->getOrderRequestValue($q_order))
->alter(
$this->getGroupRequestKey(),
$this->getGroupRequestValue($q_group)));
if (isset($has_filter[$this->view])) {
$tokens = array();
foreach ($user_phids as $phid) {
$tokens[$phid] = $handles[$phid]->getFullName();
}
$form->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/searchowner/')
->setName('set_users')
->setLabel('Users')
->setValue($tokens));
}
if ($this->view == 'custom') {
$form->appendChild(
id(new AphrontFormTextControl())
->setName('set_search')
->setLabel('Search')
->setValue($search_text)
);
$form->appendChild(
id(new AphrontFormTextControl())
->setName('set_tasks')
->setLabel('Task IDs')
->setValue(join(',', $task_ids))
);
$tokens = array();
foreach ($owner_phids as $phid) {
$tokens[$phid] = $handles[$phid]->getFullName();
}
$form->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/searchowner/')
->setName('set_owners')
->setLabel('Owners')
->setValue($tokens));
$tokens = array();
foreach ($author_phids as $phid) {
$tokens[$phid] = $handles[$phid]->getFullName();
}
$form->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/users/')
->setName('set_authors')
->setLabel('Authors')
->setValue($tokens));
}
$tokens = array();
foreach ($project_phids as $phid) {
$tokens[$phid] = $handles[$phid]->getFullName();
}
if ($this->view != 'projectall' && $this->view != 'projecttriage') {
$caption = null;
if ($this->view == 'custom') {
$caption = 'Find tasks in ALL of these projects ("AND" query).';
}
$form->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/searchproject/')
->setName('set_projects')
->setLabel('Projects')
->setCaption($caption)
->setValue($tokens));
}
if ($this->view == 'custom') {
$atokens = array();
foreach ($any_project_phids as $phid) {
$atokens[$phid] = $handles[$phid]->getFullName();
}
$form->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/projects/')
->setName('set_aprojects')
->setLabel('Any Projects')
->setCaption('Find tasks in ANY of these projects ("OR" query).')
->setValue($atokens));
$tokens = array();
foreach ($exclude_project_phids as $phid) {
$tokens[$phid] = $handles[$phid]->getFullName();
}
$form->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/projects/')
->setName('set_xprojects')
->setLabel('Exclude Projects')
->setCaption('Find tasks NOT in any of these projects.')
->setValue($tokens));
$priority = ManiphestTaskPriority::getLowestPriority();
if ($low_priority !== null) {
$priority = $low_priority;
}
$form->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Min Priority')
->setName('set_lpriority')
->setValue($priority)
->setOptions(array_reverse(
ManiphestTaskPriority::getTaskPriorityMap(), true)));
$priority = ManiphestTaskPriority::getHighestPriority();
if ($high_priority !== null) {
$priority = $high_priority;
}
$form->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Max Priority')
->setName('set_hpriority')
->setValue($priority)
->setOptions(ManiphestTaskPriority::getTaskPriorityMap()));
}
$form
->appendChild($this->renderStatusControl($q_status))
->appendChild($this->renderGroupControl($q_group))
->appendChild($this->renderOrderControl($q_order));
$submit = id(new AphrontFormSubmitControl())
->setValue('Filter Tasks');
// Only show "Save..." for novel queries which have some kind of query
// parameters set.
if ($this->view === 'custom'
&& empty($key)
&& $request->getRequestURI()->getQueryParams()) {
$submit->addCancelButton(
'/maniphest/custom/edit/?key='.$query->getQueryKey(),
'Save Custom Query...');
}
$form->appendChild($submit);
$create_uri = new PhutilURI('/maniphest/task/create/');
if ($project_phids) {
// If we have project filters selected, use them as defaults for task
// creation.
$create_uri->setQueryParam('projects', implode(';', $project_phids));
}
$filter = new AphrontListFilterView();
if (empty($key)) {
$filter->appendChild($form);
}
$nav->appendChild($filter);
$have_tasks = false;
foreach ($tasks as $group => $list) {
if (count($list)) {
$have_tasks = true;
break;
}
}
require_celerity_resource('maniphest-task-summary-css');
$list_container = new AphrontNullView();
$list_container->appendChild('<div class="maniphest-list-container">');
if (!$have_tasks) {
$list_container->appendChild(
'<h1 class="maniphest-task-group-header">'.
'No matching tasks.'.
'</h1>');
} else {
$pager = new AphrontPagerView();
$pager->setURI($request->getRequestURI(), 'offset');
$pager->setPageSize($page_size);
$pager->setOffset($page);
$pager->setCount($total_count);
$cur = ($pager->getOffset() + 1);
$max = min($pager->getOffset() + $page_size, $total_count);
$tot = $total_count;
$cur = number_format($cur);
$max = number_format($max);
$tot = number_format($tot);
$list_container->appendChild(
'<div class="maniphest-total-result-count">'.
"Displaying tasks {$cur} - {$max} of {$tot}.".
'</div>');
$selector = new AphrontNullView();
$group = $query->getParameter('group');
$order = $query->getParameter('order');
$is_draggable =
($group == 'priority') ||
($group == 'none' && $order == 'priority');
$lists = new AphrontNullView();
$lists->appendChild('<div class="maniphest-group-container">');
foreach ($tasks as $group => $list) {
$task_list = new ManiphestTaskListView();
$task_list->setShowBatchControls(true);
if ($is_draggable) {
$task_list->setShowSubpriorityControls(true);
}
$task_list->setUser($user);
$task_list->setTasks($list);
$task_list->setHandles($handles);
$count = number_format(count($list));
$header =
javelin_render_tag(
'h1',
array(
'class' => 'maniphest-task-group-header',
'sigil' => 'task-group',
'meta' => array(
'priority' => head($list)->getPriority(),
),
),
phutil_escape_html($group).' ('.$count.')');
$panel = new AphrontPanelView();
$panel->appendChild($header);
$panel->appendChild($task_list);
$panel->setNoBackground();
$lists->appendChild($panel);
}
$lists->appendChild('</div>');
$selector->appendChild($lists);
$selector->appendChild($this->renderBatchEditor($query));
$form_id = celerity_generate_unique_node_id();
$selector = phabricator_render_form(
$user,
array(
'method' => 'POST',
'action' => '/maniphest/batch/',
'id' => $form_id,
),
$selector->render());
$list_container->appendChild($selector);
$list_container->appendChild($pager);
Javelin::initBehavior(
'maniphest-subpriority-editor',
array(
'root' => $form_id,
'uri' => '/maniphest/subpriority/',
));
}
$list_container->appendChild('</div>');
$nav->appendChild($list_container);
$title = pht('Task List');
$crumbs = $this->buildApplicationCrumbs()
->addCrumb(
id(new PhabricatorCrumbView())
->setName($title))
->addAction(
id(new PhabricatorMenuItemView())
->setHref($this->getApplicationURI('/task/create/'))
->setName(pht('Create Task'))
->setIcon('create'));
$nav->setCrumbs($crumbs);
return $this->buildStandardPageResponse(
$nav,
array(
'title' => $title,
));
}
public static function loadTasks(PhabricatorSearchQuery $search_query) {
$any_project = false;
$search_text = $search_query->getParameter('fullTextSearch');
$user_phids = $search_query->getParameter('userPHIDs', array());
$task_ids = $search_query->getParameter('taskIDs', array());
$project_phids = $search_query->getParameter('projectPHIDs', array());
$any_project_phids = $search_query->getParameter(
'anyProjectPHIDs',
array());
$xproject_phids = $search_query->getParameter(
'excludeProjectPHIDs',
array());
$owner_phids = $search_query->getParameter('ownerPHIDs', array());
$author_phids = $search_query->getParameter('authorPHIDs', array());
$low_priority = $search_query->getParameter('lowPriority');
$low_priority = coalesce($low_priority,
ManiphestTaskPriority::getLowestPriority());
$high_priority = $search_query->getParameter('highPriority');
$high_priority = coalesce($high_priority,
ManiphestTaskPriority::getHighestPriority());
$query = new ManiphestTaskQuery();
$query->withTaskIDs($task_ids);
if ($project_phids) {
$query->withAllProjects($project_phids);
}
if ($xproject_phids) {
$query->withoutProjects($xproject_phids);
}
if ($any_project_phids) {
$query->withAnyProjects($any_project_phids);
}
if ($owner_phids) {
$query->withOwners($owner_phids);
}
if ($author_phids) {
$query->withAuthors($author_phids);
}
$status = $search_query->getParameter('status', 'all');
if (!empty($status['open']) && !empty($status['closed'])) {
$query->withStatus(ManiphestTaskQuery::STATUS_ANY);
} else if (!empty($status['open'])) {
$query->withStatus(ManiphestTaskQuery::STATUS_OPEN);
} else {
$query->withStatus(ManiphestTaskQuery::STATUS_CLOSED);
}
switch ($search_query->getParameter('view')) {
case 'action':
$query->withOwners($user_phids);
break;
case 'created':
$query->withAuthors($user_phids);
break;
case 'subscribed':
$query->withSubscribers($user_phids);
break;
case 'triage':
$query->withOwners($user_phids);
$query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
break;
case 'alltriage':
$query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
break;
case 'all':
break;
case 'projecttriage':
$query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
break;
case 'projectall':
break;
case 'custom':
$query->withPrioritiesBetween($low_priority, $high_priority);
break;
}
$query->withFullTextSearch($search_text);
$order_map = array(
'priority' => ManiphestTaskQuery::ORDER_PRIORITY,
'created' => ManiphestTaskQuery::ORDER_CREATED,
'title' => ManiphestTaskQuery::ORDER_TITLE,
);
$query->setOrderBy(
idx(
$order_map,
$search_query->getParameter('order'),
ManiphestTaskQuery::ORDER_MODIFIED));
$group_map = array(
'priority' => ManiphestTaskQuery::GROUP_PRIORITY,
'owner' => ManiphestTaskQuery::GROUP_OWNER,
'status' => ManiphestTaskQuery::GROUP_STATUS,
'project' => ManiphestTaskQuery::GROUP_PROJECT,
);
$query->setGroupBy(
idx(
$group_map,
$search_query->getParameter('group'),
ManiphestTaskQuery::GROUP_NONE));
$query->setCalculateRows(true);
$query->setLimit($search_query->getParameter('limit'));
$query->setOffset($search_query->getParameter('offset'));
$data = $query->execute();
$total_row_count = $query->getRowCount();
$project_group_phids = array();
if ($search_query->getParameter('group') == 'project') {
foreach ($data as $task) {
foreach ($task->getProjectPHIDs() as $phid) {
$project_group_phids[] = $phid;
}
}
}
$handle_phids = mpull($data, 'getOwnerPHID');
$handle_phids = array_merge(
$handle_phids,
$project_phids,
$user_phids,
$xproject_phids,
$owner_phids,
$author_phids,
$project_group_phids,
$any_project_phids,
array_mergev(mpull($data, 'getProjectPHIDs')));
$handles = id(new PhabricatorObjectHandleData($handle_phids))
->loadHandles();
switch ($search_query->getParameter('group')) {
case 'priority':
$data = mgroup($data, 'getPriority');
// If we have invalid priorities, they'll all map to "???". Merge
// arrays to prevent them from overwriting each other.
$out = array();
foreach ($data as $pri => $tasks) {
$out[ManiphestTaskPriority::getTaskPriorityName($pri)][] = $tasks;
}
foreach ($out as $pri => $tasks) {
$out[$pri] = array_mergev($tasks);
}
$data = $out;
break;
case 'status':
$data = mgroup($data, 'getStatus');
$out = array();
foreach ($data as $status => $tasks) {
$out[ManiphestTaskStatus::getTaskStatusFullName($status)] = $tasks;
}
$data = $out;
break;
case 'owner':
$data = mgroup($data, 'getOwnerPHID');
$out = array();
foreach ($data as $phid => $tasks) {
if ($phid) {
$out[$handles[$phid]->getFullName()] = $tasks;
} else {
$out['Unassigned'] = $tasks;
}
}
$data = $out;
ksort($data);
// Move "Unassigned" to the top of the list.
if (isset($data['Unassigned'])) {
$data = array('Unassigned' => $out['Unassigned']) + $out;
}
break;
case 'project':
$grouped = array();
foreach ($query->getGroupByProjectResults() as $project => $tasks) {
foreach ($tasks as $task) {
$group = $project ? $handles[$project]->getName() : 'No Project';
$grouped[$group][$task->getID()] = $task;
}
}
$data = $grouped;
ksort($data);
// Move "No Project" to the end of the list.
if (isset($data['No Project'])) {
$noproject = $data['No Project'];
unset($data['No Project']);
$data += array('No Project' => $noproject);
}
break;
default:
$data = array(
'Tasks' => $data,
);
break;
}
return array($data, $handles, $total_row_count);
}
private function renderBatchEditor(PhabricatorSearchQuery $search_query) {
Javelin::initBehavior(
'maniphest-batch-selector',
array(
'selectAll' => 'batch-select-all',
'selectNone' => 'batch-select-none',
'submit' => 'batch-select-submit',
'status' => 'batch-select-status-cell',
));
- $select_all = javelin_render_tag(
+ $select_all = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'class' => 'grey button',
'id' => 'batch-select-all',
),
'Select All');
- $select_none = javelin_render_tag(
+ $select_none = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'class' => 'grey button',
'id' => 'batch-select-none',
),
'Clear Selection');
$submit = phutil_tag(
'button',
array(
'id' => 'batch-select-submit',
'disabled' => 'disabled',
'class' => 'disabled',
),
"Batch Edit Selected Tasks \xC2\xBB");
- $export = javelin_render_tag(
+ $export = javelin_tag(
'a',
array(
'href' => '/maniphest/export/'.$search_query->getQueryKey().'/',
'class' => 'grey button',
),
'Export Tasks to Excel...');
return
'<div class="maniphest-batch-editor">'.
'<div class="batch-editor-header">Batch Task Editor</div>'.
'<table class="maniphest-batch-editor-layout">'.
'<tr>'.
'<td>'.
$select_all.
$select_none.
'</td>'.
'<td>'.
$export.
'</td>'.
'<td id="batch-select-status-cell">'.
'0 Selected Tasks'.
'</td>'.
'<td class="batch-select-submit-cell">'.$submit.'</td>'.
'</tr>'.
'</table>'.
'</table>';
}
private function buildQueryFromRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$status = $this->getStatusValueFromRequest();
$group = $this->getGroupValueFromRequest();
$order = $this->getOrderValueFromRequest();
$user_phids = $request->getStrList(
'users',
array($user->getPHID()));
if ($this->view == 'projecttriage' || $this->view == 'projectall') {
$projects = id(new PhabricatorProjectQuery())
->setViewer($user)
->withMemberPHIDs($user_phids)
->execute();
$any_project_phids = mpull($projects, 'getPHID');
} else {
$any_project_phids = $request->getStrList('aprojects');
}
$project_phids = $request->getStrList('projects');
$exclude_project_phids = $request->getStrList('xprojects');
$task_ids = $request->getStrList('tasks');
if ($task_ids) {
// We only need the integer portion of each task ID, so get rid of any
// non-numeric elements
$numeric_task_ids = array();
foreach ($task_ids as $task_id) {
$task_id = preg_replace('/[a-zA-Z]+/', '', $task_id);
if (!empty($task_id)) {
$numeric_task_ids[] = $task_id;
}
}
if (empty($numeric_task_ids)) {
$numeric_task_ids = array(null);
}
$task_ids = $numeric_task_ids;
}
$owner_phids = $request->getStrList('owners');
$author_phids = $request->getStrList('authors');
$search_string = $request->getStr('search');
$low_priority = $request->getInt('lpriority');
$high_priority = $request->getInt('hpriority');
$page = $request->getInt('offset');
$page_size = self::DEFAULT_PAGE_SIZE;
$query = new PhabricatorSearchQuery();
$query->setQuery('<<maniphest>>');
$query->setParameters(
array(
'fullTextSearch' => $search_string,
'view' => $this->view,
'userPHIDs' => $user_phids,
'projectPHIDs' => $project_phids,
'anyProjectPHIDs' => $any_project_phids,
'excludeProjectPHIDs' => $exclude_project_phids,
'ownerPHIDs' => $owner_phids,
'authorPHIDs' => $author_phids,
'taskIDs' => $task_ids,
'lowPriority' => $low_priority,
'highPriority' => $high_priority,
'group' => $group,
'order' => $order,
'offset' => $page,
'limit' => $page_size,
'status' => $status,
));
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$query->save();
unset($unguarded);
return $query;
}
/* -( Toggle Button Controls )---------------------------------------------
These are a giant mess since we have several different values: the request
key (GET param used in requests), the request value (short names used in
requests to keep URIs readable), and the query value (complex value stored in
the query).
*/
private function getStatusValueFromRequest() {
$map = $this->getStatusMap();
$val = $this->getRequest()->getStr($this->getStatusRequestKey());
return idx($map, $val, head($map));
}
private function getGroupValueFromRequest() {
$map = $this->getGroupMap();
$val = $this->getRequest()->getStr($this->getGroupRequestKey());
return idx($map, $val, head($map));
}
private function getOrderValueFromRequest() {
$map = $this->getOrderMap();
$val = $this->getRequest()->getStr($this->getOrderRequestKey());
return idx($map, $val, head($map));
}
private function getStatusRequestKey() {
return 's';
}
private function getGroupRequestKey() {
return 'g';
}
private function getOrderRequestKey() {
return 'o';
}
private function getStatusRequestValue($value) {
return array_search($value, $this->getStatusMap());
}
private function getGroupRequestValue($value) {
return array_search($value, $this->getGroupMap());
}
private function getOrderRequestValue($value) {
return array_search($value, $this->getOrderMap());
}
private function getStatusMap() {
return array(
'o' => array(
'open' => true,
),
'c' => array(
'closed' => true,
),
'oc' => array(
'open' => true,
'closed' => true,
),
);
}
private function getGroupMap() {
return array(
'p' => 'priority',
'o' => 'owner',
's' => 'status',
'j' => 'project',
'n' => 'none',
);
}
private function getOrderMap() {
return array(
'p' => 'priority',
'u' => 'updated',
'c' => 'created',
't' => 'title',
);
}
private function getStatusButtonMap() {
return array(
'o' => 'Open',
'c' => 'Closed',
'oc' => 'All',
);
}
private function getGroupButtonMap() {
return array(
'p' => 'Priority',
'o' => 'Owner',
's' => 'Status',
'j' => 'Project',
'n' => 'None',
);
}
private function getOrderButtonMap() {
return array(
'p' => 'Priority',
'u' => 'Updated',
'c' => 'Created',
't' => 'Title',
);
}
public function renderStatusControl($value) {
$request = $this->getRequest();
return id(new AphrontFormToggleButtonsControl())
->setLabel('Status')
->setValue($this->getStatusRequestValue($value))
->setBaseURI($request->getRequestURI(), $this->getStatusRequestKey())
->setButtons($this->getStatusButtonMap());
}
public function renderOrderControl($value) {
$request = $this->getRequest();
return id(new AphrontFormToggleButtonsControl())
->setLabel('Order')
->setValue($this->getOrderRequestValue($value))
->setBaseURI($request->getRequestURI(), $this->getOrderRequestKey())
->setButtons($this->getOrderButtonMap());
}
public function renderGroupControl($value) {
$request = $this->getRequest();
return id(new AphrontFormToggleButtonsControl())
->setLabel('Group')
->setValue($this->getGroupRequestValue($value))
->setBaseURI($request->getRequestURI(), $this->getGroupRequestKey())
->setButtons($this->getGroupButtonMap());
}
}
diff --git a/src/applications/maniphest/view/ManiphestTaskProjectsView.php b/src/applications/maniphest/view/ManiphestTaskProjectsView.php
index 5e63043238..358d3145bc 100644
--- a/src/applications/maniphest/view/ManiphestTaskProjectsView.php
+++ b/src/applications/maniphest/view/ManiphestTaskProjectsView.php
@@ -1,58 +1,58 @@
<?php
/**
* @group maniphest
*/
final class ManiphestTaskProjectsView extends ManiphestView {
private $handles;
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function render() {
require_celerity_resource('phabricator-project-tag-css');
$show = array_slice($this->handles, 0, 2);
$tags = array();
foreach ($show as $handle) {
$tags[] = phutil_tag(
'a',
array(
'href' => $handle->getURI(),
'class' => 'phabricator-project-tag',
),
- phutil_utf8_shorten($handle->getName(), 24));
+ phutil_utf8_shorten($handle->getName(), 24));
}
if (count($this->handles) > 2) {
require_celerity_resource('aphront-tooltip-css');
Javelin::initBehavior('phabricator-tooltips');
$all = array();
foreach ($this->handles as $handle) {
$all[] = $handle->getName();
}
- $tags[] = javelin_render_tag(
+ $tags[] = javelin_tag(
'span',
array(
'class' => 'phabricator-project-tag',
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => implode(', ', $all),
'size' => 200,
),
),
"\xE2\x80\xA6");
}
return implode("\n", $tags);
}
}
diff --git a/src/applications/maniphest/view/ManiphestTaskSummaryView.php b/src/applications/maniphest/view/ManiphestTaskSummaryView.php
index 075bee78dd..20c03b9fe0 100644
--- a/src/applications/maniphest/view/ManiphestTaskSummaryView.php
+++ b/src/applications/maniphest/view/ManiphestTaskSummaryView.php
@@ -1,138 +1,137 @@
<?php
/**
* @group maniphest
*/
final class ManiphestTaskSummaryView extends ManiphestView {
private $task;
private $handles;
private $showBatchControls;
private $showSubpriorityControls;
public function setTask(ManiphestTask $task) {
$this->task = $task;
return $this;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function setShowBatchControls($show_batch_controls) {
$this->showBatchControls = $show_batch_controls;
return $this;
}
public function setShowSubpriorityControls($show_subpriority_controls) {
$this->showSubpriorityControls = $show_subpriority_controls;
return $this;
}
public static function getPriorityClass($priority) {
$classes = array(
ManiphestTaskPriority::PRIORITY_UNBREAK_NOW => 'pri-unbreak',
ManiphestTaskPriority::PRIORITY_TRIAGE => 'pri-triage',
ManiphestTaskPriority::PRIORITY_HIGH => 'pri-high',
ManiphestTaskPriority::PRIORITY_NORMAL => 'pri-normal',
ManiphestTaskPriority::PRIORITY_LOW => 'pri-low',
ManiphestTaskPriority::PRIORITY_WISH => 'pri-wish',
);
return idx($classes, $priority);
}
public function render() {
if (!$this->user) {
throw new Exception("Call setUser() before rendering!");
}
$task = $this->task;
$handles = $this->handles;
require_celerity_resource('maniphest-task-summary-css');
$pri_class = self::getPriorityClass($task->getPriority());
$status_map = ManiphestTaskStatus::getTaskStatusMap();
$batch = null;
if ($this->showBatchControls) {
$batch =
'<td class="maniphest-task-batch">'.
- javelin_render_tag(
+ javelin_tag(
'input',
array(
'type' => 'checkbox',
'name' => 'batch[]',
'value' => $task->getID(),
'sigil' => 'maniphest-batch',
- ),
- null).
+ )).
'</td>';
}
$projects_view = new ManiphestTaskProjectsView();
$projects_view->setHandles(
array_select_keys(
$this->handles,
$task->getProjectPHIDs()));
$control_class = null;
$control_sigil = null;
if ($this->showSubpriorityControls) {
$control_class = 'maniphest-active-handle';
$control_sigil = 'maniphest-task-handle';
}
- $handle = javelin_render_tag(
+ $handle = javelin_tag(
'td',
array(
'class' => 'maniphest-task-handle '.$pri_class.' '.$control_class,
'sigil' => $control_sigil,
),
'');
return javelin_render_tag(
'table',
array(
'class' => 'maniphest-task-summary',
'sigil' => 'maniphest-task',
'meta' => array(
'taskID' => $task->getID(),
),
),
'<tr>'.
$handle.
$batch.
'<td class="maniphest-task-number">'.
'T'.$task->getID().
'</td>'.
'<td class="maniphest-task-status">'.
idx($status_map, $task->getStatus(), 'Unknown').
'</td>'.
'<td class="maniphest-task-owner">'.
($task->getOwnerPHID()
? $handles[$task->getOwnerPHID()]->renderLink()
: '<em>None</em>').
'</td>'.
'<td class="maniphest-task-name">'.
phutil_tag(
'a',
array(
'href' => '/T'.$task->getID(),
),
$task->getTitle()).
'</td>'.
'<td class="maniphest-task-projects">'.
$projects_view->render().
'</td>'.
'<td class="maniphest-task-updated">'.
phabricator_date($task->getDateModified(), $this->user).
'</td>'.
'</tr>');
}
}
diff --git a/src/applications/maniphest/view/ManiphestTransactionDetailView.php b/src/applications/maniphest/view/ManiphestTransactionDetailView.php
index 320b577c04..99d5aac688 100644
--- a/src/applications/maniphest/view/ManiphestTransactionDetailView.php
+++ b/src/applications/maniphest/view/ManiphestTransactionDetailView.php
@@ -1,820 +1,820 @@
<?php
/**
* @group maniphest
*/
final class ManiphestTransactionDetailView extends ManiphestView {
private $transactions;
private $handles;
private $markupEngine;
private $forEmail;
private $preview;
private $commentNumber;
private $rangeSpecification;
private $renderSummaryOnly;
private $renderFullSummary;
private $auxiliaryFields;
public function setAuxiliaryFields(array $fields) {
assert_instances_of($fields, 'ManiphestAuxiliaryFieldSpecification');
$this->auxiliaryFields = mpull($fields, null, 'getAuxiliaryKey');
return $this;
}
public function getAuxiliaryField($key) {
return idx($this->auxiliaryFields, $key);
}
public function setTransactionGroup(array $transactions) {
assert_instances_of($transactions, 'ManiphestTransaction');
$this->transactions = $transactions;
return $this;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function setMarkupEngine(PhabricatorMarkupEngine $engine) {
$this->markupEngine = $engine;
return $this;
}
public function setPreview($preview) {
$this->preview = $preview;
return $this;
}
public function setRenderSummaryOnly($render_summary_only) {
$this->renderSummaryOnly = $render_summary_only;
return $this;
}
public function getRenderSummaryOnly() {
return $this->renderSummaryOnly;
}
public function setRenderFullSummary($render_full_summary) {
$this->renderFullSummary = $render_full_summary;
return $this;
}
public function getRenderFullSummary() {
return $this->renderFullSummary;
}
public function setCommentNumber($comment_number) {
$this->commentNumber = $comment_number;
return $this;
}
public function setRangeSpecification($range) {
$this->rangeSpecification = $range;
return $this;
}
public function getRangeSpecification() {
return $this->rangeSpecification;
}
public function renderForEmail($with_date) {
$this->forEmail = true;
$transaction = reset($this->transactions);
$author = $this->renderHandles(array($transaction->getAuthorPHID()));
$action = null;
$descs = array();
$comments = null;
foreach ($this->transactions as $transaction) {
list($verb, $desc, $classes) = $this->describeAction($transaction);
if ($desc === null) {
continue;
}
if ($action === null) {
$action = $verb;
}
$desc = $author.' '.$desc.'.';
if ($with_date) {
// NOTE: This is going into a (potentially multi-recipient) email so
// we can't use a single user's timezone preferences. Use the server's
// instead, but make the timezone explicit.
$datetime = date('M jS \a\t g:i A T', $transaction->getDateCreated());
$desc = "On {$datetime}, {$desc}";
}
$descs[] = $desc;
if ($transaction->hasComments()) {
$comments = $transaction->getComments();
}
}
$descs = implode("\n", $descs);
if ($comments) {
$descs .= "\n".$comments;
}
foreach ($this->transactions as $transaction) {
$supplemental = $this->renderSupplementalInfoForEmail($transaction);
if ($supplemental) {
$descs .= "\n\n".$supplemental;
}
}
$this->forEmail = false;
return array($action, $descs);
}
public function render() {
if (!$this->user) {
throw new Exception("Call setUser() before render()!");
}
$handles = $this->handles;
$transactions = $this->transactions;
require_celerity_resource('maniphest-transaction-detail-css');
$comment_transaction = null;
foreach ($this->transactions as $transaction) {
if ($transaction->hasComments()) {
$comment_transaction = $transaction;
break;
}
}
$any_transaction = reset($transactions);
$author = $this->handles[$any_transaction->getAuthorPHID()];
$more_classes = array();
$descs = array();
foreach ($transactions as $transaction) {
list($verb, $desc, $classes) = $this->describeAction($transaction);
if ($desc === null) {
continue;
}
$more_classes = array_merge($more_classes, $classes);
$full_summary = null;
if ($this->getRenderFullSummary()) {
$full_summary = $this->renderFullSummary($transaction);
}
$descs[] = javelin_render_tag(
'div',
array(
'sigil' => 'maniphest-transaction-description',
),
$author->renderLink().' '.$desc.'.'.$full_summary);
}
if ($this->getRenderSummaryOnly()) {
return implode("\n", $descs);
}
if ($comment_transaction && $comment_transaction->hasComments()) {
$comment_block = $this->markupEngine->getOutput(
$comment_transaction,
ManiphestTransaction::MARKUP_FIELD_BODY);
$comment_block =
'<div class="maniphest-transaction-comments phabricator-remarkup">'.
$comment_block.
'</div>';
} else {
$comment_block = null;
}
$source_transaction = nonempty($comment_transaction, $any_transaction);
$xaction_view = id(new PhabricatorTransactionView())
->setUser($this->user)
->setImageURI($author->getImageURI())
->setContentSource($source_transaction->getContentSource())
->setActions($descs);
foreach ($more_classes as $class) {
$xaction_view->addClass($class);
}
if ($this->preview) {
$xaction_view->setIsPreview($this->preview);
} else {
$xaction_view->setEpoch($any_transaction->getDateCreated());
if ($this->commentNumber) {
$anchor_name = 'comment-'.$this->commentNumber;
$anchor_text =
'T'.$any_transaction->getTaskID().
'#'.$this->commentNumber;
$xaction_view->setAnchor($anchor_name, $anchor_text);
}
}
$xaction_view->appendChild($comment_block);
return $xaction_view->render();
}
private function renderSupplementalInfoForEmail($transaction) {
$handles = $this->handles;
$type = $transaction->getTransactionType();
$new = $transaction->getNewValue();
$old = $transaction->getOldValue();
switch ($type) {
case ManiphestTransactionType::TYPE_DESCRIPTION:
return "NEW DESCRIPTION\n ".trim($new)."\n\n".
"PREVIOUS DESCRIPTION\n ".trim($old);
case ManiphestTransactionType::TYPE_ATTACH:
$old_raw = nonempty($old, array());
$new_raw = nonempty($new, array());
$attach_types = array(
PhabricatorPHIDConstants::PHID_TYPE_DREV,
PhabricatorPHIDConstants::PHID_TYPE_FILE,
);
foreach ($attach_types as $attach_type) {
$old = array_keys(idx($old_raw, $attach_type, array()));
$new = array_keys(idx($new_raw, $attach_type, array()));
if ($old != $new) {
break;
}
}
$added = array_diff($new, $old);
if (!$added) {
break;
}
$links = array();
foreach (array_select_keys($handles, $added) as $handle) {
$links[] = ' '.PhabricatorEnv::getProductionURI($handle->getURI());
}
$links = implode("\n", $links);
switch ($attach_type) {
case PhabricatorPHIDConstants::PHID_TYPE_DREV:
$title = 'ATTACHED REVISIONS';
break;
case PhabricatorPHIDConstants::PHID_TYPE_FILE:
$title = 'ATTACHED FILES';
break;
}
return $title."\n".$links;
case ManiphestTransactionType::TYPE_EDGE:
$add = array_diff_key($new, $old);
if (!$add) {
break;
}
$links = array();
foreach ($add as $phid => $ignored) {
$handle = $handles[$phid];
$links[] = ' '.PhabricatorEnv::getProductionURI($handle->getURI());
}
$links = implode("\n", $links);
$edge_type = $transaction->getMetadataValue('edge:type');
$title = $this->getEdgeEmailTitle($edge_type, $add);
return $title."\n".$links;
default:
break;
}
return null;
}
private function describeAction($transaction) {
$verb = null;
$desc = null;
$classes = array();
$handles = $this->handles;
$type = $transaction->getTransactionType();
$author_phid = $transaction->getAuthorPHID();
$new = $transaction->getNewValue();
$old = $transaction->getOldValue();
switch ($type) {
case ManiphestTransactionType::TYPE_TITLE:
$verb = 'Retitled';
$desc = 'changed the title from '.$this->renderString($old).
' to '.$this->renderString($new);
break;
case ManiphestTransactionType::TYPE_DESCRIPTION:
$verb = 'Edited';
if ($this->forEmail || $this->getRenderFullSummary()) {
$desc = 'updated the task description';
} else {
$desc = 'updated the task description; '.
$this->renderExpandLink($transaction);
}
break;
case ManiphestTransactionType::TYPE_NONE:
$verb = 'Commented On';
$desc = 'added a comment';
break;
case ManiphestTransactionType::TYPE_OWNER:
if ($transaction->getAuthorPHID() == $new) {
$verb = 'Claimed';
$desc = 'claimed this task';
$classes[] = 'claimed';
} else if (!$new) {
$verb = 'Up For Grabs';
$desc = 'placed this task up for grabs';
$classes[] = 'upforgrab';
} else if (!$old) {
$verb = 'Assigned';
$desc = 'assigned this task to '.$this->renderHandles(array($new));
$classes[] = 'assigned';
} else {
$verb = 'Reassigned';
$desc = 'reassigned this task from '.
$this->renderHandles(array($old)).
' to '.
$this->renderHandles(array($new));
$classes[] = 'reassigned';
}
break;
case ManiphestTransactionType::TYPE_CCS:
$added = array_diff($new, $old);
$removed = array_diff($old, $new);
// can only add in preview so just show placeholder if nothing to add
if ($this->preview && empty($added)) {
$verb = 'Changed CC';
$desc = 'changed CCs..';
break;
}
if ($added && !$removed) {
$verb = 'Added CC';
if (count($added) == 1) {
$desc = 'added '.$this->renderHandles($added).' to CC';
} else {
$desc = 'added CCs: '.$this->renderHandles($added);
}
} else if ($removed && !$added) {
$verb = 'Removed CC';
if (count($removed) == 1) {
$desc = 'removed '.$this->renderHandles($removed).' from CC';
} else {
$desc = 'removed CCs: '.$this->renderHandles($removed);
}
} else {
$verb = 'Changed CC';
$desc = 'changed CCs, added: '.$this->renderHandles($added).'; '.
'removed: '.$this->renderHandles($removed);
}
break;
case ManiphestTransactionType::TYPE_EDGE:
$edge_type = $transaction->getMetadataValue('edge:type');
$add = array_diff_key($new, $old);
$rem = array_diff_key($old, $new);
if ($add && !$rem) {
$verb = $this->getEdgeAddVerb($edge_type);
$desc = $this->getEdgeAddList($edge_type, $add);
} else if ($rem && !$add) {
$verb = $this->getEdgeRemVerb($edge_type);
$desc = $this->getEdgeRemList($edge_type, $rem);
} else {
$verb = $this->getEdgeEditVerb($edge_type);
$desc = $this->getEdgeEditList($edge_type, $add, $rem);
}
break;
case ManiphestTransactionType::TYPE_PROJECTS:
$added = array_diff($new, $old);
$removed = array_diff($old, $new);
// can only add in preview so just show placeholder if nothing to add
if ($this->preview && empty($added)) {
$verb = 'Changed Projects';
$desc = 'changed projects..';
break;
}
if ($added && !$removed) {
$verb = 'Added Project';
if (count($added) == 1) {
$desc = 'added project '.$this->renderHandles($added);
} else {
$desc = 'added projects: '.$this->renderHandles($added);
}
} else if ($removed && !$added) {
$verb = 'Removed Project';
if (count($removed) == 1) {
$desc = 'removed project '.$this->renderHandles($removed);
} else {
$desc = 'removed projects: '.$this->renderHandles($removed);
}
} else {
$verb = 'Changed Projects';
$desc = 'changed projects, added: '.$this->renderHandles($added).'; '.
'removed: '.$this->renderHandles($removed);
}
break;
case ManiphestTransactionType::TYPE_STATUS:
if ($new == ManiphestTaskStatus::STATUS_OPEN) {
if ($old) {
$verb = 'Reopened';
$desc = 'reopened this task';
$classes[] = 'reopened';
} else {
$verb = 'Created';
$desc = 'created this task';
$classes[] = 'created';
}
} else if ($new == ManiphestTaskStatus::STATUS_CLOSED_SPITE) {
$verb = 'Spited';
$desc = 'closed this task out of spite';
$classes[] = 'spited';
} else if ($new == ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE) {
$verb = 'Merged';
$desc = 'closed this task as a duplicate';
$classes[] = 'duplicate';
} else {
$verb = 'Closed';
$full = idx(ManiphestTaskStatus::getTaskStatusMap(), $new, '???');
$desc = 'closed this task as "'.$full.'"';
$classes[] = 'closed';
}
break;
case ManiphestTransactionType::TYPE_PRIORITY:
$old_name = ManiphestTaskPriority::getTaskPriorityName($old);
$new_name = ManiphestTaskPriority::getTaskPriorityName($new);
if ($old == ManiphestTaskPriority::PRIORITY_TRIAGE) {
$verb = 'Triaged';
$desc = 'triaged this task as "'.$new_name.'" priority';
} else if ($old > $new) {
$verb = 'Lowered Priority';
$desc = 'lowered the priority of this task from "'.$old_name.'" to '.
'"'.$new_name.'"';
} else {
$verb = 'Raised Priority';
$desc = 'raised the priority of this task from "'.$old_name.'" to '.
'"'.$new_name.'"';
}
if ($new == ManiphestTaskPriority::PRIORITY_UNBREAK_NOW) {
$classes[] = 'unbreaknow';
}
break;
case ManiphestTransactionType::TYPE_ATTACH:
if ($this->preview) {
$verb = 'Changed Attached';
$desc = 'changed attachments..';
break;
}
$old_raw = nonempty($old, array());
$new_raw = nonempty($new, array());
foreach (array(
PhabricatorPHIDConstants::PHID_TYPE_DREV,
PhabricatorPHIDConstants::PHID_TYPE_TASK,
PhabricatorPHIDConstants::PHID_TYPE_FILE) as $attach_type) {
$old = array_keys(idx($old_raw, $attach_type, array()));
$new = array_keys(idx($new_raw, $attach_type, array()));
if ($old != $new) {
break;
}
}
$added = array_diff($new, $old);
$removed = array_diff($old, $new);
$add_desc = $this->renderHandles($added);
$rem_desc = $this->renderHandles($removed);
if ($added && !$removed) {
$verb = 'Attached';
$desc =
'attached '.
$this->getAttachName($attach_type, count($added)).': '.
$add_desc;
} else if ($removed && !$added) {
$verb = 'Detached';
$desc =
'detached '.
$this->getAttachName($attach_type, count($removed)).': '.
$rem_desc;
} else {
$verb = 'Changed Attached';
$desc =
'changed attached '.
$this->getAttachName($attach_type, count($added) + count($removed)).
', added: '.$add_desc.'; '.
'removed: '.$rem_desc;
}
break;
case ManiphestTransactionType::TYPE_AUXILIARY:
$aux_key = $transaction->getMetadataValue('aux:key');
$aux_field = $this->getAuxiliaryField($aux_key);
$verb = null;
if ($aux_field) {
$verb = $aux_field->renderTransactionEmailVerb($transaction);
}
if ($verb === null) {
if ($old === null) {
$verb = "Set Field";
} else if ($new === null) {
$verb = "Removed Field";
} else {
$verb = "Updated Field";
}
}
$desc = null;
if ($aux_field) {
$use_field = $aux_field;
} else {
$use_field = id(new ManiphestAuxiliaryFieldDefaultSpecification())
->setFieldType(
ManiphestAuxiliaryFieldDefaultSpecification::TYPE_STRING);
}
$desc = $use_field->renderTransactionDescription(
$transaction,
$this->forEmail
? ManiphestAuxiliaryFieldSpecification::RENDER_TARGET_TEXT
: ManiphestAuxiliaryFieldSpecification::RENDER_TARGET_HTML);
break;
default:
return array($type, ' brazenly '.$type."'d", $classes);
}
return array($verb, $desc, $classes);
}
private function renderFullSummary($transaction) {
switch ($transaction->getTransactionType()) {
case ManiphestTransactionType::TYPE_DESCRIPTION:
$id = $transaction->getID();
$old_text = wordwrap($transaction->getOldValue(), 80);
$new_text = wordwrap($transaction->getNewValue(), 80);
$engine = new PhabricatorDifferenceEngine();
$changeset = $engine->generateChangesetFromFileContent($old_text,
$new_text);
$whitespace_mode = DifferentialChangesetParser::WHITESPACE_SHOW_ALL;
$parser = new DifferentialChangesetParser();
$parser->setChangeset($changeset);
$parser->setRenderingReference($id);
$parser->setMarkupEngine($this->markupEngine);
$parser->setWhitespaceMode($whitespace_mode);
$spec = $this->getRangeSpecification();
list($range_s, $range_e, $mask) =
DifferentialChangesetParser::parseRangeSpecification($spec);
$output = $parser->render($range_s, $range_e, $mask);
return $output;
}
return null;
}
private function renderExpandLink($transaction) {
$id = $transaction->getID();
Javelin::initBehavior('maniphest-transaction-expand');
- return javelin_render_tag(
+ return javelin_tag(
'a',
array(
'href' => '/maniphest/task/descriptionchange/'.$id.'/',
'sigil' => 'maniphest-expand-transaction',
'mustcapture' => true,
),
'show details');
}
private function renderHandles($phids, $full = false) {
$links = array();
foreach ($phids as $phid) {
if ($this->forEmail) {
if ($full) {
$links[] = $this->handles[$phid]->getFullName();
} else {
$links[] = $this->handles[$phid]->getName();
}
} else {
$links[] = $this->handles[$phid]->renderLink();
}
}
return implode(', ', $links);
}
private function renderString($string) {
if ($this->forEmail) {
return '"'.$string.'"';
} else {
return '"'.phutil_escape_html($string).'"';
}
}
/* -( Strings )------------------------------------------------------------ */
/**
* @task strings
*/
private function getAttachName($attach_type, $count) {
switch ($attach_type) {
case PhabricatorPHIDConstants::PHID_TYPE_DREV:
return pht('Differential Revision(s)', $count);
case PhabricatorPHIDConstants::PHID_TYPE_FILE:
return pht('file(s)', $count);
case PhabricatorPHIDConstants::PHID_TYPE_TASK:
return pht('Maniphest Task(s)', $count);
}
}
/**
* @task strings
*/
private function getEdgeEmailTitle($type, array $list) {
$count = count($list);
switch ($type) {
case PhabricatorEdgeConfig::TYPE_TASK_HAS_RELATED_DREV:
return pht('DIFFERENTIAL %d REVISION(S)', $count);
case PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK:
return pht('DEPENDS ON %d TASK(S)', $count);
case PhabricatorEdgeConfig::TYPE_TASK_DEPENDED_ON_BY_TASK:
return pht('DEPENDENT %d TASK(s)', $count);
case PhabricatorEdgeConfig::TYPE_TASK_HAS_COMMIT:
return pht('ATTACHED %d COMMIT(S)', $count);
default:
return pht('ATTACHED %d OBJECT(S)', $count);
}
}
/**
* @task strings
*/
private function getEdgeAddVerb($type) {
switch ($type) {
case PhabricatorEdgeConfig::TYPE_TASK_HAS_RELATED_DREV:
return pht('Added Revision');
case PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK:
return pht('Added Dependency');
case PhabricatorEdgeConfig::TYPE_TASK_DEPENDED_ON_BY_TASK:
return pht('Added Dependent Task');
case PhabricatorEdgeConfig::TYPE_TASK_HAS_COMMIT:
return pht('Added Commit');
default:
return pht('Added Object');
}
}
/**
* @task strings
*/
private function getEdgeRemVerb($type) {
switch ($type) {
case PhabricatorEdgeConfig::TYPE_TASK_HAS_RELATED_DREV:
return pht('Removed Revision');
case PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK:
return pht('Removed Dependency');
case PhabricatorEdgeConfig::TYPE_TASK_DEPENDED_ON_BY_TASK:
return pht('Removed Dependent Task');
case PhabricatorEdgeConfig::TYPE_TASK_HAS_COMMIT:
return pht('Removed Commit');
default:
return pht('Removed Object');
}
}
/**
* @task strings
*/
private function getEdgeEditVerb($type) {
switch ($type) {
case PhabricatorEdgeConfig::TYPE_TASK_HAS_RELATED_DREV:
return pht('Changed Revisions');
case PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK:
return pht('Changed Dependencies');
case PhabricatorEdgeConfig::TYPE_TASK_DEPENDED_ON_BY_TASK:
return pht('Changed Dependent Tasks');
case PhabricatorEdgeConfig::TYPE_TASK_HAS_COMMIT:
return pht('Changed Commits');
default:
return pht('Changed Objects');
}
}
/**
* @task strings
*/
private function getEdgeAddList($type, array $add) {
$list = $this->renderHandles(array_keys($add), $full = true);
$count = count($add);
switch ($type) {
case PhabricatorEdgeConfig::TYPE_TASK_HAS_RELATED_DREV:
return pht('added %d revision(s): %s', $count, $list);
case PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK:
return pht('added %d dependencie(s): %s', $count, $list);
case PhabricatorEdgeConfig::TYPE_TASK_DEPENDED_ON_BY_TASK:
return pht('added %d dependent task(s): %s', $count, $list);
case PhabricatorEdgeConfig::TYPE_TASK_HAS_COMMIT:
return pht('added %d commit(s): %s', $count, $list);
default:
return pht('added %d object(s): %s', $count, $list);
}
}
/**
* @task strings
*/
private function getEdgeRemList($type, array $rem) {
$list = $this->renderHandles(array_keys($rem), $full = true);
$count = count($rem);
switch ($type) {
case PhabricatorEdgeConfig::TYPE_TASK_HAS_RELATED_DREV:
return pht('removed %d revision(s): %s', $count, $list);
case PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK:
return pht('removed %d dependencie(s): %s', $count, $list);
case PhabricatorEdgeConfig::TYPE_TASK_DEPENDED_ON_BY_TASK:
return pht('removed %d dependent task(s): %s', $count, $list);
case PhabricatorEdgeConfig::TYPE_TASK_HAS_COMMIT:
return pht('removed %d commit(s): %s', $count, $list);
default:
return pht('removed %d object(s): %s', $count, $list);
}
}
/**
* @task strings
*/
private function getEdgeEditList($type, array $add, array $rem) {
$add_list = $this->renderHandles(array_keys($add), $full = true);
$rem_list = $this->renderHandles(array_keys($rem), $full = true);
$add_count = count($add_list);
$rem_count = count($rem_list);
switch ($type) {
case PhabricatorEdgeConfig::TYPE_TASK_HAS_RELATED_DREV:
return pht(
'changed %d revision(s), added %d: %s; removed %d: %s',
$add_count + $rem_count,
$add_count,
$add_list,
$rem_count,
$rem_list);
case PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK:
return pht(
'changed %d dependencie(s), added %d: %s; removed %d: %s',
$add_count + $rem_count,
$add_count,
$add_list,
$rem_count,
$rem_list);
case PhabricatorEdgeConfig::TYPE_TASK_DEPENDED_ON_BY_TASK:
return pht(
'changed %d dependent task(s), added %d: %s; removed %d: %s',
$add_count + $rem_count,
$add_count,
$add_list,
$rem_count,
$rem_list);
case PhabricatorEdgeConfig::TYPE_TASK_HAS_COMMIT:
return pht(
'changed %d commit(s), added %d: %s; removed %d: %s',
$add_count + $rem_count,
$add_count,
$add_list,
$rem_count,
$rem_list);
default:
return pht(
'changed %d object(s), added %d: %s; removed %d: %s',
$add_count + $rem_count,
$add_count,
$add_list,
$rem_count,
$rem_list);
}
}
}
diff --git a/src/applications/notification/controller/PhabricatorNotificationListController.php b/src/applications/notification/controller/PhabricatorNotificationListController.php
index 3c1bfeedf6..739a70f3ae 100644
--- a/src/applications/notification/controller/PhabricatorNotificationListController.php
+++ b/src/applications/notification/controller/PhabricatorNotificationListController.php
@@ -1,84 +1,84 @@
<?php
final class PhabricatorNotificationListController
extends PhabricatorNotificationController {
private $filter;
public function willProcessRequest(array $data) {
$this->filter = idx($data, 'filter');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI('/notification/'));
$nav->addFilter('all', 'All Notifications');
$nav->addFilter('unread', 'Unread Notifications');
$filter = $nav->selectFilter($this->filter, 'all');
$pager = new AphrontPagerView();
$pager->setURI($request->getRequestURI(), 'offset');
$pager->setOffset($request->getInt('offset'));
$query = new PhabricatorNotificationQuery();
$query->setViewer($user);
$query->setUserPHID($user->getPHID());
switch ($filter) {
case 'unread':
$query->withUnread(true);
$header = pht('Unread Notifications');
$no_data = pht('You have no unread notifications.');
break;
default:
$header = pht('Notifications');
$no_data = pht('You have no notifications.');
break;
}
$notifications = $query->executeWithOffsetPager($pager);
if ($notifications) {
$builder = new PhabricatorNotificationBuilder($notifications);
$view = $builder->buildView();
} else {
$view =
'<div class="phabricator-notification no-notifications">'.
$no_data.
'</div>';
}
$view = array(
'<div class="phabricator-notification-list">',
$view,
'</div>',
);
$panel = new AphrontPanelView();
$panel->setHeader($header);
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
$panel->addButton(
- javelin_render_tag(
+ javelin_tag(
'a',
array(
'href' => '/notification/clear/',
'class' => 'button',
'sigil' => 'workflow',
),
'Mark All Read'));
$panel->appendChild($view);
$panel->appendChild($pager);
$nav->appendChild($panel);
return $this->buildStandardPageResponse(
$nav,
array(
'title' => 'Notifications',
));
}
}
diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php
index 5046a740b7..fe07e5767d 100644
--- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php
+++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php
@@ -1,236 +1,236 @@
<?php
final class PhabricatorOwnersDetailController
extends PhabricatorOwnersController {
private $id;
private $package;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$package = id(new PhabricatorOwnersPackage())->load($this->id);
if (!$package) {
return new Aphront404Response();
}
$this->package = $package;
$paths = $package->loadPaths();
$owners = $package->loadOwners();
$repository_phids = array();
foreach ($paths as $path) {
$repository_phids[$path->getRepositoryPHID()] = true;
}
if ($repository_phids) {
$repositories = id(new PhabricatorRepository())->loadAllWhere(
'phid in (%Ls)',
array_keys($repository_phids));
$repositories = mpull($repositories, null, 'getPHID');
} else {
$repositories = array();
}
$phids = array();
foreach ($owners as $owner) {
$phids[$owner->getUserPHID()] = true;
}
$phids = array_keys($phids);
$handles = $this->loadViewerHandles($phids);
$rows = array();
$rows[] = array(
'Name',
phutil_escape_html($package->getName()));
$rows[] = array(
'Description',
phutil_escape_html($package->getDescription()));
$primary_owner = null;
$primary_phid = $package->getPrimaryOwnerPHID();
if ($primary_phid && isset($handles[$primary_phid])) {
$primary_owner =
'<strong>'.$handles[$primary_phid]->renderLink().'</strong>';
}
$rows[] = array(
'Primary Owner',
$primary_owner,
);
$owner_links = array();
foreach ($owners as $owner) {
$owner_links[] = $handles[$owner->getUserPHID()]->renderLink();
}
$owner_links = implode('<br />', $owner_links);
$rows[] = array(
'Owners',
$owner_links);
$rows[] = array(
'Auditing',
$package->getAuditingEnabled() ? 'Enabled' : 'Disabled',
);
$path_links = array();
foreach ($paths as $path) {
$repo = idx($repositories, $path->getRepositoryPHID());
if (!$repo) {
continue;
}
$href = DiffusionRequest::generateDiffusionURI(
array(
'callsign' => $repo->getCallsign(),
'branch' => $repo->getDefaultBranch(),
'path' => $path->getPath(),
'action' => 'browse'
));
$repo_name = '<strong>'.phutil_escape_html($repo->getName()).
'</strong>';
$path_link = phutil_tag(
'a',
array(
'href' => (string) $href,
),
$path->getPath());
$path_links[] =
($path->getExcluded() ? '&ndash;' : '+').' '.
$repo_name.' '.$path_link;
}
$path_links = implode('<br />', $path_links);
$rows[] = array(
'Paths',
$path_links);
$table = new AphrontTableView($rows);
$table->setColumnClasses(
array(
'header',
'wide',
));
$panel = new AphrontPanelView();
$panel->setHeader(
'Package Details for "'.phutil_escape_html($package->getName()).'"');
$panel->addButton(
- javelin_render_tag(
+ javelin_tag(
'a',
array(
'href' => '/owners/delete/'.$package->getID().'/',
'class' => 'button grey',
'sigil' => 'workflow',
),
'Delete Package'));
$panel->addButton(
phutil_tag(
'a',
array(
'href' => '/owners/edit/'.$package->getID().'/',
'class' => 'button',
),
'Edit Package'));
$panel->appendChild($table);
$key = 'package/'.$package->getID();
$this->setSideNavFilter($key);
$commit_views = array();
$commit_uri = id(new PhutilURI('/audit/view/packagecommits/'))
->setQueryParams(
array(
'phid' => $package->getPHID(),
));
$attention_query = id(new PhabricatorAuditCommitQuery())
->withPackagePHIDs(array($package->getPHID()))
->withStatus(PhabricatorAuditCommitQuery::STATUS_OPEN)
->needCommitData(true)
->needAudits(true)
->setLimit(10);
$attention_commits = $attention_query->execute();
if ($attention_commits) {
$view = new PhabricatorAuditCommitListView();
$view->setUser($user);
$view->setCommits($attention_commits);
$commit_views[] = array(
'view' => $view,
'header' => 'Commits in this Package that Need Attention',
'button' => phutil_tag(
'a',
array(
'href' => $commit_uri->alter('status', 'open'),
'class' => 'button grey',
),
'View All Problem Commits'),
);
}
$all_query = id(new PhabricatorAuditCommitQuery())
->withPackagePHIDs(array($package->getPHID()))
->needCommitData(true)
->needAudits(true)
->setLimit(100);
$all_commits = $all_query->execute();
$view = new PhabricatorAuditCommitListView();
$view->setUser($user);
$view->setCommits($all_commits);
$view->setNoDataString('No commits in this package.');
$commit_views[] = array(
'view' => $view,
'header' => 'Recent Commits in Package',
'button' => phutil_tag(
'a',
array(
'href' => $commit_uri,
'class' => 'button grey',
),
'View All Package Commits'),
);
$phids = array();
foreach ($commit_views as $commit_view) {
$phids[] = $commit_view['view']->getRequiredHandlePHIDs();
}
$phids = array_mergev($phids);
$handles = $this->loadViewerHandles($phids);
$commit_panels = array();
foreach ($commit_views as $commit_view) {
$commit_panel = new AphrontPanelView();
$commit_panel->setHeader(phutil_escape_html($commit_view['header']));
if (isset($commit_view['button'])) {
$commit_panel->addButton($commit_view['button']);
}
$commit_view['view']->setHandles($handles);
$commit_panel->appendChild($commit_view['view']);
$commit_panels[] = $commit_panel;
}
return $this->buildStandardPageResponse(
array(
$panel,
$commit_panels,
),
array(
'title' => "Package '".$package->getName()."'",
));
}
protected function getExtraPackageViews(AphrontSideNavFilterView $view) {
$package = $this->package;
$view->addFilter('package/'.$package->getID(), 'Details');
}
}
diff --git a/src/applications/owners/controller/PhabricatorOwnersEditController.php b/src/applications/owners/controller/PhabricatorOwnersEditController.php
index 9e953d6933..a5aece08d1 100644
--- a/src/applications/owners/controller/PhabricatorOwnersEditController.php
+++ b/src/applications/owners/controller/PhabricatorOwnersEditController.php
@@ -1,269 +1,269 @@
<?php
final class PhabricatorOwnersEditController
extends PhabricatorOwnersController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
if ($this->id) {
$package = id(new PhabricatorOwnersPackage())->load($this->id);
if (!$package) {
return new Aphront404Response();
}
} else {
$package = new PhabricatorOwnersPackage();
$package->setPrimaryOwnerPHID($user->getPHID());
}
$e_name = true;
$e_primary = true;
$errors = array();
if ($request->isFormPost()) {
$package->setName($request->getStr('name'));
$package->setDescription($request->getStr('description'));
$old_auditing_enabled = $package->getAuditingEnabled();
$package->setAuditingEnabled($request->getStr('auditing') === 'enabled');
$primary = $request->getArr('primary');
$primary = reset($primary);
$old_primary = $package->getPrimaryOwnerPHID();
$package->setPrimaryOwnerPHID($primary);
$owners = $request->getArr('owners');
if ($primary) {
array_unshift($owners, $primary);
}
$owners = array_unique($owners);
$paths = $request->getArr('path');
$repos = $request->getArr('repo');
$excludes = $request->getArr('exclude');
$path_refs = array();
for ($ii = 0; $ii < count($paths); $ii++) {
if (empty($paths[$ii]) || empty($repos[$ii])) {
continue;
}
$path_refs[] = array(
'repositoryPHID' => $repos[$ii],
'path' => $paths[$ii],
'excluded' => $excludes[$ii],
);
}
if (!strlen($package->getName())) {
$e_name = 'Required';
$errors[] = 'Package name is required.';
} else {
$e_name = null;
}
if (!$package->getPrimaryOwnerPHID()) {
$e_primary = 'Required';
$errors[] = 'Package must have a primary owner.';
} else {
$e_primary = null;
}
if (!$path_refs) {
$errors[] = 'Package must include at least one path.';
}
if (!$errors) {
$package->attachUnsavedOwners($owners);
$package->attachUnsavedPaths($path_refs);
$package->attachOldAuditingEnabled($old_auditing_enabled);
$package->attachOldPrimaryOwnerPHID($old_primary);
$package->attachActorPHID($user->getPHID());
try {
$package->save();
return id(new AphrontRedirectResponse())
->setURI('/owners/package/'.$package->getID().'/');
} catch (AphrontQueryDuplicateKeyException $ex) {
$e_name = 'Duplicate';
$errors[] = 'Package name must be unique.';
}
}
} else {
$owners = $package->loadOwners();
$owners = mpull($owners, 'getUserPHID');
$paths = $package->loadPaths();
$path_refs = array();
foreach ($paths as $path) {
$path_refs[] = array(
'repositoryPHID' => $path->getRepositoryPHID(),
'path' => $path->getPath(),
'excluded' => $path->getExcluded(),
);
}
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setTitle('Package Errors');
$error_view->setErrors($errors);
}
$handles = $this->loadViewerHandles($owners);
$primary = $package->getPrimaryOwnerPHID();
if ($primary && isset($handles[$primary])) {
$token_primary_owner = array(
$primary => $handles[$primary]->getFullName(),
);
} else {
$token_primary_owner = array();
}
$token_all_owners = array_select_keys($handles, $owners);
$token_all_owners = mpull($token_all_owners, 'getFullName');
if ($package->getID()) {
$title = 'Edit Package';
$side_nav_filter = 'edit/'.$this->id;
} else {
$title = 'New Package';
$side_nav_filter = 'new';
}
$this->setSideNavFilter($side_nav_filter);
$repos = id(new PhabricatorRepository())->loadAll();
$default_paths = array();
foreach ($repos as $repo) {
$default_path = $repo->getDetail('default-owners-path');
if ($default_path) {
$default_paths[$repo->getPHID()] = $default_path;
}
}
$repos = mpull($repos, 'getCallsign', 'getPHID');
$template = new AphrontTypeaheadTemplateView();
$template = $template->render();
Javelin::initBehavior(
'owners-path-editor',
array(
'root' => 'path-editor',
'table' => 'paths',
'add_button' => 'addpath',
'repositories' => $repos,
'input_template' => $template,
'pathRefs' => $path_refs,
'completeURI' => '/diffusion/services/path/complete/',
'validateURI' => '/diffusion/services/path/validate/',
'repositoryDefaultPaths' => $default_paths,
));
require_celerity_resource('owners-path-editor-css');
$cancel_uri = $package->getID()
? '/owners/package/'.$package->getID().'/'
: '/owners/';
$form = id(new AphrontFormView())
->setUser($user)
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Name')
->setName('name')
->setValue($package->getName())
->setError($e_name))
->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/usersorprojects/')
->setLabel('Primary Owner')
->setName('primary')
->setLimit(1)
->setValue($token_primary_owner)
->setError($e_primary))
->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/usersorprojects/')
->setLabel('Owners')
->setName('owners')
->setValue($token_all_owners))
->appendChild(
id(new AphrontFormSelectControl())
->setName('auditing')
->setLabel('Auditing')
->setCaption('With auditing enabled, all future commits that touch '.
'this package will be reviewed to make sure an owner '.
'of the package is involved and the commit message has '.
'a valid revision, reviewed by, and author.')
->setOptions(array(
'disabled' => 'Disabled',
'enabled' => 'Enabled',
))
->setValue(
$package->getAuditingEnabled()
? 'enabled'
: 'disabled'))
->appendChild(
id(new AphrontFormInsetView())
->setTitle('Paths')
->addDivAttributes(array('id' => 'path-editor'))
- ->setRightButton(javelin_render_tag(
+ ->setRightButton(javelin_tag(
'a',
array(
'href' => '#',
'class' => 'button green',
'sigil' => 'addpath',
'mustcapture' => true,
),
'Add New Path'))
->setDescription('Specify the files and directories which comprise '.
'this package.')
- ->setContent(javelin_render_tag(
+ ->setContent(javelin_tag(
'table',
array(
'class' => 'owners-path-editor-table',
'sigil' => 'paths',
),
'')))
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel('Description')
->setName('description')
->setValue($package->getDescription()))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
->setValue('Save Package'));
$panel = new AphrontPanelView();
$panel->setHeader($title);
$panel->setWidth(AphrontPanelView::WIDTH_WIDE);
$panel->appendChild($error_view);
$panel->appendChild($form);
return $this->buildStandardPageResponse(
$panel,
array(
'title' => $title,
));
}
protected function getExtraPackageViews(AphrontSideNavFilterView $view) {
if ($this->id) {
$view->addFilter('edit/'.$this->id, 'Edit');
} else {
$view->addFilter('new', 'New');
}
}
}
diff --git a/src/applications/phortune/stripe/view/PhortuneStripePaymentFormView.php b/src/applications/phortune/stripe/view/PhortuneStripePaymentFormView.php
index 569f7db994..c77b5b623f 100644
--- a/src/applications/phortune/stripe/view/PhortuneStripePaymentFormView.php
+++ b/src/applications/phortune/stripe/view/PhortuneStripePaymentFormView.php
@@ -1,133 +1,133 @@
<?php
final class PhortuneStripePaymentFormView extends AphrontView {
private $stripeKey;
private $cardNumberError;
private $cardCVCError;
private $cardExpirationError;
public function setStripeKey($key) {
$this->stripeKey = $key;
return $this;
}
private function getStripeKey() {
return $this->stripeKey;
}
public function setCardNumberError($error) {
$this->cardNumberError = $error;
return $this;
}
private function getCardNumberError() {
return $this->cardNumberError;
}
public function setCardCVCError($error) {
$this->cardCVCError = $error;
return $this;
}
private function getCardCVCError() {
return $this->cardCVCError;
}
public function setCardExpirationError($error) {
$this->cardExpirationError = $error;
return $this;
}
private function getCardExpirationError() {
return $this->cardExpirationError;
}
public function render() {
$form_id = celerity_generate_unique_node_id();
require_celerity_resource('stripe-payment-form-css');
require_celerity_resource('aphront-tooltip-css');
Javelin::initBehavior('phabricator-tooltips');
$form = id(new AphrontFormView())
->setID($form_id)
->setUser($this->getUser())
->appendChild(
id(new AphrontFormMarkupControl())
->setLabel('')
->setValue(
- javelin_render_tag(
+ javelin_tag(
'div',
array(
'class' => 'credit-card-logos',
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => 'We support Visa, Mastercard, American Express, '.
'Discover, JCB, and Diners Club.',
'size' => 440,
)
)
)
)
)
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Card Number')
->setDisableAutocomplete(true)
->setSigil('number-input')
->setError($this->getCardNumberError())
)
->appendChild(
id(new AphrontFormTextControl())
->setLabel('CVC')
->setDisableAutocomplete(true)
->setSigil('cvc-input')
->setError($this->getCardCVCError())
)
->appendChild(
id(new PhortuneMonthYearExpiryControl())
->setLabel('Expiration')
->setUser($this->getUser())
->setError($this->getCardExpirationError())
)
->appendChild(
- javelin_render_tag(
+ javelin_tag(
'input',
array(
'hidden' => true,
'name' => 'stripeToken',
'sigil' => 'stripe-token-input',
)
)
)
->appendChild(
- javelin_render_tag(
+ javelin_tag(
'input',
array(
'hidden' => true,
'name' => 'cardErrors',
'sigil' => 'card-errors-input'
)
)
)
->appendChild(
phutil_tag(
'input',
array(
'hidden' => true,
'name' => 'stripeKey',
'value' => $this->getStripeKey(),
)
)
)
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Submit Payment')
);
Javelin::initBehavior(
'stripe-payment-form',
array(
'stripePublishKey' => $this->getStripeKey(),
'root' => $form_id,
)
);
return $form->render();
}
}
diff --git a/src/applications/ponder/view/PonderVotableView.php b/src/applications/ponder/view/PonderVotableView.php
index a921a79d11..c4bb4ab726 100644
--- a/src/applications/ponder/view/PonderVotableView.php
+++ b/src/applications/ponder/view/PonderVotableView.php
@@ -1,90 +1,90 @@
<?php
final class PonderVotableView extends AphrontView {
private $phid;
private $uri;
private $count;
private $vote;
public function setPHID($phid) {
$this->phid = $phid;
return $this;
}
public function setURI($uri) {
$this->uri = $uri;
return $this;
}
public function setCount($count) {
$this->count = $count;
return $this;
}
public function setVote($vote) {
$this->vote = $vote;
return $this;
}
public function render() {
require_celerity_resource('ponder-vote-css');
require_celerity_resource('javelin-behavior-ponder-votebox');
Javelin::initBehavior('ponder-votebox', array());
$uri = id(new PhutilURI($this->uri))->alter('phid', $this->phid);
- $up = javelin_render_tag(
+ $up = javelin_tag(
'a',
array(
'href' => (string)$uri,
'sigil' => 'upvote',
'mustcapture' => true,
'class' => ($this->vote > 0) ? 'ponder-vote-active' : null,
),
"\xE2\x96\xB2");
- $down = javelin_render_tag(
+ $down = javelin_tag(
'a',
array(
'href' => (string)$uri,
'sigil' => 'downvote',
'mustcapture' => true,
'class' => ($this->vote < 0) ? 'ponder-vote-active' : null,
),
"\xE2\x96\xBC");
- $count = javelin_render_tag(
+ $count = javelin_tag(
'div',
array(
'class' => 'ponder-vote-count',
'sigil' => 'ponder-vote-count',
),
- phutil_escape_html($this->count));
+ $this->count);
return javelin_render_tag(
'div',
array(
'class' => 'ponder-votable',
'sigil' => 'ponder-votable',
'meta' => array(
'count' => (int)$this->count,
'vote' => (int)$this->vote,
),
),
javelin_render_tag(
'div',
array(
'class' => 'ponder-votebox',
),
$up.$count.$down).
phutil_render_tag(
'div',
array(
'class' => 'ponder-votebox-content',
),
$this->renderChildren()));
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectMembersEditController.php b/src/applications/project/controller/PhabricatorProjectMembersEditController.php
index ae4adba176..1e89cb307f 100644
--- a/src/applications/project/controller/PhabricatorProjectMembersEditController.php
+++ b/src/applications/project/controller/PhabricatorProjectMembersEditController.php
@@ -1,165 +1,165 @@
<?php
final class PhabricatorProjectMembersEditController
extends PhabricatorProjectController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$project = id(new PhabricatorProjectQuery())
->setViewer($user)
->withIDs(array($this->id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$project) {
return new Aphront404Response();
}
$profile = $project->loadProfile();
if (empty($profile)) {
$profile = new PhabricatorProjectProfile();
}
$member_phids = $project->loadMemberPHIDs();
$errors = array();
if ($request->isFormPost()) {
$changed_something = false;
$member_map = array_fill_keys($member_phids, true);
$remove = $request->getStr('remove');
if ($remove) {
if (isset($member_map[$remove])) {
unset($member_map[$remove]);
$changed_something = true;
}
} else {
$new_members = $request->getArr('phids');
foreach ($new_members as $member) {
if (empty($member_map[$member])) {
$member_map[$member] = true;
$changed_something = true;
}
}
}
$xactions = array();
if ($changed_something) {
$xaction = new PhabricatorProjectTransaction();
$xaction->setTransactionType(
PhabricatorProjectTransactionType::TYPE_MEMBERS);
$xaction->setNewValue(array_keys($member_map));
$xactions[] = $xaction;
}
if ($xactions) {
$editor = new PhabricatorProjectEditor($project);
$editor->setActor($user);
$editor->applyTransactions($xactions);
}
return id(new AphrontRedirectResponse())
->setURI($request->getRequestURI());
}
$member_phids = array_reverse($member_phids);
$handles = $this->loadViewerHandles($member_phids);
$state = array();
foreach ($handles as $handle) {
$state[] = array(
'phid' => $handle->getPHID(),
'name' => $handle->getFullName(),
);
}
$header_name = 'Edit Members';
$title = 'Edit Members';
$list = $this->renderMemberList($handles);
$form = new AphrontFormView();
$form
->setUser($user)
->appendChild(
id(new AphrontFormTokenizerControl())
->setName('phids')
->setLabel('Add Members')
->setDatasource('/typeahead/common/users/'))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton('/project/view/'.$project->getID().'/')
->setValue('Add Members'));
$faux_form = id(new AphrontFormLayoutView())
->setBackgroundShading(true)
->setPadded(true)
->appendChild(
id(new AphrontFormInsetView())
->setTitle('Current Members ('.count($handles).')')
->appendChild($list));
$panel = new AphrontPanelView();
$panel->setHeader($header_name);
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
$panel->appendChild($form);
$panel->appendChild('<br />');
$panel->appendChild($faux_form);
$nav = $this->buildLocalNavigation($project);
$nav->selectFilter('members');
$nav->appendChild($panel);
return $this->buildStandardPageResponse(
$nav,
array(
'title' => $title,
));
}
private function renderMemberList(array $handles) {
$request = $this->getRequest();
$user = $request->getUser();
$list = id(new PhabricatorObjectListView())
->setHandles($handles);
foreach ($handles as $handle) {
$hidden_input = phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => 'remove',
'value' => $handle->getPHID(),
),
'');
- $button = javelin_render_tag(
+ $button = javelin_tag(
'button',
array(
'class' => 'grey',
),
pht('Remove'));
$list->addButton(
$handle,
phabricator_render_form(
$user,
array(
'method' => 'POST',
'action' => $request->getRequestURI(),
),
$hidden_input.$button));
}
return $list;
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php
index d843874993..4d6e4b8eee 100644
--- a/src/applications/project/controller/PhabricatorProjectProfileController.php
+++ b/src/applications/project/controller/PhabricatorProjectProfileController.php
@@ -1,287 +1,287 @@
<?php
final class PhabricatorProjectProfileController
extends PhabricatorProjectController {
private $id;
private $page;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
$this->page = idx($data, 'page');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$query = id(new PhabricatorProjectQuery())
->setViewer($user)
->withIDs(array($this->id));
if ($this->page == 'people') {
$query->needMembers(true);
}
$project = $query->executeOne();
if (!$project) {
return new Aphront404Response();
}
$profile = $project->loadProfile();
if (!$profile) {
$profile = new PhabricatorProjectProfile();
}
$picture = $profile->loadProfileImageURI();
$nav_view = $this->buildLocalNavigation($project);
$this->page = $nav_view->selectFilter($this->page, 'dashboard');
require_celerity_resource('phabricator-profile-css');
switch ($this->page) {
case 'dashboard':
$content = $this->renderTasksPage($project, $profile);
$query = new PhabricatorFeedQuery();
$query->setFilterPHIDs(
array(
$project->getPHID(),
));
$query->setLimit(50);
$query->setViewer($this->getRequest()->getUser());
$stories = $query->execute();
$content .= $this->renderStories($stories);
break;
case 'about':
$content = $this->renderAboutPage($project, $profile);
break;
case 'people':
$content = $this->renderPeoplePage($project, $profile);
break;
case 'feed':
$content = $this->renderFeedPage($project, $profile);
break;
default:
throw new Exception("Unimplemented filter '{$this->page}'.");
}
$header = new PhabricatorProfileHeaderView();
$header->setName($project->getName());
$header->setDescription(
phutil_utf8_shorten($profile->getBlurb(), 1024));
$header->setProfilePicture($picture);
$action = null;
if (!$project->isUserMember($user->getPHID())) {
$can_join = PhabricatorPolicyCapability::CAN_JOIN;
if (PhabricatorPolicyFilter::hasCapability($user, $project, $can_join)) {
$class = 'green';
} else {
$class = 'grey disabled';
}
$action = phabricator_render_form(
$user,
array(
'action' => '/project/update/'.$project->getID().'/join/',
'method' => 'post',
),
phutil_tag(
'button',
array(
'class' => $class,
),
'Join Project'));
} else {
- $action = javelin_render_tag(
+ $action = javelin_tag(
'a',
array(
'href' => '/project/update/'.$project->getID().'/leave/',
'sigil' => 'workflow',
'class' => 'grey button',
),
'Leave Project...');
}
$header->addAction($action);
$nav_view->appendChild($header);
$content = '<div style="padding: 1em;">'.$content.'</div>';
$header->appendChild($content);
return $this->buildStandardPageResponse(
$nav_view,
array(
'title' => $project->getName().' Project',
));
}
private function renderAboutPage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$viewer = $this->getRequest()->getUser();
$blurb = $profile->getBlurb();
$blurb = phutil_escape_html($blurb);
$blurb = str_replace("\n", '<br />', $blurb);
$phids = array($project->getAuthorPHID());
$phids = array_unique($phids);
$handles = $this->loadViewerHandles($phids);
$timestamp = phabricator_datetime($project->getDateCreated(), $viewer);
$about =
'<div class="phabricator-profile-info-group">
<h1 class="phabricator-profile-info-header">About</h1>
<div class="phabricator-profile-info-pane">
<table class="phabricator-profile-info-table">
<tr>
<th>Creator</th>
<td>'.$handles[$project->getAuthorPHID()]->renderLink().'</td>
</tr>
<tr>
<th>Created</th>
<td>'.$timestamp.'</td>
</tr>
<tr>
<th>PHID</th>
<td>'.phutil_escape_html($project->getPHID()).'</td>
</tr>
<tr>
<th>Blurb</th>
<td>'.$blurb.'</td>
</tr>
</table>
</div>
</div>';
return $about;
}
private function renderPeoplePage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$member_phids = $project->getMemberPHIDs();
$handles = $this->loadViewerHandles($member_phids);
$affiliated = array();
foreach ($handles as $phids => $handle) {
$affiliated[] = '<li>'.$handle->renderLink().'</li>';
}
if ($affiliated) {
$affiliated = '<ul>'.implode("\n", $affiliated).'</ul>';
} else {
$affiliated = '<p><em>No one is affiliated with this project.</em></p>';
}
return
'<div class="phabricator-profile-info-group">'.
'<h1 class="phabricator-profile-info-header">People</h1>'.
'<div class="phabricator-profile-info-pane">'.
$affiliated.
'</div>'.
'</div>';
}
private function renderFeedPage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$query = new PhabricatorFeedQuery();
$query->setFilterPHIDs(array($project->getPHID()));
$query->setViewer($this->getRequest()->getUser());
$query->setLimit(100);
$stories = $query->execute();
if (!$stories) {
return 'There are no stories about this project.';
}
return $this->renderStories($stories);
}
private function renderStories(array $stories) {
assert_instances_of($stories, 'PhabricatorFeedStory');
$builder = new PhabricatorFeedBuilder($stories);
$builder->setUser($this->getRequest()->getUser());
$view = $builder->buildView();
return
'<div class="phabricator-profile-info-group">'.
'<h1 class="phabricator-profile-info-header">Activity Feed</h1>'.
'<div class="phabricator-profile-info-pane">'.
$view->render().
'</div>'.
'</div>';
}
private function renderTasksPage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$query = id(new ManiphestTaskQuery())
->withAnyProjects(array($project->getPHID()))
->withStatus(ManiphestTaskQuery::STATUS_OPEN)
->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY)
->setLimit(10)
->setCalculateRows(true);
$tasks = $query->execute();
$count = $query->getRowCount();
$phids = mpull($tasks, 'getOwnerPHID');
$phids = array_filter($phids);
$handles = $this->loadViewerHandles($phids);
$task_views = array();
foreach ($tasks as $task) {
$view = id(new ManiphestTaskSummaryView())
->setTask($task)
->setHandles($handles)
->setUser($this->getRequest()->getUser());
$task_views[] = $view->render();
}
if (empty($tasks)) {
$task_views = '<em>No open tasks.</em>';
} else {
$task_views = implode('', $task_views);
}
$open = number_format($count);
$more_link = phutil_tag(
'a',
array(
'href' => '/maniphest/view/all/?projects='.$project->getPHID(),
),
"View All Open Tasks \xC2\xBB");
$content =
'<div class="phabricator-profile-info-group">
<h1 class="phabricator-profile-info-header">'.
"Open Tasks ({$open})".
'</h1>'.
'<div class="phabricator-profile-info-pane">'.
$task_views.
'<div class="phabricator-profile-info-pane-more-link">'.
$more_link.
'</div>'.
'</div>
</div>';
return $content;
}
}
diff --git a/src/applications/repository/controller/PhabricatorRepositoryListController.php b/src/applications/repository/controller/PhabricatorRepositoryListController.php
index fd4b6fb37c..8c4baea1d0 100644
--- a/src/applications/repository/controller/PhabricatorRepositoryListController.php
+++ b/src/applications/repository/controller/PhabricatorRepositoryListController.php
@@ -1,168 +1,168 @@
<?php
final class PhabricatorRepositoryListController
extends PhabricatorRepositoryController {
public function shouldRequireAdmin() {
return false;
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$is_admin = $user->getIsAdmin();
$repos = id(new PhabricatorRepository())->loadAll();
$repos = msort($repos, 'getName');
$rows = array();
foreach ($repos as $repo) {
if ($repo->isTracked()) {
$diffusion_link = phutil_tag(
'a',
array(
'href' => '/diffusion/'.$repo->getCallsign().'/',
),
'View in Diffusion');
} else {
$diffusion_link = '<em>Not Tracked</em>';
}
$rows[] = array(
phutil_escape_html($repo->getCallsign()),
phutil_escape_html($repo->getName()),
PhabricatorRepositoryType::getNameForRepositoryType(
$repo->getVersionControlSystem()),
$diffusion_link,
phutil_tag(
'a',
array(
'class' => 'button small grey',
'href' => '/repository/edit/'.$repo->getID().'/',
),
'Edit'),
- javelin_render_tag(
+ javelin_tag(
'a',
array(
'class' => 'button small grey',
'href' => '/repository/delete/'.$repo->getID().'/',
'sigil' => 'workflow',
),
'Delete'),
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
'Callsign',
'Repository',
'Type',
'Diffusion',
'',
''
));
$table->setColumnClasses(
array(
null,
'wide',
null,
null,
'action',
'action',
));
$table->setColumnVisibility(
array(
true,
true,
true,
true,
$is_admin,
$is_admin,
));
$panel = new AphrontPanelView();
$panel->setHeader('Repositories');
if ($is_admin) {
$panel->setCreateButton('Create New Repository', '/repository/create/');
}
$panel->appendChild($table);
$panel->setNoBackground();
$projects = id(new PhabricatorRepositoryArcanistProject())->loadAll();
$rows = array();
foreach ($projects as $project) {
$repo = idx($repos, $project->getRepositoryID());
if ($repo) {
$repo_name = phutil_escape_html($repo->getName());
} else {
$repo_name = '-';
}
$rows[] = array(
phutil_escape_html($project->getName()),
$repo_name,
phutil_tag(
'a',
array(
'href' => '/repository/project/edit/'.$project->getID().'/',
'class' => 'button grey small',
),
'Edit'),
javelin_render_tag(
'a',
array(
'href' => '/repository/project/delete/'.$project->getID().'/',
'class' => 'button grey small',
'sigil' => 'workflow',
),
'Delete'),
);
}
$project_table = new AphrontTableView($rows);
$project_table->setHeaders(
array(
'Project ID',
'Repository',
'',
'',
));
$project_table->setColumnClasses(
array(
'',
'wide',
'action',
'action',
));
$project_table->setColumnVisibility(
array(
true,
true,
$is_admin,
$is_admin,
));
$project_panel = new AphrontPanelView();
$project_panel->setHeader('Arcanist Projects');
$project_panel->appendChild($project_table);
$project_panel->setNoBackground();
return $this->buildStandardPageResponse(
array(
$this->renderDaemonNotice(),
$panel,
$project_panel,
),
array(
'title' => 'Repository List',
));
}
}
diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelEmailAddresses.php b/src/applications/settings/panel/PhabricatorSettingsPanelEmailAddresses.php
index 057ac4436d..9ad348ed6b 100644
--- a/src/applications/settings/panel/PhabricatorSettingsPanelEmailAddresses.php
+++ b/src/applications/settings/panel/PhabricatorSettingsPanelEmailAddresses.php
@@ -1,354 +1,354 @@
<?php
final class PhabricatorSettingsPanelEmailAddresses
extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'email';
}
public function getPanelName() {
return pht('Email Addresses');
}
public function getPanelGroup() {
return pht('Email');
}
public function processRequest(AphrontRequest $request) {
$user = $request->getUser();
$editable = PhabricatorEnv::getEnvConfig('account.editable');
$uri = $request->getRequestURI();
$uri->setQueryParams(array());
if ($editable) {
$new = $request->getStr('new');
if ($new) {
return $this->returnNewAddressResponse($request, $uri, $new);
}
$delete = $request->getInt('delete');
if ($delete) {
return $this->returnDeleteAddressResponse($request, $uri, $delete);
}
}
$verify = $request->getInt('verify');
if ($verify) {
return $this->returnVerifyAddressResponse($request, $uri, $verify);
}
$primary = $request->getInt('primary');
if ($primary) {
return $this->returnPrimaryAddressResponse($request, $uri, $primary);
}
$emails = id(new PhabricatorUserEmail())->loadAllWhere(
'userPHID = %s ORDER BY address',
$user->getPHID());
$rowc = array();
$rows = array();
foreach ($emails as $email) {
- $button_verify = javelin_render_tag(
+ $button_verify = javelin_tag(
'a',
array(
'class' => 'button small grey',
'href' => $uri->alter('verify', $email->getID()),
'sigil' => 'workflow',
),
'Verify');
- $button_make_primary = javelin_render_tag(
+ $button_make_primary = javelin_tag(
'a',
array(
'class' => 'button small grey',
'href' => $uri->alter('primary', $email->getID()),
'sigil' => 'workflow',
),
'Make Primary');
- $button_remove = javelin_render_tag(
+ $button_remove = javelin_tag(
'a',
array(
'class' => 'button small grey',
'href' => $uri->alter('delete', $email->getID()),
'sigil' => 'workflow'
),
'Remove');
$button_primary = phutil_tag(
'a',
array(
'class' => 'button small disabled',
),
'Primary');
if (!$email->getIsVerified()) {
$action = $button_verify;
} else if ($email->getIsPrimary()) {
$action = $button_primary;
} else {
$action = $button_make_primary;
}
if ($email->getIsPrimary()) {
$remove = $button_primary;
$rowc[] = 'highlighted';
} else {
$remove = $button_remove;
$rowc[] = null;
}
$rows[] = array(
phutil_escape_html($email->getAddress()),
$action,
$remove,
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
'Email',
'Status',
'Remove',
));
$table->setColumnClasses(
array(
'wide',
'action',
'action',
));
$table->setRowClasses($rowc);
$table->setColumnVisibility(
array(
true,
true,
$editable,
));
$view = new AphrontPanelView();
if ($editable) {
$view->addButton(
- javelin_render_tag(
+ javelin_tag(
'a',
array(
'href' => $uri->alter('new', 'true'),
'class' => 'green button',
'sigil' => 'workflow',
),
'Add New Address'));
}
$view->setHeader('Email Addresses');
$view->appendChild($table);
$view->setNoBackground();
return $view;
}
private function returnNewAddressResponse(
AphrontRequest $request,
PhutilURI $uri,
$new) {
$user = $request->getUser();
$e_email = true;
$email = trim($request->getStr('email'));
$errors = array();
if ($request->isDialogFormPost()) {
if ($new == 'verify') {
// The user clicked "Done" from the "an email has been sent" dialog.
return id(new AphrontReloadResponse())->setURI($uri);
}
if (!strlen($email)) {
$e_email = 'Required';
$errors[] = 'Email is required.';
} else if (!PhabricatorUserEmail::isAllowedAddress($email)) {
$e_email = 'Invalid';
$errors[] = PhabricatorUserEmail::describeAllowedAddresses();
}
if (!$errors) {
$object = id(new PhabricatorUserEmail())
->setAddress($email)
->setIsVerified(0);
try {
id(new PhabricatorUserEditor())
->setActor($user)
->addEmail($user, $object);
$object->sendVerificationEmail($user);
$dialog = id(new AphrontDialogView())
->setUser($user)
->addHiddenInput('new', 'verify')
->setTitle('Verification Email Sent')
->appendChild(
'<p>A verification email has been sent. Click the link in the '.
'email to verify your address.</p>')
->setSubmitURI($uri)
->addSubmitButton('Done');
return id(new AphrontDialogResponse())->setDialog($dialog);
} catch (AphrontQueryDuplicateKeyException $ex) {
$email = 'Duplicate';
$errors[] = 'Another user already has this email.';
}
}
}
if ($errors) {
$errors = id(new AphrontErrorView())
->setErrors($errors);
}
$form = id(new AphrontFormLayoutView())
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Email')
->setName('email')
->setValue($request->getStr('email'))
->setCaption(PhabricatorUserEmail::describeAllowedAddresses())
->setError($e_email));
$dialog = id(new AphrontDialogView())
->setUser($user)
->addHiddenInput('new', 'true')
->setTitle('New Address')
->appendChild($errors)
->appendChild($form)
->addSubmitButton('Save')
->addCancelButton($uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
private function returnDeleteAddressResponse(
AphrontRequest $request,
PhutilURI $uri,
$email_id) {
$user = $request->getUser();
// NOTE: You can only delete your own email addresses, and you can not
// delete your primary address.
$email = id(new PhabricatorUserEmail())->loadOneWhere(
'id = %d AND userPHID = %s AND isPrimary = 0',
$email_id,
$user->getPHID());
if (!$email) {
return new Aphront404Response();
}
if ($request->isFormPost()) {
id(new PhabricatorUserEditor())
->setActor($user)
->removeEmail($user, $email);
return id(new AphrontRedirectResponse())->setURI($uri);
}
$address = $email->getAddress();
$dialog = id(new AphrontDialogView())
->setUser($user)
->addHiddenInput('delete', $email_id)
->setTitle("Really delete address '{$address}'?")
->appendChild(
'<p>Are you sure you want to delete this address? You will no '.
'longer be able to use it to login.</p>')
->addSubmitButton('Delete')
->addCancelButton($uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
private function returnVerifyAddressResponse(
AphrontRequest $request,
PhutilURI $uri,
$email_id) {
$user = $request->getUser();
// NOTE: You can only send more email for your unverified addresses.
$email = id(new PhabricatorUserEmail())->loadOneWhere(
'id = %d AND userPHID = %s AND isVerified = 0',
$email_id,
$user->getPHID());
if (!$email) {
return new Aphront404Response();
}
if ($request->isFormPost()) {
$email->sendVerificationEmail($user);
return id(new AphrontRedirectResponse())->setURI($uri);
}
$address = $email->getAddress();
$dialog = id(new AphrontDialogView())
->setUser($user)
->addHiddenInput('verify', $email_id)
->setTitle("Send Another Verification Email?")
->appendChild(
'<p>Send another copy of the verification email to '.
phutil_escape_html($address).'?</p>')
->addSubmitButton('Send Email')
->addCancelButton($uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
private function returnPrimaryAddressResponse(
AphrontRequest $request,
PhutilURI $uri,
$email_id) {
$user = $request->getUser();
// NOTE: You can only make your own verified addresses primary.
$email = id(new PhabricatorUserEmail())->loadOneWhere(
'id = %d AND userPHID = %s AND isVerified = 1 AND isPrimary = 0',
$email_id,
$user->getPHID());
if (!$email) {
return new Aphront404Response();
}
if ($request->isFormPost()) {
id(new PhabricatorUserEditor())
->setActor($user)
->changePrimaryEmail($user, $email);
return id(new AphrontRedirectResponse())->setURI($uri);
}
$address = $email->getAddress();
$dialog = id(new AphrontDialogView())
->setUser($user)
->addHiddenInput('primary', $email_id)
->setTitle("Change primary email address?")
->appendChild(
'<p>If you change your primary address, Phabricator will send all '.
'email to '.phutil_escape_html($address).'.</p>')
->addSubmitButton('Change Primary Address')
->addCancelButton($uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}
diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelSSHKeys.php b/src/applications/settings/panel/PhabricatorSettingsPanelSSHKeys.php
index a0b6b06616..133c45ec9c 100644
--- a/src/applications/settings/panel/PhabricatorSettingsPanelSSHKeys.php
+++ b/src/applications/settings/panel/PhabricatorSettingsPanelSSHKeys.php
@@ -1,266 +1,266 @@
<?php
final class PhabricatorSettingsPanelSSHKeys
extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'ssh';
}
public function getPanelName() {
return pht('SSH Public Keys');
}
public function getPanelGroup() {
return pht('Authentication');
}
public function isEnabled() {
return PhabricatorEnv::getEnvConfig('auth.sshkeys.enabled');
}
public function processRequest(AphrontRequest $request) {
$user = $request->getUser();
$edit = $request->getStr('edit');
$delete = $request->getStr('delete');
if (!$edit && !$delete) {
return $this->renderKeyListView($request);
}
$id = nonempty($edit, $delete);
if ($id && is_numeric($id)) {
// NOTE: Prevent editing/deleting of keys you don't own.
$key = id(new PhabricatorUserSSHKey())->loadOneWhere(
'userPHID = %s AND id = %d',
$user->getPHID(),
(int)$id);
if (!$key) {
return new Aphront404Response();
}
} else {
$key = new PhabricatorUserSSHKey();
$key->setUserPHID($user->getPHID());
}
if ($delete) {
return $this->processDelete($request, $key);
}
$e_name = true;
$e_key = true;
$errors = array();
$entire_key = $key->getEntireKey();
if ($request->isFormPost()) {
$key->setName($request->getStr('name'));
$entire_key = $request->getStr('key');
if (!strlen($entire_key)) {
$errors[] = 'You must provide an SSH Public Key.';
$e_key = 'Required';
} else {
$parts = str_replace("\n", '', trim($entire_key));
$parts = preg_split('/\s+/', $parts);
if (count($parts) == 2) {
$parts[] = ''; // Add an empty comment part.
} else if (count($parts) == 3) {
// This is the expected case.
} else {
if (preg_match('/private\s*key/i', $entire_key)) {
// Try to give the user a better error message if it looks like
// they uploaded a private key.
$e_key = 'Invalid';
$errors[] = 'Provide your public key, not your private key!';
} else {
$e_key = 'Invalid';
$errors[] = 'Provided public key is not properly formatted.';
}
}
if (!$errors) {
list($type, $body, $comment) = $parts;
if (!preg_match('/^ssh-dsa|ssh-rsa$/', $type)) {
$e_key = 'Invalid';
$errors[] = 'Public key should be "ssh-dsa" or "ssh-rsa".';
} else {
$key->setKeyType($type);
$key->setKeyBody($body);
$key->setKeyHash(md5($body));
$key->setKeyComment($comment);
$e_key = null;
}
}
}
if (!strlen($key->getName())) {
$errors[] = 'You must name this public key.';
$e_name = 'Required';
} else {
$e_name = null;
}
if (!$errors) {
try {
$key->save();
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI());
} catch (AphrontQueryDuplicateKeyException $ex) {
$e_key = 'Duplicate';
$errors[] = 'This public key is already associated with a user '.
'account.';
}
}
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setTitle('Form Errors');
$error_view->setErrors($errors);
}
$is_new = !$key->getID();
if ($is_new) {
$header = 'Add New SSH Public Key';
$save = 'Add Key';
} else {
$header = 'Edit SSH Public Key';
$save = 'Save Changes';
}
$form = id(new AphrontFormView())
->setUser($user)
->addHiddenInput('edit', $is_new ? 'true' : $key->getID())
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Name')
->setName('name')
->setValue($key->getName())
->setError($e_name))
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel('Public Key')
->setName('key')
->setValue($entire_key)
->setError($e_key))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($this->getPanelURI())
->setValue($save));
$panel = new AphrontPanelView();
$panel->setHeader($header);
$panel->appendChild($form);
$panel->setNoBackground();
return id(new AphrontNullView())
->appendChild(
array(
$error_view,
$panel,
));
}
private function renderKeyListView(AphrontRequest $request) {
$user = $request->getUser();
$keys = id(new PhabricatorUserSSHKey())->loadAllWhere(
'userPHID = %s',
$user->getPHID());
$rows = array();
foreach ($keys as $key) {
$rows[] = array(
phutil_tag(
'a',
array(
'href' => $this->getPanelURI('?edit='.$key->getID()),
),
$key->getName()),
phutil_escape_html($key->getKeyComment()),
phutil_escape_html($key->getKeyType()),
phabricator_date($key->getDateCreated(), $user),
phabricator_time($key->getDateCreated(), $user),
- javelin_render_tag(
+ javelin_tag(
'a',
array(
'href' => $this->getPanelURI('?delete='.$key->getID()),
'class' => 'small grey button',
'sigil' => 'workflow',
),
'Delete'),
);
}
$table = new AphrontTableView($rows);
$table->setNoDataString("You haven't added any SSH Public Keys.");
$table->setHeaders(
array(
'Name',
'Comment',
'Type',
'Created',
'Time',
'',
));
$table->setColumnClasses(
array(
'wide pri',
'',
'',
'',
'right',
'action',
));
$panel = new AphrontPanelView();
$panel->addButton(
phutil_tag(
'a',
array(
'href' => $this->getPanelURI('?edit=true'),
'class' => 'green button',
),
'Add New Public Key'));
$panel->setHeader('SSH Public Keys');
$panel->appendChild($table);
$panel->setNoBackground();
return $panel;
}
private function processDelete(
AphrontRequest $request,
PhabricatorUserSSHKey $key) {
$user = $request->getUser();
$name = phutil_escape_html($key->getName());
if ($request->isDialogFormPost()) {
$key->delete();
return id(new AphrontReloadResponse())
->setURI($this->getPanelURI());
}
$dialog = id(new AphrontDialogView())
->setUser($user)
->addHiddenInput('delete', $key->getID())
->setTitle('Really delete SSH Public Key?')
->appendChild(
'<p>The key "<strong>'.$name.'</strong>" will be permanently deleted, '.
'and you will not longer be able to use the corresponding private key '.
'to authenticate.</p>')
->addSubmitButton('Delete Public Key')
->addCancelButton($this->getPanelURI());
return id(new AphrontDialogResponse())
->setDialog($dialog);
}
}
diff --git a/src/applications/uiexample/examples/JavelinUIExample.php b/src/applications/uiexample/examples/JavelinUIExample.php
index 5c9096d1e8..5976066bd2 100644
--- a/src/applications/uiexample/examples/JavelinUIExample.php
+++ b/src/applications/uiexample/examples/JavelinUIExample.php
@@ -1,66 +1,66 @@
<?php
final class JavelinUIExample extends PhabricatorUIExample {
public function getName() {
return 'Javelin UI';
}
public function getDescription() {
return 'Here are some Javelin UI elements that you could use.';
}
public function renderExample() {
$request = $this->getRequest();
$user = $request->getUser();
// toggle-class
$container_id = celerity_generate_unique_node_id();
$button_red_id = celerity_generate_unique_node_id();
$button_blue_id = celerity_generate_unique_node_id();
- $button_red = javelin_render_tag(
+ $button_red = javelin_tag(
'a',
array(
'class' => 'button',
'sigil' => 'jx-toggle-class',
'href' => '#',
'id' => $button_red_id,
'meta' => array(
'map' => array(
$container_id => 'jxui-red-border',
$button_red_id => 'jxui-active',
),
),
),
'Toggle Red Border');
- $button_blue = javelin_render_tag(
+ $button_blue = javelin_tag(
'a',
array(
'class' => 'button jxui-active',
'sigil' => 'jx-toggle-class',
'href' => '#',
'id' => $button_blue_id,
'meta' => array(
'state' => true,
'map' => array(
$container_id => 'jxui-blue-background',
$button_blue_id => 'jxui-active',
),
),
),
'Toggle Blue Background');
$div = phutil_tag(
'div',
array(
'id' => $container_id,
'class' => 'jxui-example-container jxui-blue-background',
),
array($button_red, $button_blue));
return array($div);
}
}
diff --git a/src/applications/uiexample/examples/PhabricatorUINotificationExample.php b/src/applications/uiexample/examples/PhabricatorUINotificationExample.php
index c112a33fcd..031bb42c30 100644
--- a/src/applications/uiexample/examples/PhabricatorUINotificationExample.php
+++ b/src/applications/uiexample/examples/PhabricatorUINotificationExample.php
@@ -1,30 +1,30 @@
<?php
final class PhabricatorUINotificationExample extends PhabricatorUIExample {
public function getName() {
return 'Notifications';
}
public function getDescription() {
return 'Use <tt>JX.Notification</tt> to create notifications.';
}
public function renderExample() {
require_celerity_resource('phabricator-notification-css');
Javelin::initBehavior('phabricator-notification-example');
- $content = javelin_render_tag(
+ $content = javelin_tag(
'a',
array(
'sigil' => 'notification-example',
'class' => 'button green',
),
'Show Notification');
$content = '<div style="padding: 1em 3em;">'.$content.'</content>';
return $content;
}
}
diff --git a/src/applications/uiexample/examples/PhabricatorUITooltipExample.php b/src/applications/uiexample/examples/PhabricatorUITooltipExample.php
index 77a346510b..9f806e3516 100644
--- a/src/applications/uiexample/examples/PhabricatorUITooltipExample.php
+++ b/src/applications/uiexample/examples/PhabricatorUITooltipExample.php
@@ -1,104 +1,104 @@
<?php
final class PhabricatorUITooltipExample extends PhabricatorUIExample {
public function getName() {
return 'Tooltips';
}
public function getDescription() {
return 'Use <tt>JX.Tooltip</tt> to create tooltips.';
}
public function renderExample() {
Javelin::initBehavior('phabricator-tooltips');
require_celerity_resource('aphront-tooltip-css');
$style = 'width: 200px; '.
'height: 200px '.
'text-align: center; '.
'margin: 20px; '.
'background: #dfdfdf; '.
'padding: 30px 10px; '.
'border: 1px solid black; ';
$lorem = <<<EOTEXT
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget urna
sed ante ultricies consequat id a odio. Mauris interdum volutpat sapien eu
accumsan. In hac habitasse platea dictumst. Lorem ipsum dolor sit amet,
consectetur adipiscing elit.
EOTEXT;
$overflow = str_repeat('M', 1024);
$metas = array(
'hi' => array(
'tip' => 'Hi',
),
'lorem (north)' => array(
'tip' => $lorem,
),
'lorem (east)' => array(
'tip' => $lorem,
'align' => 'E',
),
'lorem (south)' => array(
'tip' => $lorem,
'align' => 'S',
),
'lorem (west)' => array(
'tip' => $lorem,
'align' => 'W',
),
'lorem (large, north)' => array(
'tip' => $lorem,
'size' => 300,
),
'lorem (large, east)' => array(
'tip' => $lorem,
'size' => 300,
'align' => 'E',
),
'lorem (large, west)' => array(
'tip' => $lorem,
'size' => 300,
'align' => 'W',
),
'lorem (large, south)' => array(
'tip' => $lorem,
'size' => 300,
'align' => 'S',
),
'overflow (north)' => array(
'tip' => $overflow,
),
'overflow (east)' => array(
'tip' => $overflow,
'align' => 'E',
),
'overflow (south)' => array(
'tip' => $overflow,
'align' => 'S',
),
'overflow (west)' => array(
'tip' => $overflow,
'align' => 'W',
),
);
$content = array();
foreach ($metas as $key => $meta) {
- $content[] = javelin_render_tag(
+ $content[] = javelin_tag(
'div',
array(
'sigil' => 'has-tooltip',
'meta' => $meta,
'style' => $style,
),
- phutil_escape_html($key));
+ $key);
}
return $content;
}
}
diff --git a/src/applications/xhprof/view/PhabricatorXHProfProfileTopLevelView.php b/src/applications/xhprof/view/PhabricatorXHProfProfileTopLevelView.php
index cd20ac4f39..12326bb16d 100644
--- a/src/applications/xhprof/view/PhabricatorXHProfProfileTopLevelView.php
+++ b/src/applications/xhprof/view/PhabricatorXHProfProfileTopLevelView.php
@@ -1,142 +1,142 @@
<?php
/**
* @phutil-external-symbol function xhprof_compute_flat_info
*/
final class PhabricatorXHProfProfileTopLevelView
extends PhabricatorXHProfProfileView {
private $profileData;
private $limit;
private $file;
public function setProfileData(array $data) {
$this->profileData = $data;
return $this;
}
public function setLimit($limit) {
$this->limit = $limit;
return $this;
}
public function setFile(PhabricatorFile $file) {
$this->file = $file;
return $this;
}
public function render() {
DarkConsoleXHProfPluginAPI::includeXHProfLib();
$GLOBALS['display_calls'] = true;
$totals = array();
$flat = xhprof_compute_flat_info($this->profileData, $totals);
unset($GLOBALS['display_calls']);
$aggregated = array();
foreach ($flat as $call => $counters) {
$parts = explode('@', $call, 2);
$agg_call = reset($parts);
if (empty($aggregated[$agg_call])) {
$aggregated[$agg_call] = $counters;
} else {
foreach ($aggregated[$agg_call] as $key => $val) {
if ($key != 'wt') {
$aggregated[$agg_call][$key] += $counters[$key];
}
}
}
}
$flat = $aggregated;
$flat = isort($flat, 'wt');
$flat = array_reverse($flat);
$rows = array();
$rows[] = array(
'Total',
number_format($totals['ct']),
number_format($totals['wt']).' us',
'100.0%',
number_format($totals['wt']).' us',
'100.0%',
);
if ($this->limit) {
$flat = array_slice($flat, 0, $this->limit);
}
foreach ($flat as $call => $counters) {
$rows[] = array(
$this->renderSymbolLink($call),
number_format($counters['ct']),
number_format($counters['wt']).' us',
sprintf('%.1f%%', 100 * $counters['wt'] / $totals['wt']),
number_format($counters['excl_wt']).' us',
sprintf('%.1f%%', 100 * $counters['excl_wt'] / $totals['wt']),
);
}
Javelin::initBehavior('phabricator-tooltips');
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
'Symbol',
'Count',
- javelin_render_tag(
+ javelin_tag(
'span',
array(
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => 'Total wall time spent in this function and all of '.
'its children (children are other functions it called '.
'while executing).',
'size' => 200,
),
),
'Wall Time (Inclusive)'),
'%',
- javelin_render_tag(
+ javelin_tag(
'span',
array(
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => 'Wall time spent in this function, excluding time '.
'spent in children (children are other functions it '.
'called while executing).',
'size' => 200,
),
),
'Wall Time (Exclusive)'),
'%',
));
$table->setColumnClasses(
array(
'wide pri',
'n',
'n',
'n',
'n',
'n',
));
$panel = new AphrontPanelView();
$panel->setHeader('XHProf Profile');
if ($this->file) {
$panel->addButton(
phutil_tag(
'a',
array(
'href' => $this->file->getBestURI(),
'class' => 'green button',
),
'Download .xhprof Profile'));
}
$panel->appendChild($table);
return $panel->render();
}
}
diff --git a/src/infrastructure/diff/PhabricatorInlineCommentController.php b/src/infrastructure/diff/PhabricatorInlineCommentController.php
index 8b7b91f191..99fe0b70da 100644
--- a/src/infrastructure/diff/PhabricatorInlineCommentController.php
+++ b/src/infrastructure/diff/PhabricatorInlineCommentController.php
@@ -1,247 +1,247 @@
<?php
abstract class PhabricatorInlineCommentController
extends PhabricatorController {
abstract protected function createComment();
abstract protected function loadComment($id);
abstract protected function loadCommentForEdit($id);
private $changesetID;
private $isNewFile;
private $isOnRight;
private $lineNumber;
private $lineLength;
private $commentText;
private $operation;
private $commentID;
public function getCommentID() {
return $this->commentID;
}
public function getOperation() {
return $this->operation;
}
public function getCommentText() {
return $this->commentText;
}
public function getLineLength() {
return $this->lineLength;
}
public function getLineNumber() {
return $this->lineNumber;
}
public function getIsOnRight() {
return $this->isOnRight;
}
public function getChangesetID() {
return $this->changesetID;
}
public function getIsNewFile() {
return $this->isNewFile;
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$this->readRequestParameters();
switch ($this->getOperation()) {
case 'delete':
$inline = $this->loadCommentForEdit($this->getCommentID());
if ($request->isFormPost()) {
$inline->delete();
return $this->buildEmptyResponse();
}
$dialog = new AphrontDialogView();
$dialog->setUser($user);
$dialog->setSubmitURI($request->getRequestURI());
$dialog->setTitle('Really delete this comment?');
$dialog->addHiddenInput('id', $this->getCommentID());
$dialog->addHiddenInput('op', 'delete');
$dialog->appendChild('<p>Delete this inline comment?</p>');
$dialog->addCancelButton('#');
$dialog->addSubmitButton('Delete');
return id(new AphrontDialogResponse())->setDialog($dialog);
case 'edit':
$inline = $this->loadCommentForEdit($this->getCommentID());
$text = $this->getCommentText();
if ($request->isFormPost()) {
if (strlen($text)) {
$inline->setContent($text);
$inline->save();
return $this->buildRenderedCommentResponse(
$inline,
$this->getIsOnRight());
} else {
$inline->delete();
return $this->buildEmptyResponse();
}
}
$edit_dialog = $this->buildEditDialog();
$edit_dialog->setTitle('Edit Inline Comment');
$edit_dialog->addHiddenInput('id', $this->getCommentID());
$edit_dialog->addHiddenInput('op', 'edit');
$edit_dialog->appendChild(
$this->renderTextArea(
nonempty($text, $inline->getContent())));
return id(new AphrontAjaxResponse())
->setContent($edit_dialog->render());
case 'create':
$text = $this->getCommentText();
if (!$request->isFormPost() || !strlen($text)) {
return $this->buildEmptyResponse();
}
$inline = $this->createComment()
->setChangesetID($this->getChangesetID())
->setAuthorPHID($user->getPHID())
->setLineNumber($this->getLineNumber())
->setLineLength($this->getLineLength())
->setIsNewFile($this->getIsNewFile())
->setContent($text)
->save();
return $this->buildRenderedCommentResponse(
$inline,
$this->getIsOnRight());
case 'reply':
default:
$edit_dialog = $this->buildEditDialog();
if ($this->getOperation() == 'reply') {
$inline = $this->loadComment($this->getCommentID());
$edit_dialog->setTitle('Reply to Inline Comment');
$changeset = $inline->getChangesetID();
$is_new = $inline->getIsNewFile();
$number = $inline->getLineNumber();
$length = $inline->getLineLength();
} else {
$edit_dialog->setTitle('New Inline Comment');
$changeset = $this->getChangesetID();
$is_new = $this->getIsNewFile();
$number = $this->getLineNumber();
$length = $this->getLineLength();
}
$edit_dialog->addHiddenInput('op', 'create');
$edit_dialog->addHiddenInput('changeset', $changeset);
$edit_dialog->addHiddenInput('is_new', $is_new);
$edit_dialog->addHiddenInput('number', $number);
$edit_dialog->addHiddenInput('length', $length);
$text_area = $this->renderTextArea($this->getCommentText());
$edit_dialog->appendChild($text_area);
return id(new AphrontAjaxResponse())
->setContent($edit_dialog->render());
}
}
private function readRequestParameters() {
$request = $this->getRequest();
// NOTE: This isn't necessarily a DifferentialChangeset ID, just an
// application identifier for the changeset. In Diffusion, it's a Path ID.
$this->changesetID = $request->getInt('changeset');
$this->isNewFile = $request->getBool('is_new');
$this->isOnRight = $request->getBool('on_right');
$this->lineNumber = $request->getInt('number');
$this->lineLength = $request->getInt('length');
$this->commentText = $request->getStr('text');
$this->commentID = $request->getInt('id');
$this->operation = $request->getStr('op');
}
private function buildEditDialog() {
$request = $this->getRequest();
$user = $request->getUser();
$edit_dialog = new DifferentialInlineCommentEditView();
$edit_dialog->setUser($user);
$edit_dialog->setSubmitURI($request->getRequestURI());
$edit_dialog->setOnRight($this->getIsOnRight());
$edit_dialog->setNumber($this->getLineNumber());
$edit_dialog->setLength($this->getLineLength());
return $edit_dialog;
}
private function buildEmptyResponse() {
return id(new AphrontAjaxResponse())
->setContent(
array(
'markup' => '',
));
}
private function buildRenderedCommentResponse(
PhabricatorInlineCommentInterface $inline,
$on_right) {
$request = $this->getRequest();
$user = $request->getUser();
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($user);
$engine->addObject(
$inline,
PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
$engine->process();
$phids = array($user->getPHID());
$handles = $this->loadViewerHandles($phids);
$view = new DifferentialInlineCommentView();
$view->setInlineComment($inline);
$view->setOnRight($on_right);
$view->setBuildScaffolding(true);
$view->setMarkupEngine($engine);
$view->setHandles($handles);
$view->setEditable(true);
return id(new AphrontAjaxResponse())
->setContent(
array(
'inlineCommentID' => $inline->getID(),
'markup' => $view->render(),
));
}
private function renderTextArea($text) {
- return javelin_render_tag(
+ return javelin_tag(
'textarea',
array(
'class' => 'differential-inline-comment-edit-textarea',
'sigil' => 'differential-inline-comment-edit-textarea',
'name' => 'text',
),
- phutil_escape_html($text));
+ $text);
}
}
diff --git a/src/infrastructure/javelin/markup.php b/src/infrastructure/javelin/markup.php
index cff2e68d97..8c22002fe8 100644
--- a/src/infrastructure/javelin/markup.php
+++ b/src/infrastructure/javelin/markup.php
@@ -1,64 +1,76 @@
<?php
function javelin_render_tag(
$tag,
array $attributes = array(),
$content = null) {
+ if (is_array($content)) {
+ $content = implode('', $content);
+ }
+
+ $html = javelin_tag($tag, $attributes, phutil_safe_html($content));
+ return $html->getHTMLContent();
+}
+
+function javelin_tag(
+ $tag,
+ array $attributes = array(),
+ $content = null) {
+
if (isset($attributes['sigil']) ||
isset($attributes['meta']) ||
isset($attributes['mustcapture'])) {
foreach ($attributes as $k => $v) {
switch ($k) {
case 'sigil':
$attributes['data-sigil'] = $v;
unset($attributes[$k]);
break;
case 'meta':
$response = CelerityAPI::getStaticResourceResponse();
$id = $response->addMetadata($v);
$attributes['data-meta'] = $id;
unset($attributes[$k]);
break;
case 'mustcapture':
if ($v) {
$attributes['data-mustcapture'] = '1';
} else {
unset($attributes['data-mustcapture']);
}
unset($attributes[$k]);
break;
}
}
}
- return phutil_render_tag($tag, $attributes, $content);
+ return phutil_tag($tag, $attributes, $content);
}
-
function phabricator_render_form(PhabricatorUser $user, $attributes, $content) {
if (strcasecmp(idx($attributes, 'method'), 'POST') == 0 &&
!preg_match('#^(https?:|//)#', idx($attributes, 'action'))) {
$content = phabricator_render_form_magic($user).$content;
}
return javelin_render_tag('form', $attributes, $content);
}
function phabricator_render_form_magic(PhabricatorUser $user) {
return
phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => AphrontRequest::getCSRFTokenName(),
'value' => $user->getCSRFToken(),
)).
phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => '__form__',
'value' => true,
));
}
diff --git a/src/infrastructure/markup/rule/PhabricatorRemarkupRuleCountdown.php b/src/infrastructure/markup/rule/PhabricatorRemarkupRuleCountdown.php
index b2219ad428..548a2b1d64 100644
--- a/src/infrastructure/markup/rule/PhabricatorRemarkupRuleCountdown.php
+++ b/src/infrastructure/markup/rule/PhabricatorRemarkupRuleCountdown.php
@@ -1,74 +1,74 @@
<?php
/**
* @group markup
*/
final class PhabricatorRemarkupRuleCountdown extends PhutilRemarkupRule {
const KEY_RULE_COUNTDOWN = 'rule.countdown';
public function apply($text) {
return preg_replace_callback(
"@\B{C(\d+)}\B@",
array($this, 'markupCountdown'),
$text);
}
private function markupCountdown($matches) {
$countdown = id(new PhabricatorTimer())->load($matches[1]);
if (!$countdown) {
return $matches[0];
}
$id = celerity_generate_unique_node_id();
$engine = $this->getEngine();
$token = $engine->storeText('');
$metadata_key = self::KEY_RULE_COUNTDOWN;
$metadata = $engine->getTextMetadata($metadata_key, array());
$metadata[$id] = array($countdown->getDatepoint(), $token);
$engine->setTextMetadata($metadata_key, $metadata);
return $token;
}
public function didMarkupText() {
$engine = $this->getEngine();
$metadata_key = self::KEY_RULE_COUNTDOWN;
$metadata = $engine->getTextMetadata($metadata_key, array());
if (!$metadata) {
return;
}
require_celerity_resource('javelin-behavior-countdown-timer');
foreach ($metadata as $id => $info) {
list($time, $token) = $info;
$count = phutil_tag(
'span',
array(
'id' => $id,
),
array(
- javelin_render_tag('span',
+ javelin_tag('span',
array('sigil' => 'phabricator-timer-days'), '').'d',
- javelin_render_tag('span',
+ javelin_tag('span',
array('sigil' => 'phabricator-timer-hours'), '').'h',
- javelin_render_tag('span',
+ javelin_tag('span',
array('sigil' => 'phabricator-timer-minutes'), '').'m',
- javelin_render_tag('span',
+ javelin_tag('span',
array('sigil' => 'phabricator-timer-seconds'), '').'s',
));
Javelin::initBehavior('countdown-timer', array(
'timestamp' => $time,
'container' => $id,
));
$engine->overwriteStoredText($token, $count);
}
$engine->setTextMetadata($metadata_key, array());
}
}
diff --git a/src/infrastructure/markup/rule/PhabricatorRemarkupRuleEmbedFile.php b/src/infrastructure/markup/rule/PhabricatorRemarkupRuleEmbedFile.php
index 6426f27fa0..6adc7958f8 100644
--- a/src/infrastructure/markup/rule/PhabricatorRemarkupRuleEmbedFile.php
+++ b/src/infrastructure/markup/rule/PhabricatorRemarkupRuleEmbedFile.php
@@ -1,170 +1,170 @@
<?php
/**
* @group markup
*/
final class PhabricatorRemarkupRuleEmbedFile
extends PhutilRemarkupRule {
const KEY_RULE_EMBED_FILE = 'rule.embed.file';
const KEY_EMBED_FILE_PHIDS = 'phabricator.embedded-file-phids';
public function apply($text) {
return preg_replace_callback(
"@{F(\d+)([^}]+?)?}@",
array($this, 'markupEmbedFile'),
$text);
}
public function markupEmbedFile($matches) {
$file = null;
if ($matches[1]) {
// TODO: This is pretty inefficient if there are a bunch of files.
$file = id(new PhabricatorFile())->load($matches[1]);
}
if (!$file) {
return $matches[0];
}
$phid = $file->getPHID();
$engine = $this->getEngine();
$token = $engine->storeText('');
$metadata_key = self::KEY_RULE_EMBED_FILE;
$metadata = $engine->getTextMetadata($metadata_key, array());
$bundle = array('token' => $token);
$options = array(
'size' => 'thumb',
'layout' => 'left',
'float' => false,
'name' => null,
);
if (!empty($matches[2])) {
$matches[2] = trim($matches[2], ', ');
$parser = new PhutilSimpleOptions();
$options = $parser->parse($matches[2]) + $options;
}
$file_name = coalesce($options['name'], $file->getName());
$options['name'] = $file_name;
$attrs = array();
switch ((string)$options['size']) {
case 'full':
$attrs['src'] = $file->getBestURI();
$options['image_class'] = null;
break;
case 'thumb':
default:
$attrs['src'] = $file->getPreview220URI();
$options['image_class'] = 'phabricator-remarkup-embed-image';
break;
}
$bundle['attrs'] = $attrs;
$bundle['options'] = $options;
$bundle['meta'] = array(
'phid' => $file->getPHID(),
'viewable' => $file->isViewableImage(),
'uri' => $file->getBestURI(),
'dUri' => $file->getDownloadURI(),
'name' => $options['name'],
);
$metadata[$phid][] = $bundle;
$engine->setTextMetadata($metadata_key, $metadata);
return $token;
}
public function didMarkupText() {
$engine = $this->getEngine();
$metadata_key = self::KEY_RULE_EMBED_FILE;
$metadata = $engine->getTextMetadata($metadata_key, array());
if (!$metadata) {
return;
}
$file_phids = array();
foreach ($metadata as $phid => $bundles) {
foreach ($bundles as $data) {
$options = $data['options'];
$meta = $data['meta'];
if (!$meta['viewable'] || $options['layout'] == 'link') {
$link = id(new PhabricatorFileLinkView())
->setFilePHID($meta['phid'])
->setFileName($meta['name'])
->setFileDownloadURI($meta['dUri'])
->setFileViewURI($meta['uri'])
->setFileViewable($meta['viewable']);
$embed = $link->render();
$engine->overwriteStoredText($data['token'], $embed);
continue;
}
require_celerity_resource('lightbox-attachment-css');
$img = phutil_tag('img', $data['attrs']);
- $embed = javelin_render_tag(
+ $embed = javelin_tag(
'a',
array(
'href' => $meta['uri'],
'class' => $options['image_class'],
'sigil' => 'lightboxable',
'mustcapture' => true,
'meta' => $meta,
),
$img);
$layout_class = null;
switch ($options['layout']) {
case 'right':
case 'center':
case 'inline':
case 'left':
$layout_class = 'phabricator-remarkup-embed-layout-'.
$options['layout'];
break;
default:
$layout_class = 'phabricator-remarkup-embed-layout-left';
break;
}
if ($options['float']) {
switch ($options['layout']) {
case 'center':
case 'inline':
break;
case 'right':
$layout_class .= ' phabricator-remarkup-embed-float-right';
break;
case 'left':
default:
$layout_class .= ' phabricator-remarkup-embed-float-left';
break;
}
}
if ($layout_class) {
$embed = phutil_tag(
'div',
array(
'class' => $layout_class,
),
$embed);
}
$engine->overwriteStoredText($data['token'], $embed);
}
$file_phids[] = $phid;
}
$engine->setTextMetadata(self::KEY_EMBED_FILE_PHIDS, $file_phids);
$engine->setTextMetadata($metadata_key, array());
}
}
diff --git a/src/view/AphrontDialogView.php b/src/view/AphrontDialogView.php
index 266286cef2..e1ebad90c8 100644
--- a/src/view/AphrontDialogView.php
+++ b/src/view/AphrontDialogView.php
@@ -1,184 +1,184 @@
<?php
final class AphrontDialogView extends AphrontView {
private $title;
private $submitButton;
private $cancelURI;
private $cancelText = 'Cancel';
private $submitURI;
private $hidden = array();
private $class;
private $renderAsForm = true;
private $formID;
private $width = 'default';
const WIDTH_DEFAULT = 'default';
const WIDTH_FORM = 'form';
const WIDTH_FULL = 'full';
public function setSubmitURI($uri) {
$this->submitURI = $uri;
return $this;
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function getTitle() {
return $this->title;
}
public function addSubmitButton($text = 'Okay') {
$this->submitButton = $text;
return $this;
}
public function addCancelButton($uri, $text = 'Cancel') {
$this->cancelURI = $uri;
$this->cancelText = $text;
return $this;
}
public function addHiddenInput($key, $value) {
if (is_array($value)) {
foreach ($value as $hidden_key => $hidden_value) {
$this->hidden[] = array($key.'['.$hidden_key.']', $hidden_value);
}
} else {
$this->hidden[] = array($key, $value);
}
return $this;
}
public function setClass($class) {
$this->class = $class;
return $this;
}
public function setRenderDialogAsDiv() {
// TODO: This API is awkward.
$this->renderAsForm = false;
return $this;
}
public function setFormID($id) {
$this->formID = $id;
return $this;
}
public function setWidth($width) {
$this->width = $width;
return $this;
}
final public function render() {
require_celerity_resource('aphront-dialog-view-css');
$buttons = array();
if ($this->submitButton) {
- $buttons[] = javelin_render_tag(
+ $buttons[] = javelin_tag(
'button',
array(
'name' => '__submit__',
'sigil' => '__default__',
),
- phutil_escape_html($this->submitButton));
+ $this->submitButton);
}
if ($this->cancelURI) {
- $buttons[] = javelin_render_tag(
+ $buttons[] = javelin_tag(
'a',
array(
'href' => $this->cancelURI,
'class' => 'button grey',
'name' => '__cancel__',
'sigil' => 'jx-workflow-button',
),
- phutil_escape_html($this->cancelText));
+ $this->cancelText);
}
$buttons = implode('', $buttons);
if (!$this->user) {
throw new Exception(
"You must call setUser() when rendering an AphrontDialogView.");
}
$more = $this->class;
switch ($this->width) {
case self::WIDTH_FORM:
case self::WIDTH_FULL:
$more .= ' aphront-dialog-view-width-'.$this->width;
break;
case self::WIDTH_DEFAULT:
break;
default:
throw new Exception("Unknown dialog width '{$this->width}'!");
}
$attributes = array(
'class' => 'aphront-dialog-view '.$more,
'sigil' => 'jx-dialog',
);
$form_attributes = array(
'action' => $this->submitURI,
'method' => 'post',
'id' => $this->formID,
);
$hidden_inputs = array();
foreach ($this->hidden as $desc) {
list($key, $value) = $desc;
- $hidden_inputs[] = javelin_render_tag(
+ $hidden_inputs[] = javelin_tag(
'input',
array(
'type' => 'hidden',
'name' => $key,
'value' => $value,
'sigil' => 'aphront-dialog-application-input'
));
}
$hidden_inputs = implode("\n", $hidden_inputs);
$hidden_inputs =
'<input type="hidden" name="__dialog__" value="1" />'.
$hidden_inputs;
if (!$this->renderAsForm) {
$buttons = phabricator_render_form(
$this->user,
$form_attributes,
$hidden_inputs.$buttons);
}
$content =
'<div class="aphront-dialog-head">'.
phutil_escape_html($this->title).
'</div>'.
'<div class="aphront-dialog-body">'.
$this->renderChildren().
'</div>'.
'<div class="aphront-dialog-tail">'.
$buttons.
'<div style="clear: both;"></div>'.
'</div>';
if ($this->renderAsForm) {
return phabricator_render_form(
$this->user,
$form_attributes + $attributes,
$hidden_inputs.
$content);
} else {
return javelin_render_tag(
'div',
$attributes,
$content);
}
}
}
diff --git a/src/view/control/AphrontAttachedFileView.php b/src/view/control/AphrontAttachedFileView.php
index a728515387..a3e1cad357 100644
--- a/src/view/control/AphrontAttachedFileView.php
+++ b/src/view/control/AphrontAttachedFileView.php
@@ -1,57 +1,57 @@
<?php
final class AphrontAttachedFileView extends AphrontView {
private $file;
public function setFile(PhabricatorFile $file) {
$this->file = $file;
return $this;
}
public function render() {
require_celerity_resource('aphront-attached-file-view-css');
$file = $this->file;
$phid = $file->getPHID();
$thumb = phutil_tag(
'img',
array(
'src' => $file->getThumb60x45URI(),
'width' => 60,
'height' => 45,
));
$name = phutil_tag(
'a',
array(
'href' => $file->getViewURI(),
'target' => '_blank',
),
$file->getName());
$size = number_format($file->getByteSize()).' bytes';
- $remove = javelin_render_tag(
+ $remove = javelin_tag(
'a',
array(
'class' => 'button grey',
'sigil' => 'aphront-attached-file-view-remove',
// NOTE: Using 'ref' here instead of 'meta' because the file upload
// endpoint doesn't receive request metadata and thus can't generate
// a valid response with node metadata.
'ref' => $file->getPHID(),
),
"\xE2\x9C\x96"); // "Heavy Multiplication X"
return
'<table class="aphront-attached-file-view">
<tr>
<td>'.$thumb.'</td>
<th><strong>'.$name.'</strong><br />'.$size.'</th>
<td class="aphront-attached-file-view-remove">'.$remove.'</td>
</tr>
</table>';
}
}
diff --git a/src/view/control/AphrontTokenizerTemplateView.php b/src/view/control/AphrontTokenizerTemplateView.php
index 178b313873..d80fe2566c 100644
--- a/src/view/control/AphrontTokenizerTemplateView.php
+++ b/src/view/control/AphrontTokenizerTemplateView.php
@@ -1,92 +1,92 @@
<?php
final class AphrontTokenizerTemplateView extends AphrontView {
private $value;
private $name;
private $id;
public function setID($id) {
$this->id = $id;
return $this;
}
public function setValue(array $value) {
$this->value = $value;
return $this;
}
public function getValue() {
return $this->value;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function render() {
require_celerity_resource('aphront-tokenizer-control-css');
$id = $this->id;
$name = $this->getName();
$values = nonempty($this->getValue(), array());
$tokens = array();
foreach ($values as $key => $value) {
$tokens[] = $this->renderToken($key, $value);
}
- $input = javelin_render_tag(
+ $input = javelin_tag(
'input',
array(
'mustcapture' => true,
'name' => $name,
'class' => 'jx-tokenizer-input',
'sigil' => 'tokenizer-input',
'style' => 'width: 0px;',
'disabled' => 'disabled',
'type' => 'text',
));
$content = $tokens;
$content[] = $input;
$content[] = phutil_tag('div', array('style' => 'clear: both;'), '');
return phutil_tag(
'div',
array(
'id' => $id,
'class' => 'jx-tokenizer-container',
),
$content);
}
private function renderToken($key, $value) {
$input_name = $this->getName();
if ($input_name) {
$input_name .= '[]';
}
return phutil_tag(
'a',
array(
'class' => 'jx-tokenizer-token',
),
array(
$value,
phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => $input_name,
'value' => $key,
)).
phutil_tag('span', array('class' => 'jx-tokenizer-x-placeholder'), ''),
));
}
}
diff --git a/src/view/control/AphrontTypeaheadTemplateView.php b/src/view/control/AphrontTypeaheadTemplateView.php
index 5e27c682cf..e536a7a7cf 100644
--- a/src/view/control/AphrontTypeaheadTemplateView.php
+++ b/src/view/control/AphrontTypeaheadTemplateView.php
@@ -1,65 +1,67 @@
<?php
final class AphrontTypeaheadTemplateView extends AphrontView {
private $value;
private $name;
private $id;
public function setID($id) {
$this->id = $id;
return $this;
}
public function setValue(array $value) {
$this->value = $value;
return $this;
}
public function getValue() {
return $this->value;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function render() {
require_celerity_resource('aphront-typeahead-control-css');
$id = $this->id;
$name = $this->getName();
$values = nonempty($this->getValue(), array());
$tokens = array();
foreach ($values as $key => $value) {
$tokens[] = $this->renderToken($key, $value);
}
- $input = javelin_render_tag(
+ $input = javelin_tag(
'input',
array(
'name' => $name,
'class' => 'jx-typeahead-input',
'sigil' => 'typeahead',
'type' => 'text',
'value' => $this->value,
'autocomplete' => 'off',
));
- return javelin_render_tag(
+ return javelin_tag(
'div',
array(
'id' => $id,
'sigil' => 'typeahead-hardpoint',
'class' => 'jx-typeahead-hardpoint',
),
- $input.
- '<div style="clear: both;"></div>');
+ array(
+ $input,
+ phutil_tag('div', array('style' => 'clear: both'), ''),
+ ));
}
}
diff --git a/src/view/form/control/AphrontFormDateControl.php b/src/view/form/control/AphrontFormDateControl.php
index aefdafbd75..afe989f854 100644
--- a/src/view/form/control/AphrontFormDateControl.php
+++ b/src/view/form/control/AphrontFormDateControl.php
@@ -1,266 +1,266 @@
<?php
final class AphrontFormDateControl extends AphrontFormControl {
private $initialTime;
private $valueDay;
private $valueMonth;
private $valueYear;
private $valueTime;
const TIME_START_OF_DAY = 'start-of-day';
const TIME_END_OF_DAY = 'end-of-day';
const TIME_START_OF_BUSINESS = 'start-of-business';
const TIME_END_OF_BUSINESS = 'end-of-business';
public function setInitialTime($time) {
$this->initialTime = $time;
return $this;
}
public function readValueFromRequest(AphrontRequest $request) {
$user = $this->user;
if (!$this->user) {
throw new Exception(
"Call setUser() before readValueFromRequest()!");
}
$user_zone = $user->getTimezoneIdentifier();
$zone = new DateTimeZone($user_zone);
$day = $request->getInt($this->getDayInputName());
$month = $request->getInt($this->getMonthInputName());
$year = $request->getInt($this->getYearInputName());
$time = $request->getStr($this->getTimeInputName());
$err = $this->getError();
if ($day || $month || $year || $time) {
$this->valueDay = $day;
$this->valueMonth = $month;
$this->valueYear = $year;
$this->valueTime = $time;
// Assume invalid.
$err = 'Invalid';
try {
$date = new DateTime("{$year}-{$month}-{$day} {$time}", $zone);
$value = $date->format('U');
} catch (Exception $ex) {
$value = null;
}
if ($value) {
$this->setValue($value);
$err = null;
} else {
$this->setValue(null);
}
} else {
// TODO: We could eventually allow these to be customized per install or
// per user or both, but let's wait and see.
switch ($this->initialTime) {
case self::TIME_START_OF_DAY:
default:
$time = '12:00 AM';
break;
case self::TIME_START_OF_BUSINESS:
$time = '9:00 AM';
break;
case self::TIME_END_OF_BUSINESS:
$time = '5:00 PM';
break;
case self::TIME_END_OF_DAY:
$time = '11:59 PM';
break;
}
$today = $this->formatTime(time(), 'Y-m-d');
try {
$date = new DateTime("{$today} {$time}", $zone);
$value = $date->format('U');
} catch (Exception $ex) {
$value = null;
}
if ($value) {
$this->setValue($value);
} else {
$this->setValue(null);
}
}
$this->setError($err);
return $this->getValue();
}
protected function getCustomControlClass() {
return 'aphront-form-control-date';
}
public function setValue($epoch) {
$result = parent::setValue($epoch);
if ($epoch === null) {
return;
}
$readable = $this->formatTime($epoch, 'Y!m!d!g:i A');
$readable = explode('!', $readable, 4);
$this->valueYear = $readable[0];
$this->valueMonth = $readable[1];
$this->valueDay = $readable[2];
$this->valueTime = $readable[3];
return $result;
}
private function getMinYear() {
$cur_year = $this->formatTime(
time(),
'Y');
$val_year = $this->getYearInputValue();
return min($cur_year, $val_year) - 3;
}
private function getMaxYear() {
$cur_year = $this->formatTime(
time(),
'Y');
$val_year = $this->getYearInputValue();
return max($cur_year, $val_year) + 3;
}
private function getDayInputValue() {
return $this->valueDay;
}
private function getMonthInputValue() {
return $this->valueMonth;
}
private function getYearInputValue() {
return $this->valueYear;
}
private function getTimeInputValue() {
return $this->valueTime;
}
private function formatTime($epoch, $fmt) {
return phabricator_format_local_time(
$epoch,
$this->user,
$fmt);
}
private function getDayInputName() {
return $this->getName().'_d';
}
private function getMonthInputName() {
return $this->getName().'_m';
}
private function getYearInputName() {
return $this->getName().'_y';
}
private function getTimeInputName() {
return $this->getName().'_t';
}
protected function renderInput() {
$min_year = $this->getMinYear();
$max_year = $this->getMaxYear();
$days = range(1, 31);
$days = array_combine($days, $days);
$months = array(
1 => 'Jan',
2 => 'Feb',
3 => 'Mar',
4 => 'Apr',
5 => 'May',
6 => 'Jun',
7 => 'Jul',
8 => 'Aug',
9 => 'Sep',
10 => 'Oct',
11 => 'Nov',
12 => 'Dec',
);
$years = range($this->getMinYear(), $this->getMaxYear());
$years = array_combine($years, $years);
$days_sel = AphrontFormSelectControl::renderSelectTag(
$this->getDayInputValue(),
$days,
array(
'name' => $this->getDayInputName(),
'sigil' => 'day-input',
));
$months_sel = AphrontFormSelectControl::renderSelectTag(
$this->getMonthInputValue(),
$months,
array(
'name' => $this->getMonthInputName(),
'sigil' => 'month-input',
));
$years_sel = AphrontFormSelectControl::renderSelectTag(
$this->getYearInputValue(),
$years,
array(
'name' => $this->getYearInputName(),
'sigil' => 'year-input',
));
- $cal_icon = javelin_render_tag(
+ $cal_icon = javelin_tag(
'a',
array(
'href' => '#',
'class' => 'calendar-button',
'sigil' => 'calendar-button',
),
'');
$time_sel = phutil_tag(
'input',
array(
'name' => $this->getTimeInputName(),
'sigil' => 'time-input',
'value' => $this->getTimeInputValue(),
'type' => 'text',
'class' => 'aphront-form-date-time-input',
),
'');
Javelin::initBehavior('fancy-datepicker', array());
return javelin_render_tag(
'div',
array(
'class' => 'aphront-form-date-container',
'sigil' => 'phabricator-date-control',
),
self::renderSingleView(
array(
$days_sel,
$months_sel,
$years_sel,
$cal_icon,
$time_sel,
)));
}
}
diff --git a/src/view/form/control/AphrontFormTextControl.php b/src/view/form/control/AphrontFormTextControl.php
index 76b3cbb32e..7ba47a5728 100644
--- a/src/view/form/control/AphrontFormTextControl.php
+++ b/src/view/form/control/AphrontFormTextControl.php
@@ -1,41 +1,41 @@
<?php
final class AphrontFormTextControl extends AphrontFormControl {
private $disableAutocomplete;
private $sigil;
public function setDisableAutocomplete($disable) {
$this->disableAutocomplete = $disable;
return $this;
}
private function getDisableAutocomplete() {
return $this->disableAutocomplete;
}
public function getSigil() {
return $this->sigil;
}
public function setSigil($sigil) {
$this->sigil = $sigil;
return $this;
}
protected function getCustomControlClass() {
return 'aphront-form-control-text';
}
protected function renderInput() {
- return javelin_render_tag(
+ return javelin_tag(
'input',
array(
'type' => 'text',
'name' => $this->getName(),
'value' => $this->getValue(),
'disabled' => $this->getDisabled() ? 'disabled' : null,
'autocomplete' => $this->getDisableAutocomplete() ? 'off' : null,
'id' => $this->getID(),
'sigil' => $this->getSigil(),
));
}
}
diff --git a/src/view/form/control/PhabricatorRemarkupControl.php b/src/view/form/control/PhabricatorRemarkupControl.php
index 1272007c0c..be41fdf655 100644
--- a/src/view/form/control/PhabricatorRemarkupControl.php
+++ b/src/view/form/control/PhabricatorRemarkupControl.php
@@ -1,154 +1,154 @@
<?php
final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl {
protected function renderInput() {
$id = $this->getID();
if (!$id) {
$id = celerity_generate_unique_node_id();
$this->setID($id);
}
// We need to have this if previews render images, since Ajax can not
// currently ship JS or CSS.
require_celerity_resource('lightbox-attachment-css');
Javelin::initBehavior(
'aphront-drag-and-drop-textarea',
array(
'target' => $id,
'activatedClass' => 'aphront-textarea-drag-and-drop',
'uri' => '/file/dropupload/',
));
Javelin::initBehavior('phabricator-remarkup-assist', array());
Javelin::initBehavior('phabricator-tooltips', array());
$actions = array(
'b' => array(
'tip' => pht('Bold'),
),
'i' => array(
'tip' => pht('Italics'),
),
'tt' => array(
'tip' => pht('Monospaced'),
),
array(
'spacer' => true,
),
'ul' => array(
'tip' => pht('Bulleted List'),
),
'ol' => array(
'tip' => pht('Numbered List'),
),
'code' => array(
'tip' => pht('Code Block'),
),
'table' => array(
'tip' => pht('Table'),
),
array(
'spacer' => true,
),
'meme' => array(
'tip' => pht('Meme'),
),
'help' => array(
'tip' => pht('Help'),
'align' => 'right',
'href' => PhabricatorEnv::getDoclink(
'article/Remarkup_Reference.html'),
),
);
$buttons = array();
foreach ($actions as $action => $spec) {
if (idx($spec, 'spacer')) {
$buttons[] = phutil_tag(
'span',
array(
'class' => 'remarkup-assist-separator',
),
'');
continue;
}
$classes = array();
$classes[] = 'remarkup-assist-button';
if (idx($spec, 'align') == 'right') {
$classes[] = 'remarkup-assist-right';
}
$href = idx($spec, 'href', '#');
if ($href == '#') {
$meta = array('action' => $action);
$mustcapture = true;
$target = null;
} else {
$meta = array();
$mustcapture = null;
$target = '_blank';
}
$tip = idx($spec, 'tip');
if ($tip) {
$meta['tip'] = $tip;
}
require_celerity_resource('sprite-icon-css');
- $buttons[] = javelin_render_tag(
+ $buttons[] = javelin_tag(
'a',
array(
'class' => implode(' ', $classes),
'href' => $href,
'sigil' => 'remarkup-assist has-tooltip',
'meta' => $meta,
'mustcapture' => $mustcapture,
'target' => $target,
'tabindex' => -1,
),
phutil_tag(
'div',
array(
'class' => 'remarkup-assist sprite-icon remarkup-assist-'.$action,
),
''));
}
$buttons = phutil_tag(
'div',
array(
'class' => 'remarkup-assist-bar',
),
$buttons);
$monospaced_textareas = null;
$monospaced_textareas_class = null;
$user = $this->getUser();
if ($user) {
$monospaced_textareas = $user
->loadPreferences()
->getPreference(
PhabricatorUserPreferences::PREFERENCE_MONOSPACED_TEXTAREAS);
if ($monospaced_textareas == 'enabled') {
$monospaced_textareas_class = 'PhabricatorMonospaced';
}
}
$this->setCustomClass(
'remarkup-assist-textarea '.$monospaced_textareas_class);
return javelin_render_tag(
'div',
array(
'sigil' => 'remarkup-assist-control',
),
$buttons.
parent::renderInput());
}
}
diff --git a/src/view/layout/AphrontMoreView.php b/src/view/layout/AphrontMoreView.php
index 09f9a099b0..3eb89714a5 100644
--- a/src/view/layout/AphrontMoreView.php
+++ b/src/view/layout/AphrontMoreView.php
@@ -1,55 +1,55 @@
<?php
final class AphrontMoreView extends AphrontView {
private $some;
private $more;
private $expandtext;
public function setSome($some) {
$this->some = $some;
return $this;
}
public function setMore($more) {
$this->more = $more;
return $this;
}
public function setExpandText($text) {
$this->expandtext = $text;
return $this;
}
public function render() {
$some = $this->some;
$text = "(Show More\xE2\x80\xA6)";
if ($this->expandtext !== null) {
$text = $this->expandtext;
}
$link = null;
if ($this->more && $this->more != $this->some) {
Javelin::initBehavior('aphront-more');
- $link = ' '.javelin_render_tag(
+ $link = ' '.javelin_tag(
'a',
array(
'sigil' => 'aphront-more-view-show-more',
'mustcapture' => true,
'href' => '#',
'meta' => array(
'more' => $this->more,
),
),
$text);
}
return javelin_render_tag(
'div',
array(
'sigil' => 'aphront-more-view',
),
$some.$link);
}
}
diff --git a/src/view/layout/PhabricatorActionView.php b/src/view/layout/PhabricatorActionView.php
index b5a6cf5792..53c2c1e943 100644
--- a/src/view/layout/PhabricatorActionView.php
+++ b/src/view/layout/PhabricatorActionView.php
@@ -1,138 +1,138 @@
<?php
final class PhabricatorActionView extends AphrontView {
private $name;
private $icon;
private $href;
private $disabled;
private $workflow;
private $renderAsForm;
public function setHref($href) {
$this->href = $href;
return $this;
}
public function setIcon($icon) {
$this->icon = $icon;
return $this;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function setDisabled($disabled) {
$this->disabled = $disabled;
return $this;
}
public function setWorkflow($workflow) {
$this->workflow = $workflow;
return $this;
}
public function setRenderAsForm($form) {
$this->renderAsForm = $form;
return $this;
}
public function render() {
$icon = null;
if ($this->icon) {
$suffix = '';
if ($this->disabled) {
$suffix = '-grey';
}
require_celerity_resource('sprite-icon-css');
$icon = phutil_tag(
'span',
array(
'class' => 'phabricator-action-view-icon sprite-icon '.
'action-'.$this->icon.$suffix,
),
'');
}
if ($this->href) {
if ($this->renderAsForm) {
if (!$this->user) {
throw new Exception(
'Call setUser() when rendering an action as a form.');
}
- $item = javelin_render_tag(
+ $item = javelin_tag(
'button',
array(
'class' => 'phabricator-action-view-item',
),
- phutil_escape_html($this->name));
+ $this->name);
$item = phabricator_render_form(
$this->user,
array(
'action' => $this->href,
'method' => 'POST',
'sigil' => $this->workflow ? 'workflow' : null,
),
$item);
} else {
- $item = javelin_render_tag(
+ $item = javelin_tag(
'a',
array(
'href' => $this->href,
'class' => 'phabricator-action-view-item',
'sigil' => $this->workflow ? 'workflow' : null,
),
- phutil_escape_html($this->name));
+ $this->name);
}
} else {
$item = phutil_tag(
'span',
array(
'class' => 'phabricator-action-view-item',
),
$this->name);
}
$classes = array();
$classes[] = 'phabricator-action-view';
if ($this->disabled) {
$classes[] = 'phabricator-action-view-disabled';
}
return phutil_tag(
'li',
array(
'class' => implode(' ', $classes),
),
array($icon, $item));
}
public static function getAvailableIcons() {
$root = dirname(phutil_get_library_root('phabricator'));
$path = $root.'/resources/sprite/manifest/icon.json';
$data = Filesystem::readFile($path);
$manifest = json_decode($data, true);
$results = array();
$prefix = 'action-';
foreach ($manifest['sprites'] as $sprite) {
$name = $sprite['name'];
if (preg_match('/-(white|grey)$/', $name)) {
continue;
}
if (!strncmp($name, $prefix, strlen($prefix))) {
$results[] = substr($name, strlen($prefix));
}
}
return $results;
}
}
diff --git a/src/view/layout/PhabricatorAnchorView.php b/src/view/layout/PhabricatorAnchorView.php
index 2f642fab3d..4650a91b98 100644
--- a/src/view/layout/PhabricatorAnchorView.php
+++ b/src/view/layout/PhabricatorAnchorView.php
@@ -1,45 +1,45 @@
<?php
final class PhabricatorAnchorView extends AphrontView {
private $anchorName;
private $navigationMarker;
public function setAnchorName($name) {
$this->anchorName = $name;
return $this;
}
public function setNavigationMarker($marker) {
$this->navigationMarker = $marker;
return $this;
}
public function render() {
$marker = null;
if ($this->navigationMarker) {
- $marker = javelin_render_tag(
+ $marker = javelin_tag(
'legend',
array(
'class' => 'phabricator-anchor-navigation-marker',
'sigil' => 'marker',
'meta' => array(
'anchor' => $this->anchorName,
),
),
'');
}
$anchor = phutil_tag(
'a',
array(
'name' => $this->anchorName,
'id' => $this->anchorName,
'class' => 'phabricator-anchor-view',
),
'');
return $marker.$anchor;
}
}
diff --git a/src/view/layout/PhabricatorCrumbsView.php b/src/view/layout/PhabricatorCrumbsView.php
index 457b0221cd..0ac55bb46f 100644
--- a/src/view/layout/PhabricatorCrumbsView.php
+++ b/src/view/layout/PhabricatorCrumbsView.php
@@ -1,72 +1,75 @@
<?php
final class PhabricatorCrumbsView extends AphrontView {
private $crumbs = array();
private $actions = array();
protected function canAppendChild() {
return false;
}
public function addCrumb(PhabricatorCrumbView $crumb) {
$this->crumbs[] = $crumb;
return $this;
}
public function addAction(PhabricatorMenuItemView $action) {
$this->actions[] = $action;
return $this;
}
public function render() {
require_celerity_resource('phabricator-crumbs-view-css');
$action_view = null;
if ($this->actions) {
$actions = array();
foreach ($this->actions as $action) {
$icon = null;
if ($action->getIcon()) {
$icon = phutil_tag(
'span',
array(
'class' => 'sprite-icon action-'.$action->getIcon(),
),
'');
}
- $actions[] = javelin_render_tag(
+ $actions[] = javelin_tag(
'a',
array(
'href' => $action->getHref(),
'class' => 'phabricator-crumbs-action',
'sigil' => $action->getWorkflow() ? 'workflow' : null,
),
- $icon.phutil_escape_html($action->getName()));
+ array(
+ $icon,
+ $action->getName(),
+ ));
}
$action_view = phutil_render_tag(
'div',
array(
'class' => 'phabricator-crumbs-actions',
),
self::renderSingleView($actions));
}
if ($this->crumbs) {
last($this->crumbs)->setIsLastCrumb(true);
}
return phutil_render_tag(
'div',
array(
'class' => 'phabricator-crumbs-view '.
'sprite-gradient gradient-breadcrumbs',
),
$action_view.
self::renderSingleView($this->crumbs));
}
}
diff --git a/src/view/layout/PhabricatorFileLinkView.php b/src/view/layout/PhabricatorFileLinkView.php
index 6869e9cd35..ff3434d872 100644
--- a/src/view/layout/PhabricatorFileLinkView.php
+++ b/src/view/layout/PhabricatorFileLinkView.php
@@ -1,82 +1,81 @@
<?php
final class PhabricatorFileLinkView extends AphrontView {
private $fileName;
private $fileDownloadURI;
private $fileViewURI;
private $fileViewable;
private $filePHID;
public function setFilePHID($file_phid) {
$this->filePHID = $file_phid;
return $this;
}
private function getFilePHID() {
return $this->filePHID;
}
public function setFileViewable($file_viewable) {
$this->fileViewable = $file_viewable;
return $this;
}
private function getFileViewable() {
return $this->fileViewable;
}
public function setFileViewURI($file_view_uri) {
$this->fileViewURI = $file_view_uri;
return $this;
}
private function getFileViewURI() {
return $this->fileViewURI;
}
public function setFileDownloadURI($file_download_uri) {
$this->fileDownloadURI = $file_download_uri;
return $this;
}
private function getFileDownloadURI() {
return $this->fileDownloadURI;
}
public function setFileName($file_name) {
$this->fileName = $file_name;
return $this;
}
private function getFileName() {
return $this->fileName;
}
public function render() {
require_celerity_resource('phabricator-remarkup-css');
require_celerity_resource('lightbox-attachment-css');
$sigil = null;
$meta = null;
$mustcapture = false;
if ($this->getFileViewable()) {
$mustcapture = true;
$sigil = 'lightboxable';
$meta = array(
'phid' => $this->getFilePHID(),
'viewable' => $this->getFileViewable(),
'uri' => $this->getFileViewURI(),
'dUri' => $this->getFileDownloadURI(),
'name' => $this->getFileName(),
);
}
- return javelin_render_tag(
+ return javelin_tag(
'a',
array(
'href' => $this->getFileViewURI(),
'class' => 'phabricator-remarkup-embed-layout-link',
'sigil' => $sigil,
'meta' => $meta,
'mustcapture' => $mustcapture,
),
- phutil_escape_html($this->getFileName())
- );
+ $this->getFileName());
}
}
diff --git a/src/view/layout/PhabricatorTimelineEventView.php b/src/view/layout/PhabricatorTimelineEventView.php
index 63f7af3cdc..792c71da92 100644
--- a/src/view/layout/PhabricatorTimelineEventView.php
+++ b/src/view/layout/PhabricatorTimelineEventView.php
@@ -1,300 +1,300 @@
<?php
final class PhabricatorTimelineEventView extends AphrontView {
private $userHandle;
private $title;
private $icon;
private $color;
private $classes = array();
private $contentSource;
private $dateCreated;
private $anchor;
private $isEditable;
private $isEdited;
private $transactionPHID;
private $isPreview;
public function setTransactionPHID($transaction_phid) {
$this->transactionPHID = $transaction_phid;
return $this;
}
public function getTransactionPHID() {
return $this->transactionPHID;
}
public function setIsEdited($is_edited) {
$this->isEdited = $is_edited;
return $this;
}
public function getIsEdited() {
return $this->isEdited;
}
public function setIsPreview($is_preview) {
$this->isPreview = $is_preview;
return $this;
}
public function getIsPreview() {
return $this->isPreview;
}
public function setIsEditable($is_editable) {
$this->isEditable = $is_editable;
return $this;
}
public function getIsEditable() {
return $this->isEditable;
}
public function setDateCreated($date_created) {
$this->dateCreated = $date_created;
return $this;
}
public function getDateCreated() {
return $this->dateCreated;
}
public function setContentSource(PhabricatorContentSource $content_source) {
$this->contentSource = $content_source;
return $this;
}
public function getContentSource() {
return $this->contentSource;
}
public function setUserHandle(PhabricatorObjectHandle $handle) {
$this->userHandle = $handle;
return $this;
}
public function setAnchor($anchor) {
$this->anchor = $anchor;
return $this;
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function addClass($class) {
$this->classes[] = $class;
return $this;
}
public function setIcon($icon) {
$this->icon = $icon;
return $this;
}
public function setColor($color) {
$this->color = $color;
return $this;
}
public function render() {
$content = $this->renderChildren();
$title = $this->title;
if (($title === null) && !strlen($content)) {
$title = '';
}
$extra = $this->renderExtra();
if ($title !== null || $extra !== null) {
$title_classes = array();
$title_classes[] = 'phabricator-timeline-title';
$icon = null;
if ($this->icon) {
$title_classes[] = 'phabricator-timeline-title-with-icon';
$icon = phutil_tag(
'span',
array(
'class' => 'phabricator-timeline-icon-fill',
),
phutil_tag(
'span',
array(
'class' => 'phabricator-timeline-icon sprite-icon '.
'action-'.$this->icon.'-white',
),
''));
}
$title = phutil_render_tag(
'div',
array(
'class' => implode(' ', $title_classes),
),
$title.$extra);
$title = $icon.$title;
}
$wedge = phutil_tag(
'div',
array(
'class' => 'phabricator-timeline-wedge phabricator-timeline-border',
),
'');
$image_uri = $this->userHandle->getImageURI();
$image = phutil_tag(
'div',
array(
'style' => 'background-image: url('.$image_uri.')',
'class' => 'phabricator-timeline-image',
),
'');
$content_classes = array();
$content_classes[] = 'phabricator-timeline-content';
$classes = array();
$classes[] = 'phabricator-timeline-event-view';
$classes[] = 'phabricator-timeline-border';
if ($content) {
$classes[] = 'phabricator-timeline-major-event';
$content = phutil_render_tag(
'div',
array(
'class' => implode(' ', $content_classes),
),
phutil_render_tag(
'div',
array(
'class' => 'phabricator-timeline-inner-content',
),
$title.
phutil_render_tag(
'div',
array(
'class' => 'phabricator-timeline-core-content',
),
$content)));
$content = $image.$wedge.$content;
} else {
$classes[] = 'phabricator-timeline-minor-event';
$content = phutil_render_tag(
'div',
array(
'class' => implode(' ', $content_classes),
),
$image.$wedge.$title);
}
$outer_classes = $this->classes;
$outer_classes[] = 'phabricator-timeline-shell';
if ($this->color) {
$outer_classes[] = 'phabricator-timeline-'.$this->color;
}
$sigil = null;
$meta = null;
if ($this->getTransactionPHID()) {
$sigil = 'transaction';
$meta = array(
'phid' => $this->getTransactionPHID(),
'anchor' => $this->anchor,
);
}
return javelin_render_tag(
'div',
array(
'class' => implode(' ', $outer_classes),
'id' => $this->anchor ? 'anchor-'.$this->anchor : null,
'sigil' => $sigil,
'meta' => $meta,
),
phutil_render_tag(
'div',
array(
'class' => implode(' ', $classes),
),
$content));
}
private function renderExtra() {
$extra = array();
if ($this->getIsPreview()) {
$extra[] = pht('PREVIEW');
} else {
$xaction_phid = $this->getTransactionPHID();
if ($this->getIsEdited()) {
- $extra[] = javelin_render_tag(
+ $extra[] = javelin_tag(
'a',
array(
'href' => '/transactions/history/'.$xaction_phid.'/',
'sigil' => 'workflow',
),
pht('Edited'));
}
if ($this->getIsEditable()) {
- $extra[] = javelin_render_tag(
+ $extra[] = javelin_tag(
'a',
array(
'href' => '/transactions/edit/'.$xaction_phid.'/',
'sigil' => 'workflow transaction-edit',
),
pht('Edit'));
}
$source = $this->getContentSource();
if ($source) {
$extra[] = id(new PhabricatorContentSourceView())
->setContentSource($source)
->setUser($this->getUser())
->render();
}
if ($this->getDateCreated()) {
$date = phabricator_datetime(
$this->getDateCreated(),
$this->getUser());
if ($this->anchor) {
Javelin::initBehavior('phabricator-watch-anchor');
$anchor = id(new PhabricatorAnchorView())
->setAnchorName($this->anchor)
->render();
$date = $anchor.phutil_tag(
'a',
array(
'href' => '#'.$this->anchor,
),
$date);
}
$extra[] = $date;
}
}
$extra = implode(' &middot; ', $extra);
if ($extra) {
$extra = phutil_render_tag(
'span',
array(
'class' => 'phabricator-timeline-extra',
),
$extra);
}
return $extra;
}
}
diff --git a/src/view/layout/headsup/AphrontHeadsupActionView.php b/src/view/layout/headsup/AphrontHeadsupActionView.php
index 6371be8698..eaffca17a2 100644
--- a/src/view/layout/headsup/AphrontHeadsupActionView.php
+++ b/src/view/layout/headsup/AphrontHeadsupActionView.php
@@ -1,73 +1,73 @@
<?php
final class AphrontHeadsupActionView extends AphrontView {
private $name;
private $class;
private $uri;
private $workflow;
private $instant;
public function setName($name) {
$this->name = $name;
return $this;
}
public function setClass($class) {
$this->class = $class;
return $this;
}
public function setURI($uri) {
$this->uri = $uri;
return $this;
}
public function setWorkflow($workflow) {
$this->workflow = $workflow;
return $this;
}
public function setInstant($instant) {
$this->instant = $instant;
return $this;
}
public function render() {
if ($this->instant) {
$button_class = $this->class.' link';
return phabricator_render_form(
$this->user,
array(
'action' => $this->uri,
'method' => 'post',
'style' => 'display: inline',
),
'<button class="'.$button_class.'">'.
phutil_escape_html($this->name).
'</button>'
);
}
if ($this->uri) {
$tag = 'a';
} else {
$tag = 'span';
}
$attrs = array(
'href' => $this->uri,
'class' => $this->class,
);
if ($this->workflow) {
$attrs['sigil'] = 'workflow';
}
- return javelin_render_tag(
+ return javelin_tag(
$tag,
$attrs,
- phutil_escape_html($this->name));
+ $this->name);
}
}
diff --git a/src/view/page/menu/PhabricatorMainMenuIconView.php b/src/view/page/menu/PhabricatorMainMenuIconView.php
index a94c903b55..467e2ba6ee 100644
--- a/src/view/page/menu/PhabricatorMainMenuIconView.php
+++ b/src/view/page/menu/PhabricatorMainMenuIconView.php
@@ -1,93 +1,93 @@
<?php
final class PhabricatorMainMenuIconView extends AphrontView {
private $classes = array();
private $href;
private $name;
private $sortOrder = 0.5;
private $workflow;
private $style;
public function setName($name) {
$this->name = $name;
return $this;
}
public function setWorkflow($workflow) {
$this->workflow = $workflow;
return $this;
}
public function getName() {
return $this->name;
}
public function setHref($href) {
$this->href = $href;
return $this;
}
public function getHref() {
return $this->href;
}
public function addClass($class) {
$this->classes[] = $class;
return $this;
}
public function addStyle($style) {
$this->style = $style;
return $this;
}
/**
* Provide a float, where 0.0 is the profile item and 1.0 is the logout
* item. Normally you should pick something between the two.
*
* @param float Sort order.
* @return this
*/
public function setSortOrder($sort_order) {
$this->sortOrder = $sort_order;
return $this;
}
public function getSortOrder() {
return $this->sortOrder;
}
public function render() {
$name = $this->getName();
$href = $this->getHref();
$classes = $this->classes;
$classes[] = 'phabricator-main-menu-icon';
- $label = javelin_render_tag(
+ $label = javelin_tag(
'a',
array(
'href' => $href,
'class' => 'phabricator-main-menu-icon-label',
),
- phutil_escape_html($name));
+ $name);
- $item = javelin_render_tag(
+ $item = javelin_tag(
'a',
array(
'href' => $href,
'class' => implode(' ', $classes),
'style' => $this->style,
'sigil' => $this->workflow ? 'workflow' : null,
),
'');
$group = new PhabricatorMainMenuGroupView();
$group->appendChild($item);
$group->appendChild($label);
return $group->render();
}
}
diff --git a/src/view/page/menu/PhabricatorMainMenuSearchView.php b/src/view/page/menu/PhabricatorMainMenuSearchView.php
index c444ca9078..fe3ab48e71 100644
--- a/src/view/page/menu/PhabricatorMainMenuSearchView.php
+++ b/src/view/page/menu/PhabricatorMainMenuSearchView.php
@@ -1,79 +1,79 @@
<?php
final class PhabricatorMainMenuSearchView extends AphrontView {
private $scope;
private $id;
public function setScope($scope) {
$this->scope = $scope;
return $this;
}
public function getID() {
if (!$this->id) {
$this->id = celerity_generate_unique_node_id();
}
return $this->id;
}
public function render() {
$user = $this->user;
$target_id = celerity_generate_unique_node_id();
$search_id = $this->getID();
$input = phutil_tag(
'input',
array(
'type' => 'text',
'name' => 'query',
'id' => $search_id,
'autocomplete' => 'off',
));
$scope = $this->scope;
- $target = javelin_render_tag(
+ $target = javelin_tag(
'div',
array(
'id' => $target_id,
'class' => 'phabricator-main-menu-search-target',
),
'');
Javelin::initBehavior(
'phabricator-search-typeahead',
array(
'id' => $target_id,
'input' => $search_id,
'src' => '/typeahead/common/mainsearch/',
'limit' => 10,
'placeholder' => PhabricatorSearchScope::getScopePlaceholder($scope),
));
$scope_input = phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => 'scope',
'value' => $scope,
));
$form = phabricator_render_form(
$user,
array(
'action' => '/search/',
'method' => 'POST',
),
'<div class="phabricator-main-menu-search-container">'.
$input.
'<button>Search</button>'.
$scope_input.
$target.
'</div>');
return $form;
}
}
diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php
index e833a6f828..b029e2cc8b 100644
--- a/src/view/page/menu/PhabricatorMainMenuView.php
+++ b/src/view/page/menu/PhabricatorMainMenuView.php
@@ -1,370 +1,370 @@
<?php
final class PhabricatorMainMenuView extends AphrontView {
private $defaultSearchScope;
private $controller;
private $applicationMenu;
public function setApplicationMenu(PhabricatorMenuView $application_menu) {
$this->applicationMenu = $application_menu;
return $this;
}
public function getApplicationMenu() {
return $this->applicationMenu;
}
public function setController(PhabricatorController $controller) {
$this->controller = $controller;
return $this;
}
public function getController() {
return $this->controller;
}
public function setDefaultSearchScope($default_search_scope) {
$this->defaultSearchScope = $default_search_scope;
return $this;
}
public function getDefaultSearchScope() {
return $this->defaultSearchScope;
}
public function render() {
$user = $this->user;
require_celerity_resource('phabricator-main-menu-view');
$header_id = celerity_generate_unique_node_id();
$menus = array();
$alerts = array();
if ($user->isLoggedIn()) {
list($menu, $dropdown) = $this->renderNotificationMenu();
$alerts[] = $menu;
$menus[] = $dropdown;
}
$phabricator_menu = $this->renderPhabricatorMenu();
if ($alerts) {
$alerts = phutil_render_tag(
'div',
array(
'class' => 'phabricator-main-menu-alerts',
),
self::renderSingleView($alerts));
}
$application_menu = $this->getApplicationMenu();
if ($application_menu) {
$application_menu->addClass('phabricator-dark-menu');
$application_menu->addClass('phabricator-application-menu');
}
return phutil_render_tag(
'div',
array(
'class' => 'phabricator-main-menu',
'id' => $header_id,
),
self::renderSingleView(
array(
$this->renderPhabricatorMenuButton($header_id),
$application_menu
? $this->renderApplicationMenuButton($header_id)
: null,
$this->renderPhabricatorLogo(),
$alerts,
$phabricator_menu,
$application_menu,
))).
self::renderSingleView($menus);
}
private function renderSearch() {
$user = $this->user;
$result = null;
$keyboard_config = array(
'helpURI' => '/help/keyboardshortcut/',
);
if ($user->isLoggedIn()) {
$search = new PhabricatorMainMenuSearchView();
$search->setUser($user);
$search->setScope($this->getDefaultSearchScope());
$result = $search;
$pref_shortcut = PhabricatorUserPreferences::PREFERENCE_SEARCH_SHORTCUT;
if ($user->loadPreferences()->getPreference($pref_shortcut, true)) {
$keyboard_config['searchID'] = $search->getID();
}
}
Javelin::initBehavior('phabricator-keyboard-shortcuts', $keyboard_config);
if ($result) {
$result = id(new PhabricatorMenuItemView())
->addClass('phabricator-main-menu-search')
->appendChild($result);
}
return $result;
}
private function renderPhabricatorMenuButton($header_id) {
- return javelin_render_tag(
+ return javelin_tag(
'a',
array(
'class' => 'phabricator-main-menu-expand-button '.
'phabricator-expand-core-menu',
'sigil' => 'jx-toggle-class',
'meta' => array(
'map' => array(
$header_id => 'phabricator-core-menu-expanded',
),
),
),
phutil_tag(
'span',
array(
'class' => 'phabricator-menu-button-icon sprite-menu menu-icon-eye',
),
''));
}
public function renderApplicationMenuButton($header_id) {
- return javelin_render_tag(
+ return javelin_tag(
'a',
array(
'class' => 'phabricator-main-menu-expand-button '.
'phabricator-expand-application-menu',
'sigil' => 'jx-toggle-class',
'meta' => array(
'map' => array(
$header_id => 'phabricator-application-menu-expanded',
),
),
),
phutil_tag(
'span',
array(
'class' => 'phabricator-menu-button-icon sprite-menu menu-icon-app',
),
''));
}
private function renderPhabricatorMenu() {
$user = $this->getUser();
$controller = $this->getController();
$applications = PhabricatorApplication::getAllInstalledApplications();
$applications = msort($applications, 'getName');
$core = array();
$more = array();
$actions = array();
require_celerity_resource('sprite-apps-large-css');
$group_core = PhabricatorApplication::GROUP_CORE;
foreach ($applications as $application) {
if ($application->shouldAppearInLaunchView()) {
$icon = $application->getIconName().'-light-large';
$item = id(new PhabricatorMenuItemView())
->setName($application->getName())
->setHref($application->getBaseURI())
->appendChild($this->renderMenuIcon($icon));
if ($application->getApplicationGroup() == $group_core) {
$core[] = $item;
} else {
$more[] = $item;
}
}
$app_actions = $application->buildMainMenuItems($user, $controller);
foreach ($app_actions as $action) {
$actions[] = $action;
}
}
$view = new PhabricatorMenuView();
$view->addClass('phabricator-dark-menu');
$view->addClass('phabricator-core-menu');
$search = $this->renderSearch();
$view->appendChild($search);
$view
->newLabel(pht('Home'))
->addClass('phabricator-core-item-device');
$view->addMenuItem(
id(new PhabricatorMenuItemView())
->addClass('phabricator-core-item-device')
->setName(pht('Phabricator Home'))
->setHref('/')
->appendChild($this->renderMenuIcon('logo-light-large')));
if ($controller && $controller->getCurrentApplication()) {
$application = $controller->getCurrentApplication();
$icon = $application->getIconName().'-light-large';
$view->addMenuItem(
id(new PhabricatorMenuItemView())
->addClass('phabricator-core-item-device')
->setName(pht('%s Home', $application->getName()))
->appendChild($this->renderMenuIcon($icon))
->setHref($controller->getApplicationURI()));
}
if ($core) {
$view->addMenuItem(
id(new PhabricatorMenuItemView())
->addClass('phabricator-core-item-device')
->setType(PhabricatorMenuItemView::TYPE_LABEL)
->setName(pht('Core Applications')));
foreach ($core as $item) {
$item->addClass('phabricator-core-item-device');
$view->addMenuItem($item);
}
}
if ($actions) {
$actions = msort($actions, 'getSortOrder');
$view->addMenuItem(
id(new PhabricatorMenuItemView())
->addClass('phabricator-core-item-device')
->setType(PhabricatorMenuItemView::TYPE_LABEL)
->setName(pht('Actions')));
foreach ($actions as $action) {
$icon = $action->getIcon();
if ($icon) {
if ($action->getSelected()) {
$action->appendChild($this->renderMenuIcon($icon.'-blue-large'));
} else {
$action->appendChild($this->renderMenuIcon($icon.'-light-large'));
}
}
$view->addMenuItem($action);
}
}
if ($more) {
$view->addMenuItem(
id(new PhabricatorMenuItemView())
->addClass('phabricator-core-item-device')
->setType(PhabricatorMenuItemView::TYPE_LABEL)
->setName(pht('More Applications')));
foreach ($more as $item) {
$item->addClass('phabricator-core-item-device');
$view->addMenuItem($item);
}
}
return $view;
}
private function renderPhabricatorLogo() {
return phutil_tag(
'a',
array(
'class' => 'phabricator-main-menu-logo',
'href' => '/',
),
phutil_tag(
'span',
array(
'class' => 'sprite-menu phabricator-main-menu-logo-image',
),
''));
}
private function renderNotificationMenu() {
$user = $this->user;
require_celerity_resource('phabricator-notification-css');
require_celerity_resource('phabricator-notification-menu-css');
require_celerity_resource('sprite-menu-css');
$count_id = celerity_generate_unique_node_id();
$dropdown_id = celerity_generate_unique_node_id();
$bubble_id = celerity_generate_unique_node_id();
$count_number = id(new PhabricatorFeedStoryNotification())
->countUnread($user);
if ($count_number > 999) {
$count_number = "\xE2\x88\x9E";
}
$count_tag = phutil_tag(
'span',
array(
'id' => $count_id,
'class' => 'phabricator-main-menu-alert-count'
),
$count_number);
$icon_tag = phutil_tag(
'span',
array(
'class' => 'sprite-menu phabricator-main-menu-alert-icon',
),
'');
$container_classes = array(
'phabricator-main-menu-alert-bubble',
'sprite-menu',
'alert-notifications',
);
if ($count_number) {
$container_classes[] = 'alert-unread';
}
$bubble_tag = phutil_tag(
'a',
array(
'href' => '/notification/',
'class' => implode(' ', $container_classes),
'id' => $bubble_id,
),
array($icon_tag, $count_tag));
Javelin::initBehavior(
'aphlict-dropdown',
array(
'bubbleID' => $bubble_id,
'countID' => $count_id,
'dropdownID' => $dropdown_id,
));
- $notification_dropdown = javelin_render_tag(
+ $notification_dropdown = javelin_tag(
'div',
array(
'id' => $dropdown_id,
'class' => 'phabricator-notification-menu',
'sigil' => 'phabricator-notification-menu',
'style' => 'display: none;',
),
'');
return array($bubble_tag, $notification_dropdown);
}
private function renderMenuIcon($name) {
return phutil_tag(
'span',
array(
'class' => 'phabricator-core-menu-icon '.
'sprite-apps-large app-'.$name,
),
'');
}
}

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 21, 10:59 AM (59 m, 33 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
352512
Default Alt Text
(441 KB)

Event Timeline