@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
at upstream/main 392 lines 9.7 kB view raw
1<?php 2 3final class AphrontFormDateControl extends AphrontFormControl { 4 5 private $initialTime; 6 private $zone; 7 8 private $valueDate; 9 private $valueTime; 10 private $allowNull; 11 private $continueOnInvalidDate = false; 12 private $isTimeDisabled; 13 private $isDisabled; 14 private $endDateID; 15 16 public function setAllowNull($allow_null) { 17 $this->allowNull = $allow_null; 18 return $this; 19 } 20 21 public function setIsTimeDisabled($is_disabled) { 22 $this->isTimeDisabled = $is_disabled; 23 return $this; 24 } 25 26 public function setIsDisabled($is_datepicker_disabled) { 27 $this->isDisabled = $is_datepicker_disabled; 28 return $this; 29 } 30 31 public function setEndDateID($value) { 32 $this->endDateID = $value; 33 return $this; 34 } 35 36 const TIME_START_OF_DAY = 'start-of-day'; 37 const TIME_END_OF_DAY = 'end-of-day'; 38 const TIME_START_OF_BUSINESS = 'start-of-business'; 39 const TIME_END_OF_BUSINESS = 'end-of-business'; 40 41 public function setInitialTime($time) { 42 $this->initialTime = $time; 43 return $this; 44 } 45 46 public function readValueFromRequest(AphrontRequest $request) { 47 $date = $request->getStr($this->getDateInputName()); 48 $time = $request->getStr($this->getTimeInputName()); 49 $enabled = $request->getBool($this->getCheckboxInputName()); 50 51 if ($this->allowNull && !$enabled) { 52 $this->setError(null); 53 $this->setValue(null); 54 return; 55 } 56 57 $err = $this->getError(); 58 59 if ($date || $time) { 60 $this->valueDate = $date; 61 $this->valueTime = $time; 62 63 // Assume invalid. 64 $err = pht('Invalid'); 65 66 $zone = $this->getTimezone(); 67 68 try { 69 $datetime = new DateTime("{$date} {$time}", $zone); 70 $value = $datetime->format('U'); 71 } catch (Exception $ex) { 72 $value = null; 73 } 74 75 if ($value) { 76 $this->setValue($value); 77 $err = null; 78 } else { 79 $this->setValue(null); 80 } 81 } else { 82 $value = $this->getInitialValue(); 83 if ($value) { 84 $this->setValue($value); 85 } else { 86 $this->setValue(null); 87 } 88 } 89 90 $this->setError($err); 91 92 return $this->getValue(); 93 } 94 95 protected function getCustomControlClass() { 96 return 'aphront-form-control-date'; 97 } 98 99 public function setValue($epoch) { 100 if ($epoch instanceof AphrontFormDateControlValue) { 101 $this->continueOnInvalidDate = true; 102 $this->valueDate = $epoch->getValueDate(); 103 $this->valueTime = $epoch->getValueTime(); 104 $this->allowNull = $epoch->getOptional(); 105 $this->isDisabled = $epoch->isDisabled(); 106 107 return parent::setValue($epoch->getEpoch()); 108 } 109 110 $result = parent::setValue($epoch); 111 112 if ($epoch === null) { 113 return $result; 114 } 115 116 $readable = $this->formatTime($epoch, 'Y!m!d!'.$this->getTimeFormat()); 117 $readable = explode('!', $readable, 4); 118 119 $year = $readable[0]; 120 $month = $readable[1]; 121 $day = $readable[2]; 122 123 $this->valueDate = $month.'/'.$day.'/'.$year; 124 $this->valueTime = $readable[3]; 125 126 return $result; 127 } 128 129 private function getDateInputValue() { 130 $date_format = $this->getDateFormat(); 131 $timezone = $this->getTimezone(); 132 133 try { 134 $datetime = new DateTime($this->valueDate, $timezone); 135 } catch (Exception $ex) { 136 return $this->valueDate; 137 } 138 139 return $datetime->format($date_format); 140 } 141 142 private function getTimeFormat() { 143 $viewer = $this->getViewer(); 144 $time_key = PhabricatorTimeFormatSetting::SETTINGKEY; 145 return $viewer->getUserSetting($time_key); 146 } 147 148 private function getDateFormat() { 149 $viewer = $this->getViewer(); 150 $date_key = PhabricatorDateFormatSetting::SETTINGKEY; 151 return $viewer->getUserSetting($date_key); 152 } 153 154 private function getTimeInputValue() { 155 return $this->valueTime; 156 } 157 158 private function formatTime($epoch, $fmt) { 159 return phabricator_format_local_time( 160 $epoch, 161 $this->getViewer(), 162 $fmt); 163 } 164 165 private function getDateInputName() { 166 return $this->getName().'_d'; 167 } 168 169 private function getTimeInputName() { 170 return $this->getName().'_t'; 171 } 172 173 private function getCheckboxInputName() { 174 return $this->getName().'_e'; 175 } 176 177 protected function renderInput() { 178 179 $disabled = null; 180 if ($this->getValue() === null && !$this->continueOnInvalidDate) { 181 $this->setValue($this->getInitialValue()); 182 if ($this->allowNull) { 183 $disabled = 'disabled'; 184 } 185 } 186 187 if ($this->isDisabled) { 188 $disabled = 'disabled'; 189 } 190 191 $checkbox = null; 192 if ($this->allowNull) { 193 $checkbox = javelin_tag( 194 'input', 195 array( 196 'type' => 'checkbox', 197 'name' => $this->getCheckboxInputName(), 198 'sigil' => 'calendar-enable', 199 'class' => 'aphront-form-date-enabled-input', 200 'value' => 1, 201 'checked' => ($disabled === null ? 'checked' : null), 202 )); 203 } 204 205 $date_sel = javelin_tag( 206 'input', 207 array( 208 'autocomplete' => 'off', 209 'name' => $this->getDateInputName(), 210 'sigil' => 'date-input', 211 'value' => $this->getDateInputValue(), 212 'type' => 'text', 213 'class' => 'aphront-form-date-input', 214 ), 215 ''); 216 217 $date_div = javelin_tag( 218 'div', 219 array( 220 'class' => 'aphront-form-date-input-container', 221 ), 222 $date_sel); 223 224 $cicon = id(new PHUIIconView()) 225 ->setIcon('fa-calendar'); 226 227 $cal_icon = javelin_tag( 228 'a', 229 array( 230 'href' => '#', 231 'class' => 'calendar-button', 232 'sigil' => 'calendar-button', 233 'aria-label' => pht('Calendar date picker'), 234 ), 235 $cicon); 236 237 $values = $this->getTimeTypeaheadValues(); 238 239 $time_id = celerity_generate_unique_node_id(); 240 Javelin::initBehavior('time-typeahead', array( 241 'startTimeID' => $time_id, 242 'endTimeID' => $this->endDateID, 243 'timeValues' => $values, 244 'format' => $this->getTimeFormat(), 245 )); 246 247 $time_sel = javelin_tag( 248 'input', 249 array( 250 'autocomplete' => 'off', 251 'name' => $this->getTimeInputName(), 252 'sigil' => 'time-input', 253 'value' => $this->getTimeInputValue(), 254 'type' => 'text', 255 'class' => 'aphront-form-time-input', 256 ), 257 ''); 258 259 $time_div = javelin_tag( 260 'div', 261 array( 262 'id' => $time_id, 263 'class' => 'aphront-form-time-input-container', 264 ), 265 $time_sel); 266 267 $viewer = $this->getViewer(); 268 $week_key = PhabricatorWeekStartDaySetting::SETTINGKEY; 269 $week_start = $viewer->getUserSetting($week_key); 270 271 $date_pht = array( 272 'S|M|T|W|T|F|S' => pht('S|M|T|W|T|F|S'), 273 'January' => pht('January'), 274 'February' => pht('February'), 275 'March' => pht('March'), 276 'April' => pht('April'), 277 'May' => pht('May'), 278 'June' => pht('June'), 279 'July' => pht('July'), 280 'August' => pht('August'), 281 'September' => pht('September'), 282 'October' => pht('October'), 283 'November' => pht('November'), 284 'December' => pht('December'), 285 ); 286 Javelin::initBehavior('fancy-datepicker', array( 287 'format' => $this->getDateFormat(), 288 'weekStart' => $week_start, 289 'pht' => $date_pht, 290 )); 291 292 $classes = array(); 293 $classes[] = 'aphront-form-date-container'; 294 if ($disabled) { 295 $classes[] = 'datepicker-disabled'; 296 } 297 if ($this->isTimeDisabled) { 298 $classes[] = 'no-time'; 299 } 300 301 return javelin_tag( 302 'div', 303 array( 304 'class' => implode(' ', $classes), 305 'sigil' => 'phabricator-date-control', 306 'meta' => array( 307 'disabled' => (bool)$disabled, 308 ), 309 'id' => $this->getID(), 310 ), 311 array( 312 $checkbox, 313 $date_div, 314 $cal_icon, 315 $time_div, 316 )); 317 } 318 319 private function getTimezone() { 320 if ($this->zone) { 321 return $this->zone; 322 } 323 324 $viewer = $this->getViewer(); 325 326 $user_zone = $viewer->getTimezoneIdentifier(); 327 $this->zone = new DateTimeZone($user_zone); 328 return $this->zone; 329 } 330 331 private function getInitialValue() { 332 $zone = $this->getTimezone(); 333 334 // TODO: We could eventually allow these to be customized per install or 335 // per user or both, but let's wait and see. 336 switch ($this->initialTime) { 337 case self::TIME_START_OF_DAY: 338 default: 339 $time = '12:00 AM'; 340 break; 341 case self::TIME_START_OF_BUSINESS: 342 $time = '9:00 AM'; 343 break; 344 case self::TIME_END_OF_BUSINESS: 345 $time = '5:00 PM'; 346 break; 347 case self::TIME_END_OF_DAY: 348 $time = '11:59 PM'; 349 break; 350 } 351 352 $today = $this->formatTime(time(), 'Y-m-d'); 353 try { 354 $date = new DateTime("{$today} {$time}", $zone); 355 $value = $date->format('U'); 356 } catch (Exception $ex) { 357 $value = null; 358 } 359 360 return $value; 361 } 362 363 private function getTimeTypeaheadValues() { 364 $time_format = $this->getTimeFormat(); 365 $times = array(); 366 367 if ($time_format == 'g:i A') { 368 $am_pm_list = array('AM', 'PM'); 369 370 foreach ($am_pm_list as $am_pm) { 371 for ($hour = 0; $hour < 12; $hour++) { 372 $actual_hour = ($hour == 0) ? 12 : $hour; 373 $times[] = $actual_hour.':00 '.$am_pm; 374 $times[] = $actual_hour.':30 '.$am_pm; 375 } 376 } 377 } else if ($time_format == 'H:i') { 378 for ($hour = 0; $hour < 24; $hour++) { 379 $written_hour = ($hour > 9) ? $hour : '0'.$hour; 380 $times[] = $written_hour.':00'; 381 $times[] = $written_hour.':30'; 382 } 383 } 384 385 foreach ($times as $key => $time) { 386 $times[$key] = array($key, $time); 387 } 388 389 return $times; 390 } 391 392}