1<?php 2// papertable.php -- HotCRP helper class for producing paper tables 3// Copyright (c) 2006-2018 Eddie Kohler; see LICENSE. 4 5class PaperTable { 6 public $conf; 7 public $prow; 8 private $_prow; 9 public $user; 10 private $all_rrows = null; 11 public $viewable_rrows = null; 12 var $crows = null; 13 private $mycrows; 14 private $can_view_reviews = false; 15 var $rrow = null; 16 var $editrrow = null; 17 var $mode; 18 private $prefer_approvable = false; 19 private $allreviewslink; 20 private $edit_status = null; 21 22 public $editable; 23 public $edit_fields; 24 public $edit_fields_position; 25 26 private $qreq; 27 private $useRequest; 28 private $review_values; 29 private $npapstrip = 0; 30 private $npapstrip_tag_entry; 31 private $allFolded; 32 private $matchPreg; 33 private $entryMatches; 34 private $canUploadFinal; 35 36 private $allow_admin; 37 private $admin; 38 private $view_authors = 0; 39 private $view_options = []; 40 41 private $cf = null; 42 private $quit = false; 43 44 static private $textAreaRows = array("title" => 1, "abstract" => 5, "authorInformation" => 5, "collaborators" => 5); 45 46 function __construct($prow, $qreq, $mode = null) { 47 global $Conf, $Me; 48 49 $this->conf = $Conf; 50 $this->prow = $prow; 51 $this->_prow = $this->prow ? : new PaperInfo(null, null, $this->conf); 52 $this->user = $user = $Me; 53 $this->allow_admin = $user->allow_administer($prow); 54 $this->admin = $user->can_administer($prow); 55 $this->qreq = $qreq; 56 57 $this->canUploadFinal = $this->prow 58 && $this->prow->outcome > 0 59 && $this->user->call_with_overrides(Contact::OVERRIDE_TIME, "can_submit_final_paper", $this->prow); 60 61 if (!$this->prow) { 62 $this->mode = "edit"; 63 return; 64 } 65 66 // calculate visibility of authors and options 67 // 0: not visible; 1: fold (admin only); 2: visible 68 $new_overrides = 0; 69 if ($this->allow_admin) 70 $new_overrides |= Contact::OVERRIDE_CONFLICT; 71 if ($this->mode === "edit") 72 $new_overrides |= Contact::OVERRIDE_EDIT_CONDITIONS; 73 $overrides = $user->add_overrides($new_overrides); 74 75 if ($user->can_view_authors($prow)) 76 $this->view_authors = 2; 77 $olist = $this->conf->paper_opts->option_list_type(!$this->canUploadFinal); 78 foreach ($olist as $o) { 79 if ($user->can_view_paper_option($prow, $o)) 80 $this->view_options[$o->id] = 2; 81 } 82 83 if ($this->allow_admin) { 84 $user->remove_overrides(Contact::OVERRIDE_CONFLICT); 85 if ($this->view_authors && !$user->can_view_authors($prow)) 86 $this->view_authors = 1; 87 foreach ($olist as $o) 88 if (isset($this->view_options[$o->id]) 89 && !$user->can_view_paper_option($prow, $o)) 90 $this->view_options[$o->id] = 1; 91 } 92 93 $user->set_overrides($overrides); 94 95 // enumerate allowed modes 96 $ms = array(); 97 if ($user->can_view_review($prow, null) 98 || $prow->review_submitted($user)) 99 $this->can_view_reviews = $ms["p"] = true; 100 else if ($prow->timeWithdrawn > 0 && !$this->conf->timeUpdatePaper($prow)) 101 $ms["p"] = true; 102 if ($user->can_review($prow, null)) 103 $ms["re"] = true; 104 if ($user->can_view_paper($prow) && $this->allow_admin) 105 $ms["p"] = true; 106 if ($prow->has_author($user) 107 && ($this->conf->timeFinalizePaper($prow) || $prow->timeSubmitted <= 0)) 108 $ms["edit"] = true; 109 if ($user->can_view_paper($prow)) 110 $ms["p"] = true; 111 if ($prow->has_author($user) || $this->allow_admin) 112 $ms["edit"] = true; 113 if ($prow->review_type($user) >= REVIEW_SECONDARY || $this->allow_admin) 114 $ms["assign"] = true; 115 if (!$mode) 116 $mode = $this->qreq->m ? : $this->qreq->mode; 117 if ($mode === "pe") 118 $mode = "edit"; 119 else if ($mode === "view" || $mode === "r") 120 $mode = "p"; 121 else if ($mode === "rea") { 122 $mode = "re"; 123 $this->prefer_approvable = true; 124 } 125 if ($mode && isset($ms[$mode])) 126 $this->mode = $mode; 127 else 128 $this->mode = key($ms); 129 if (isset($ms["re"]) && isset($this->qreq->reviewId)) 130 $this->mode = "re"; 131 132 // choose list 133 if (!$this->conf->has_active_list()) 134 $this->conf->set_active_list($this->find_session_list($prow->paperId)); 135 else { 136 $list = $this->conf->active_list(); 137 assert($list && ($list->set_current_id($prow->paperId) || $list->digest)); 138 } 139 140 $this->matchPreg = []; 141 if (($list = $this->conf->active_list()) && $list->highlight 142 && preg_match('_\Ap/([^/]*)/([^/]*)(?:/|\z)_', $list->listid, $m)) { 143 $hlquery = is_string($list->highlight) ? $list->highlight : urldecode($m[2]); 144 $ps = new PaperSearch($user, ["t" => $m[1], "q" => $hlquery]); 145 foreach ($ps->field_highlighters() as $k => $v) 146 $this->matchPreg[$k] = $v; 147 } 148 if (empty($this->matchPreg)) 149 $this->matchPreg = null; 150 } 151 private function find_session_list($pid) { 152 if (isset($_COOKIE["hotlist-info"]) 153 && ($list = SessionList::decode_info_string($_COOKIE["hotlist-info"])) 154 && $list->list_type() === "p" 155 && ($list->set_current_id($pid) || $list->digest)) 156 return $list; 157 158 // look up list description 159 $list = null; 160 $listdesc = $this->qreq->ls; 161 if ($listdesc) { 162 if (($opt = PaperSearch::unparse_listid($listdesc))) 163 $list = $this->try_list($opt, $pid); 164 if (!$list && preg_match('{\A(all|s):(.*)\z}s', $listdesc, $m)) 165 $list = $this->try_list(["t" => $m[1], "q" => $m[2]], $pid); 166 if (!$list && preg_match('{\A[a-z]+\z}', $listdesc)) 167 $list = $this->try_list(["t" => $listdesc], $pid); 168 if (!$list) 169 $list = $this->try_list(["q" => $listdesc], $pid); 170 } 171 172 // default lists 173 if (!$list) 174 $list = $this->try_list([], $pid); 175 if (!$list && $this->user->privChair) 176 $list = $this->try_list(["t" => "all"], $pid); 177 178 return $list; 179 } 180 private function try_list($opt, $pid) { 181 $srch = new PaperSearch($this->user, $opt); 182 $list = $srch->session_list_object(); 183 return $list->set_current_id($pid); 184 } 185 186 private static function _combine_match_preg($m1, $m) { 187 if (is_object($m)) 188 $m = get_object_vars($m); 189 if (!is_array($m)) 190 $m = ["abstract" => $m, "title" => $m, 191 "authorInformation" => $m, "collaborators" => $m]; 192 foreach ($m as $k => $v) 193 if (!isset($m1[$k]) || !$m1[$k]) 194 $m1[$k] = $v; 195 return $m1; 196 } 197 198 function initialize($editable, $useRequest) { 199 $this->editable = $editable; 200 $this->useRequest = $useRequest; 201 $this->allFolded = $this->mode === "re" || $this->mode === "assign" 202 || ($this->mode !== "edit" 203 && (!empty($this->all_rrows) || !empty($this->crows))); 204 } 205 206 function set_edit_status(PaperStatus $status) { 207 $this->edit_status = $status; 208 } 209 210 function set_review_values(ReviewValues $rvalues = null) { 211 $this->review_values = $rvalues; 212 } 213 214 function can_view_reviews() { 215 return $this->can_view_reviews; 216 } 217 218 static function do_header($paperTable, $id, $action_mode, $qreq) { 219 global $Conf, $Me; 220 $prow = $paperTable ? $paperTable->prow : null; 221 $format = 0; 222 223 $t = '<div id="header_page" class="header_page_submission"><div id="header_page_submission_inner"><h1 class="paptitle'; 224 225 if (!$paperTable && !$prow) { 226 if (($pid = $qreq->paperId) && ctype_digit($pid)) 227 $title = "#$pid"; 228 else 229 $title = $Conf->_c("paper_title", "Submission"); 230 $t .= '">' . $title; 231 } else if (!$prow) { 232 $title = $Conf->_c("paper_title", "New submission"); 233 $t .= '">' . $title; 234 } else { 235 $title = "#" . $prow->paperId; 236 $viewable_tags = $prow->viewable_tags($Me); 237 if ($viewable_tags || $Me->can_view_tags($prow)) { 238 $t .= ' has-tag-classes'; 239 if (($color = $prow->conf->tags()->color_classes($viewable_tags))) 240 $t .= ' ' . $color; 241 } 242 $t .= '"><a class="q" href="' . hoturl("paper", array("p" => $prow->paperId, "ls" => null)) 243 . '"><span class="taghl"><span class="pnum">' . $title . '</span>' 244 . ' '; 245 246 $highlight_text = null; 247 $title_matches = 0; 248 if ($paperTable && $paperTable->matchPreg 249 && ($highlight = get($paperTable->matchPreg, "title"))) 250 $highlight_text = Text::highlight($prow->title, $highlight, $title_matches); 251 252 if (!$title_matches && ($format = $prow->title_format())) 253 $t .= '<span class="ptitle need-format" data-format="' . $format . '">'; 254 else 255 $t .= '<span class="ptitle">'; 256 if ($highlight_text) 257 $t .= $highlight_text; 258 else if ($prow->title === "") 259 $t .= "[No title]"; 260 else 261 $t .= htmlspecialchars($prow->title); 262 263 $t .= '</span></span></a>'; 264 if ($viewable_tags && $Conf->tags()->has_decoration) { 265 $tagger = new Tagger($Me); 266 $t .= $tagger->unparse_decoration_html($viewable_tags); 267 } 268 } 269 270 $t .= '</h1></div></div>'; 271 if ($paperTable && $prow) 272 $t .= $paperTable->_paptabBeginKnown(); 273 274 $Conf->header($title, $id, [ 275 "action_bar" => actionBar($action_mode, $qreq), 276 "title_div" => $t, "class" => "paper", "paperId" => $qreq->paperId 277 ]); 278 if ($format) 279 echo Ht::unstash_script("render_text.on_page()"); 280 } 281 282 private function abstract_foldable($abstract) { 283 return strlen($abstract) > 190; 284 } 285 286 private function echoDivEnter() { 287 $folds = ["a" => true, "p" => $this->allFolded, "b" => $this->allFolded, "t" => $this->allFolded]; 288 foreach (["a", "p", "b", "t"] as $k) 289 if (!$this->conf->session("foldpaper$k", 1)) 290 $folds[$k] = false; 291 292 // if highlighting, automatically unfold abstract/authors 293 if ($this->prow && $folds["b"]) { 294 $abstract = $this->entryData("abstract"); 295 if ($this->entryMatches || !$this->abstract_foldable($abstract)) 296 $folds["b"] = false; 297 } 298 if ($this->matchPreg && $this->prow && $folds["a"]) { 299 $this->entryData("authorInformation"); 300 if ($this->entryMatches) 301 $folds["a"] = $folds["p"] = false; 302 } 303 304 // collect folders 305 $folders = array("clearfix"); 306 if ($this->prow) { 307 if ($this->view_authors == 1) 308 $folders[] = $folds["a"] ? "fold8c" : "fold8o"; 309 if ($this->view_authors && $this->allFolded) 310 $folders[] = $folds["p"] ? "fold9c" : "fold9o"; 311 } 312 $folders[] = $folds["t"] ? "fold5c" : "fold5o"; 313 $folders[] = $folds["b"] ? "fold6c" : "fold6o"; 314 315 // echo div 316 echo '<div id="foldpaper" class="', join(" ", $folders), '" data-fold-session="' 317 . htmlspecialchars(json_encode_browser([ 318 "5" => "foldpapert", "6" => "foldpaperb", 319 "8" => "foldpapera", "9" => "foldpaperp" 320 ])) . '">'; 321 } 322 323 private function echoDivExit() { 324 echo "</div>"; 325 } 326 327 function has_problem_at($f) { 328 if ($this->edit_status) { 329 if (str_starts_with($f, "au")) { 330 if ($f === "authorInformation") 331 $f = "authors"; 332 else if (preg_match('/\A.*?(\d+)\z/', $f, $m) 333 && $this->edit_status->has_problem_at("author$m[1]")) 334 return true; 335 } 336 return $this->edit_status->has_problem_at($f); 337 } else 338 return false; 339 } 340 341 function error_class($f) { 342 return $this->has_problem_at($f) ? " error" : ""; 343 } 344 function has_error_class($f) { 345 return $this->has_problem_at($f) ? " has-error" : ""; 346 } 347 348 private function editable_papt($what, $heading, $extra = [], PaperOption $opt = null) { 349 $for = get($extra, "for", false); 350 $t = '<div class="papeg'; 351 if ($opt && $opt->edit_condition()) { 352 $t .= ' has-edit-condition'; 353 if (!$opt->test_edit_condition($this->_prow)) 354 $t .= ' hidden'; 355 $t .= '" data-edit-condition="' . htmlspecialchars(json_encode($opt->compile_edit_condition($this->_prow))); 356 Ht::stash_script('$(edit_paper_ui.edit_condition)', 'edit_condition'); 357 } 358 $t .= '"><div class="papet' . $this->error_class($what); 359 if ($for === "checkbox") 360 $t .= ' checki'; 361 if (($id = get($extra, "id"))) 362 $t .= '" id="' . $id; 363 return $t . '">' . Ht::label($heading, $for === "checkbox" ? false : $for, ["class" => "papfn"]) . '</div>'; 364 } 365 366 function messages_for($field) { 367 if ($this->edit_status && ($ms = $this->edit_status->messages_at($field, true))) { 368 $status = array_reduce($ms, function ($c, $m) { return max($c, $m[2]); }, 0); 369 return Ht::xmsg($status, array_map(function ($m) { return $m[1]; }, $ms)); 370 } else 371 return ""; 372 } 373 374 private function papt($what, $name, $extra = array()) { 375 $fold = defval($extra, "fold", false); 376 $editfolder = defval($extra, "editfolder", false); 377 if ($fold || $editfolder) { 378 $foldnum = defval($extra, "foldnum", 0); 379 $foldnumclass = $foldnum ? " data-fold-target=\"$foldnum\"" : ""; 380 } 381 382 if (get($extra, "type") === "ps") 383 list($divclass, $hdrclass) = array("pst", "psfn"); 384 else 385 list($divclass, $hdrclass) = array("pavt", "pavfn"); 386 387 $c = "<div class=\"$divclass" . $this->error_class($what); 388 if (($fold || $editfolder) && !get($extra, "float")) 389 $c .= " ui js-foldup\"" . $foldnumclass . ">"; 390 else 391 $c .= "\">"; 392 $c .= "<span class=\"$hdrclass\">"; 393 if (!$fold) { 394 $n = (is_array($name) ? $name[0] : $name); 395 if ($editfolder) 396 $c .= "<a class=\"q fn ui js-foldup\" " 397 . "href=\"" . SelfHref::make($this->qreq, ["atab" => $what]) 398 . "\"" . $foldnumclass . ">" . $n 399 . "</a><span class=\"fx\">" . $n . "</span>"; 400 else 401 $c .= $n; 402 } else { 403 $c .= '<a class="q ui js-foldup" href=""' . $foldnumclass; 404 if (($title = defval($extra, "foldtitle"))) 405 $c .= ' title="' . $title . '"'; 406 $c .= '>' . expander(null, $foldnum); 407 if (!is_array($name)) 408 $name = array($name, $name); 409 if ($name[0] !== $name[1]) 410 $c .= '<span class="fn' . $foldnum . '">' . $name[1] . '</span><span class="fx' . $foldnum . '">' . $name[0] . '</span>'; 411 else 412 $c .= $name[0]; 413 $c .= '</a>'; 414 } 415 $c .= "</span>"; 416 if ($editfolder) { 417 $c .= "<span class=\"pstedit fn\">" 418 . "<a class=\"ui xx need-tooltip js-foldup\" href=\"" 419 . SelfHref::make($this->qreq, ["atab" => $what]) 420 . "\"" . $foldnumclass . " data-tooltip=\"Edit\">" 421 . "<span class=\"psteditimg\">" 422 . Ht::img("edit48.png", "[Edit]", "editimg") 423 . "</span> <u class=\"x\">Edit</u></a></span>"; 424 } 425 if (isset($extra["float"])) 426 $c .= $extra["float"]; 427 $c .= "</div>"; 428 return $c; 429 } 430 431 private function editable_textarea($fieldName) { 432 $js = ["id" => $fieldName, 433 "class" => "papertext need-autogrow" . $this->has_error_class($fieldName), 434 "rows" => self::$textAreaRows[$fieldName], "cols" => 60]; 435 if ($fieldName === "abstract") 436 $js["spellcheck"] = true; 437 $value = $pvalue = $this->prow ? $this->prow->$fieldName : ""; 438 if ($this->useRequest && isset($this->qreq[$fieldName])) { 439 $value = cleannl($this->qreq[$fieldName]); 440 if (self::$textAreaRows[$fieldName] === 1) 441 $value = trim($value); 442 if ($value !== $pvalue) 443 $js["data-default-value"] = $pvalue; 444 } 445 return Ht::textarea($fieldName, $value, $js); 446 } 447 448 private function entryData($fieldName, $table_type = false) { 449 $this->entryMatches = 0; 450 $text = $this->prow ? $this->prow->$fieldName : ""; 451 if ($this->matchPreg 452 && isset(self::$textAreaRows[$fieldName]) 453 && isset($this->matchPreg[$fieldName])) 454 $text = Text::highlight($text, $this->matchPreg[$fieldName], $this->entryMatches); 455 else 456 $text = htmlspecialchars($text); 457 return $table_type === "col" ? nl2br($text) : $text; 458 } 459 460 private function field_name($name) { 461 return $this->conf->_c("paper_field/edit", $name); 462 } 463 464 private function field_hint($name, $itext = "") { 465 $args = array_merge(["paper_edit_description"], func_get_args()); 466 if (count($args) === 2) 467 $args[] = ""; 468 $t = call_user_func_array([$this->conf->ims(), "xci"], $args); 469 if ($t !== "") 470 return '<div class="paphint">' . $t . '</div>'; 471 return ""; 472 } 473 474 private function echo_editable_title() { 475 echo $this->editable_papt("title", $this->field_name("Title"), ["for" => "title"]), 476 $this->messages_for("title"), 477 $this->field_hint("Title"), 478 '<div class="papev">', $this->editable_textarea("title"), "</div></div>\n\n"; 479 } 480 481 static function pdf_stamps_html($data, $options = null) { 482 global $Conf; 483 $tooltip = !$options || !get($options, "notooltip"); 484 485 $t = array(); 486 $tm = defval($data, "timestamp", defval($data, "timeSubmitted", 0)); 487 if ($tm > 0) 488 $t[] = ($tooltip ? '<span class="nb need-tooltip" data-tooltip="Time of PDF upload">' : '<span class="nb">') 489 . '<svg width="12" height="12" viewBox="0 0 96 96" style="vertical-align:-2px"><path style="fill:#333" d="M48 6a42 42 0 1 1 0 84 42 42 0 1 1 0-84zm0 10a32 32 0 1 0 0 64 32 32 0 1 0 0-64z"/><path style="fill:#333" d="M48 19A5 5 0 0 0 43 24V46c0 2.352.37 4.44 1.464 5.536l12 12c4.714 4.908 12-2.36 7-7L53 46V24A5 5 0 0 0 43 24z"/></svg>' 490 . " " . $Conf->unparse_time_full($tm) . "</span>"; 491 if (($hash = defval($data, "sha1")) != "") 492 $hash = Filer::hash_as_text($hash); 493 if ($hash) { 494 list($xhash, $pfx, $alg) = Filer::analyze_hash($hash); 495 $x = '<span class="nb checksum'; 496 if ($tooltip) { 497 $x .= ' need-tooltip" data-tooltip="'; 498 if ($alg === "sha1") 499 $x .= "SHA-1 checksum"; 500 else if ($alg === "sha256") 501 $x .= "SHA-256 checksum"; 502 } 503 $x .= '"><svg width="12" height="12" viewBox="0 0 48 48" style="vertical-align:-2px"><path style="fill:#333" d="M19 32l-8-8-7 7 14 14 26-26-6-6-19 19z"/><path style="fill:#333" d="M15 3V10H8v5h7v7h5v-7H27V10h-7V3h-5z"/></svg> ' 504 . '<span class="checksum-overflow">' . $xhash . '</span>' 505 . '<span class="checksum-abbreviation">' . substr($xhash, 0, 8) . '</span></span>'; 506 $t[] = $x; 507 } 508 if (!empty($t)) 509 return '<span class="hint">' . join(" <span class='barsep'>·</span> ", $t) . "</span>"; 510 else 511 return ""; 512 } 513 514 private function paptabDownload() { 515 assert(!$this->editable); 516 $prow = $this->prow; 517 $out = array(); 518 519 // download 520 if ($this->user->can_view_pdf($prow)) { 521 $dprefix = ""; 522 $dtype = $prow->finalPaperStorageId > 1 ? DTYPE_FINAL : DTYPE_SUBMISSION; 523 if (($doc = $prow->document($dtype)) && $doc->paperStorageId > 1) { 524 if (($stamps = self::pdf_stamps_html($doc))) 525 $stamps = "<span class='sep'></span>" . $stamps; 526 if ($dtype == DTYPE_FINAL) 527 $dname = $this->conf->_c("paper_field", "Final version"); 528 else if ($prow->timeSubmitted != 0) 529 $dname = $this->conf->_c("paper_field", "Submission"); 530 else 531 $dname = $this->conf->_c("paper_field", "Draft submission"); 532 $out[] = '<p class="xd">' . $dprefix . $doc->link_html('<span class="pavfn">' . $dname . '</span>', DocumentInfo::L_REQUIREFORMAT) . $stamps . '</p>'; 533 } 534 535 foreach ($prow ? $prow->options() : [] as $ov) { 536 $o = $ov->option; 537 if ($o->display() === PaperOption::DISP_SUBMISSION 538 && get($this->view_options, $o->id) 539 && ($oh = $this->unparse_option_html($ov))) { 540 $out = array_merge($out, $oh); 541 } 542 } 543 544 if ($prow->finalPaperStorageId > 1 && $prow->paperStorageId > 1) 545 $out[] = '<p class="xd"><small>' . $prow->document(DTYPE_SUBMISSION)->link_html("Submission version", DocumentInfo::L_SMALL | DocumentInfo::L_NOSIZE) . "</small></p>"; 546 } 547 548 // conflicts 549 if ($this->user->isPC && !$prow->has_conflict($this->user) 550 && $this->conf->timeUpdatePaper($prow) 551 && $this->mode !== "assign" 552 && $this->mode !== "contact" 553 && $prow->outcome >= 0) 554 $out[] = Ht::xmsg("warning", 'The authors still have <a href="' . hoturl("deadlines") . '">time</a> to make changes.'); 555 556 echo join("", $out); 557 } 558 559 private function is_ready() { 560 return $this->is_ready_checked() && ($this->prow || $this->conf->opt("noPapers")); 561 } 562 563 private function is_ready_checked() { 564 if ($this->useRequest) 565 return !!$this->qreq->submitpaper; 566 else if ($this->prow && $this->prow->timeSubmitted > 0) 567 return true; 568 else 569 return !$this->conf->setting("sub_freeze") 570 && (!$this->prow 571 || (!$this->conf->opt("noPapers") && $this->prow->paperStorageId <= 1)); 572 } 573 574 private function echo_editable_complete() { 575 if ($this->canUploadFinal) { 576 echo Ht::hidden("submitpaper", 1); 577 return; 578 } 579 580 $checked = $this->is_ready_checked(); 581 echo '<div class="ready-container ', 582 (($this->prow && $this->prow->paperStorageId > 1) 583 || $this->conf->opt("noPapers") ? "foldo" : "foldc"), 584 '"><div class="checki fx"><span class="checkc">', 585 Ht::checkbox("submitpaper", 1, $checked, ["class" => "js-check-submittable"]), 586 " </span>"; 587 if ($this->conf->setting("sub_freeze")) 588 echo Ht::label("<strong>" . $this->conf->_("The submission is complete.") . "</strong>"), 589 '<p class="settings-ap hint">You must complete your submission before the deadline or it will not be reviewed. Completed submissions are frozen and cannot be changed further.</p>'; 590 else 591 echo Ht::label("<strong>" . $this->conf->_("The submission is ready for review.") . "</strong>"); 592 echo "</div></div>\n"; 593 } 594 595 static function document_upload_input($inputid, $dtype, $accepts) { 596 $t = '<input id="' . $inputid . '" type="file" name="' . $inputid . '"'; 597 if ($accepts !== null && count($accepts) == 1) 598 $t .= ' accept="' . $accepts[0]->mimetype . '"'; 599 $t .= ' size="30" class="'; 600 $k = ["document-uploader"]; 601 if ($dtype == DTYPE_SUBMISSION || $dtype == DTYPE_FINAL) 602 $k[] = "js-check-submittable"; 603 return $t . join(" ", $k) . '" />'; 604 } 605 606 function echo_editable_document(PaperOption $docx, $storageId) { 607 $dtype = $docx->id; 608 if ($dtype == DTYPE_SUBMISSION || $dtype == DTYPE_FINAL) { 609 $noPapers = $this->conf->opt("noPapers"); 610 if ($noPapers === 1 || $noPapers === true) 611 return; 612 } 613 $inputid = $dtype > 0 ? "opt" . $dtype : "paperUpload"; 614 615 $accepts = $docx->mimetypes(); 616 $field = $docx->field_key(); 617 $doc = null; 618 if ($this->prow && $this->user->can_view_pdf($this->prow) && $storageId > 1) 619 $doc = $this->prow->document($dtype, $storageId, true); 620 621 $msgs = []; 622 if ($accepts) 623 $msgs[] = htmlspecialchars(Mimetype::description($accepts)); 624 $msgs[] = "max " . ini_get("upload_max_filesize") . "B"; 625 $heading = $this->field_name(htmlspecialchars($docx->title)) . ' <span class="n">(' . join(", ", $msgs) . ")</span>"; 626 echo $this->editable_papt($field, $heading, ["for" => $doc ? false : $inputid], $docx), 627 $this->field_hint(htmlspecialchars($docx->title), $docx->description), 628 $this->messages_for($field); 629 630 echo '<div class="papev has-document" data-dtype="', $dtype, 631 '" data-document-name="', $docx->field_key(), '"'; 632 if ($doc) 633 echo ' data-docid="', $doc->paperStorageId, '"'; 634 echo '>'; 635 if ($dtype > 0) 636 echo Ht::hidden("has_opt" . $dtype, 1); 637 638 $upload_input = self::document_upload_input($inputid, $dtype, $accepts); 639 640 // current version, if any 641 $has_cf = false; 642 if ($doc) { 643 if ($doc->mimetype === "application/pdf") { 644 if (!$this->cf) 645 $this->cf = new CheckFormat($this->conf, CheckFormat::RUN_NO); 646 $spec = $this->conf->format_spec($dtype); 647 $has_cf = $spec && !$spec->is_empty(); 648 if ($has_cf) 649 $this->cf->check_document($this->prow, $doc); 650 } 651 652 echo '<div class="document-file nameless">', $doc->link_html(), '</div>', 653 '<div class="document-upload hidden">', $upload_input, '</div>', 654 '<div class="document-stamps">'; 655 if (($stamps = self::pdf_stamps_html($doc))) 656 echo $stamps; 657 echo '</div><div class="document-actions">', 658 Ht::button("Replace", ["class" => "btn ui js-replace-document document-action"]); 659 if ($dtype > 0) 660 '<a href="" class="ui js-remove-document document-action">Delete</a>'; 661 if ($has_cf && ($this->cf->failed || $this->cf->need_run || $this->cf->possible_run)) { 662 echo '<a href="" class="ui js-check-format document-action">', 663 ($this->cf->failed || $this->cf->need_run ? "Check format" : "Recheck format"), 664 '</a>'; 665 } else if ($has_cf && !$this->cf->has_problem()) { 666 echo '<span class="document-action dim">Format OK</span>'; 667 } 668 echo '</div>'; 669 if ($has_cf) { 670 echo '<div class="document-format">'; 671 if (!$this->cf->failed && $this->cf->has_problem()) 672 echo $this->cf->document_report($this->prow, $doc); 673 echo '</div>'; 674 } 675 } else { 676 echo '<div class="document-upload">', $upload_input, '</div>'; 677 } 678 679 echo "</div>"; 680 } 681 682 private function echo_editable_submission() { 683 if ($this->canUploadFinal) 684 $this->echo_editable_document($this->conf->paper_opts->get(DTYPE_FINAL), $this->prow ? $this->prow->finalPaperStorageId : 0); 685 else 686 $this->echo_editable_document($this->conf->paper_opts->get(DTYPE_SUBMISSION), $this->prow ? $this->prow->paperStorageId : 0); 687 echo "</div>\n\n"; 688 } 689 690 private function echo_editable_abstract() { 691 $noAbstract = $this->conf->opt("noAbstract"); 692 if ($noAbstract !== 1 && $noAbstract !== true) { 693 $title = $this->field_name("Abstract"); 694 if ($noAbstract === 2) 695 $title .= ' <span class="n">(optional)</span>'; 696 echo $this->editable_papt("abstract", $title, ["for" => "abstract"]), 697 $this->field_hint("Abstract"), 698 $this->messages_for("abstract"), 699 '<div class="papev abstract">'; 700 if (($fi = $this->conf->format_info($this->prow ? $this->prow->paperFormat : null))) 701 echo $fi->description_preview_html(); 702 echo $this->editable_textarea("abstract"), 703 "</div></div>\n\n"; 704 } 705 } 706 707 private function paptabAbstract() { 708 $text = $this->entryData("abstract"); 709 if (trim($text) === "") { 710 if ($this->conf->opt("noAbstract")) 711 return false; 712 else 713 $text = "[No abstract]"; 714 } 715 $extra = []; 716 if ($this->allFolded && $this->abstract_foldable($text)) 717 $extra = ["fold" => "paper", "foldnum" => 6, 718 "foldtitle" => "Toggle full abstract"]; 719 echo '<div class="paperinfo-cl"><div class="paperinfo-abstract"><div class="pg">', 720 $this->papt("abstract", $this->conf->_c("paper_field", "Abstract"), $extra), 721 '<div class="pavb abstract'; 722 if ($this->prow 723 && !$this->entryMatches 724 && ($format = $this->prow->format_of($text))) { 725 echo ' need-format" data-format="', $format, '.abs">', $text; 726 Ht::stash_script('$(render_text.on_page)', 'render_on_page'); 727 } else 728 echo ' format0">', Ht::format0($text); 729 echo "</div></div></div>"; 730 if ($extra) 731 echo '<div class="fn6 fx7 longtext-fader"></div>', 732 '<div class="fn6 fx7 longtext-expander"><a class="ui x js-foldup" href="" data-fold-target="6">[more]</a></div>'; 733 echo "</div>\n"; 734 if ($extra) 735 echo Ht::unstash_script("render_text.on_page()"); 736 return true; 737 } 738 739 private function editable_author_component_entry($n, $pfx, $au) { 740 $auval = ""; 741 if ($pfx === "auname") { 742 $js = ["size" => "35", "placeholder" => "Name", "autocomplete" => "off"]; 743 if ($au && $au->firstName && $au->lastName && !preg_match('@^\s*(v[oa]n\s+|d[eu]\s+)?\S+(\s+jr.?|\s+sr.?|\s+i+)?\s*$@i', $au->lastName)) 744 $auval = $au->lastName . ", " . $au->firstName; 745 else if ($au) 746 $auval = $au->name(); 747 } else if ($pfx === "auemail") { 748 $js = ["size" => "30", "placeholder" => "Email", "autocomplete" => "off"]; 749 $auval = $au ? $au->email : ""; 750 } else { 751 $js = ["size" => "32", "placeholder" => "Affiliation", "autocomplete" => "off"]; 752 $auval = $au ? $au->affiliation : ""; 753 } 754 755 $val = $auval; 756 if ($this->useRequest) { 757 $val = ($pfx === '$' ? "" : (string) $this->qreq["$pfx$n"]); 758 } 759 760 $js["class"] = "need-autogrow js-autosubmit e$pfx" . $this->has_error_class("$pfx$n"); 761 if ($val !== $auval) { 762 $js["data-default-value"] = $auval; 763 } 764 return Ht::entry("$pfx$n", $val, $js); 765 } 766 private function editable_authors_tr($n, $au, $max_authors) { 767 $t = '<tr>'; 768 if ($max_authors != 1) 769 $t .= '<td class="rxcaption">' . $n . '.</td>'; 770 return $t . '<td class="lentry">' 771 . $this->editable_author_component_entry($n, "auname", $au) . ' ' 772 . $this->editable_author_component_entry($n, "auemail", $au) . ' ' 773 . $this->editable_author_component_entry($n, "auaff", $au) 774 . '<span class="nb btnbox aumovebox"><a href="" class="ui btn qx need-tooltip row-order-ui moveup" data-tooltip="Move up" tabindex="-1">' 775 . Icons::ui_triangle(0) 776 . '</a><a href="" class="ui btn qx need-tooltip row-order-ui movedown" data-tooltip="Move down" tabindex="-1">' 777 . Icons::ui_triangle(2) 778 . '</a><a href="" class="ui btn qx need-tooltip row-order-ui delete" data-tooltip="Delete" tabindex="-1">✖</a></span></td></tr>'; 779 } 780 781 private function echo_editable_authors() { 782 $max_authors = (int) $this->conf->opt("maxAuthors"); 783 $min_authors = $max_authors > 0 ? min(5, $max_authors) : 5; 784 785 $sb = $this->conf->submission_blindness(); 786 $title = $this->conf->_c("paper_field/edit", "Authors", $max_authors); 787 if ($sb === Conf::BLIND_ALWAYS) 788 $title .= " (blind)"; 789 else if ($sb === Conf::BLIND_UNTILREVIEW) 790 $title .= " (blind until review)"; 791 echo $this->editable_papt("authors", $title); 792 $hint = "List the authors, including email addresses and affiliations."; 793 if ($sb === Conf::BLIND_ALWAYS) 794 $hint .= " Submission is blind, so reviewers will not see author information."; 795 else if ($sb === Conf::BLIND_UNTILREVIEW) 796 $hint .= " Reviewers will not see author information until they submit a review."; 797 echo $this->field_hint("Authors", $hint, $sb), 798 $this->messages_for("authors"), 799 '<div class="papev"><table id="auedittable" class="auedittable js-row-order">', 800 '<tbody class="need-row-order-autogrow" data-min-rows="', $min_authors, '" ', 801 ($max_authors > 0 ? 'data-max-rows="' . $max_authors . '" ' : ''), 802 'data-row-template="', htmlspecialchars($this->editable_authors_tr('$', null, $max_authors)), '">'; 803 804 $aulist = $this->prow ? $this->prow->author_list() : []; 805 if ($this->useRequest) { 806 $n = $nonempty_n = 0; 807 while (1) { 808 $auname = $this->qreq["auname" . ($n + 1)]; 809 $auemail = $this->qreq["auemail" . ($n + 1)]; 810 $auaff = $this->qreq["auaff" . ($n + 1)]; 811 if ($auname === null && $auemail === null && $auaff === null) { 812 break; 813 } 814 ++$n; 815 if ((string) $auname !== "" || (string) $auemail !== "" || (string) $auaff !== "") { 816 $nonempty_n = $n; 817 } 818 } 819 while (count($aulist) < $nonempty_n) { 820 $aulist[] = null; 821 } 822 } 823 824 for ($n = 1; $n <= count($aulist); ++$n) { 825 echo $this->editable_authors_tr($n, get($aulist, $n - 1), $max_authors); 826 } 827 if ($max_authors <= 0 || $n <= $max_authors) { 828 do { 829 echo $this->editable_authors_tr($n, null, $max_authors); 830 ++$n; 831 } while ($n <= $min_authors); 832 } 833 echo "</tbody></table></div></div>\n\n"; 834 } 835 836 private function authorData($table, $type, $viewAs = null) { 837 if ($this->matchPreg && isset($this->matchPreg["authorInformation"])) 838 $highpreg = $this->matchPreg["authorInformation"]; 839 else 840 $highpreg = false; 841 $this->entryMatches = 0; 842 $names = []; 843 844 if (empty($table)) { 845 return "[No authors]"; 846 } else if ($type === "last") { 847 foreach ($table as $au) { 848 $n = Text::abbrevname_text($au); 849 $names[] = Text::highlight($n, $highpreg, $nm); 850 $this->entryMatches += $nm; 851 } 852 return join(", ", $names); 853 } else { 854 foreach ($table as $au) { 855 $nm1 = $nm2 = $nm3 = 0; 856 $n = $e = $t = ""; 857 $n = trim(Text::highlight("$au->firstName $au->lastName", $highpreg, $nm1)); 858 if ($au->email !== "") { 859 $e = Text::highlight($au->email, $highpreg, $nm2); 860 $e = '<<a href="mailto:' . htmlspecialchars($au->email) 861 . '" class="mailto">' . $e . '</a>>'; 862 } 863 $t = ($n === "" ? $e : $n); 864 if ($au->affiliation !== "") 865 $t .= ' <span class="auaff">(' . Text::highlight($au->affiliation, $highpreg, $nm3) . ')</span>'; 866 if ($n !== "" && $e !== "") 867 $t .= " " . $e; 868 $this->entryMatches += $nm1 + $nm2 + $nm3; 869 $t = trim($t); 870 if ($au->email !== "" && $au->contactId 871 && $viewAs !== null && $viewAs->email !== $au->email && $viewAs->privChair) 872 $t .= " <a href=\"" . SelfHref::make($this->qreq, ["actas" => $au->email]) . "\">" . Ht::img("viewas.png", "[Act as]", array("title" => "Act as " . Text::name_text($au))) . "</a>"; 873 $names[] = '<p class="odname">' . $t . '</p>'; 874 } 875 return join("\n", $names); 876 } 877 } 878 879 private function _analyze_authors() { 880 // clean author information 881 $aulist = $this->prow->author_list(); 882 883 // find contact author information, combine with author table 884 $result = $this->conf->qe("select firstName, lastName, '' affiliation, email, contactId from ContactInfo where contactId?a", array_keys($this->prow->contacts())); 885 $contacts = array(); 886 while ($result && ($row = $result->fetch_object("Author"))) { 887 $match = -1; 888 for ($i = 0; $match < 0 && $i < count($aulist); ++$i) 889 if (strcasecmp($aulist[$i]->email, $row->email) == 0) 890 $match = $i; 891 if (($row->firstName !== "" || $row->lastName !== "") && $match < 0) { 892 $contact_n = $row->firstName . " " . $row->lastName; 893 $contact_preg = str_replace("\\.", "\\S*", "{\\b" . preg_quote($row->firstName) . "\\b.*\\b" . preg_quote($row->lastName) . "\\b}i"); 894 for ($i = 0; $match < 0 && $i < count($aulist); ++$i) { 895 $f = $aulist[$i]->firstName; 896 $l = $aulist[$i]->lastName; 897 if (($f !== "" || $l !== "") && $aulist[$i]->email === "") { 898 $author_n = $f . " " . $l; 899 $author_preg = str_replace("\\.", "\\S*", "{\\b" . preg_quote($f) . "\\b.*\\b" . preg_quote($l) . "\\b}i"); 900 if (preg_match($contact_preg, $author_n) 901 || preg_match($author_preg, $contact_n)) 902 $match = $i; 903 } 904 } 905 } 906 if ($match >= 0) { 907 $au = $aulist[$match]; 908 if ($au->email === "") 909 $au->email = $row->email; 910 } else { 911 $contacts[] = $au = $row; 912 $au->nonauthor = true; 913 } 914 $au->contactId = (int) $row->contactId; 915 Contact::set_sorter($au, $this->conf); 916 } 917 Dbl::free($result); 918 919 uasort($contacts, "Contact::compare"); 920 return array($aulist, $contacts); 921 } 922 923 private function paptabAuthors($skip_contacts) { 924 if ($this->view_authors == 0) { 925 echo '<div class="pg">', 926 $this->papt("authorInformation", $this->conf->_c("paper_field", "Authors", 0)), 927 '<div class="pavb"><i>Hidden for blind review</i></div>', 928 "</div>\n\n"; 929 return; 930 } 931 932 // clean author information 933 list($aulist, $contacts) = $this->_analyze_authors(); 934 935 // "author" or "authors"? 936 $auname = $this->conf->_c("paper_field", "Authors", count($aulist)); 937 if ($this->view_authors == 1) 938 $auname .= " (deblinded)"; 939 else if ($this->user->act_author_view($this->prow)) { 940 $sb = $this->conf->submission_blindness(); 941 if ($sb === Conf::BLIND_ALWAYS 942 || ($sb === Conf::BLIND_OPTIONAL && $this->prow->blind)) 943 $auname .= " (blind)"; 944 else if ($sb === Conf::BLIND_UNTILREVIEW) 945 $auname .= " (blind until review)"; 946 } 947 948 // header with folding 949 echo '<div class="pg">', 950 '<div class="pavt ui js-aufoldup', $this->error_class("authors"), 951 '"><span class="pavfn">'; 952 if ($this->view_authors == 1 || $this->allFolded) 953 echo '<a class="q ui js-aufoldup" href="" title="Toggle author display">'; 954 if ($this->view_authors == 1) 955 echo '<span class="fn8">', $this->conf->_c("paper_field", "Authors", 0), '</span><span class="fx8">'; 956 if ($this->allFolded) 957 echo expander(null, 9); 958 else if ($this->view_authors == 1) 959 echo expander(false); 960 echo $auname; 961 if ($this->view_authors == 1) 962 echo '</span>'; 963 if ($this->view_authors == 1 || $this->allFolded) 964 echo '</a>'; 965 echo '</span></div>'; 966 967 // contents 968 echo '<div class="pavb">'; 969 if ($this->view_authors == 1) 970 echo '<a class="q fn8 ui js-aufoldup" href="" title="Toggle author display">', 971 '+ <i>Hidden for blind review</i>', 972 '</a><div class="fx8">'; 973 if ($this->allFolded) 974 echo '<div class="fn9">', 975 $this->authorData($aulist, "last", null), 976 ' <a class="ui js-aufoldup" href="">[details]</a>', 977 '</div><div class="fx9">'; 978 echo $this->authorData($aulist, "col", $this->user); 979 if ($this->allFolded) 980 echo '</div>'; 981 if ($this->view_authors == 1) 982 echo '</div>'; 983 echo "</div></div>\n\n"; 984 985 // contacts 986 if (count($contacts) > 0 && !$skip_contacts) { 987 echo '<div class="pg fx9', ($this->view_authors > 1 ? "" : " fx8"), '">', 988 $this->papt("authorInformation", 989 $this->conf->_c("paper_field", "Contacts", count($contacts))), 990 '<div class="pavb">', 991 $this->authorData($contacts, "col", $this->user), 992 "</div></div>\n\n"; 993 } 994 } 995 996 private function unparse_option_html(PaperOptionValue $ov) { 997 $o = $ov->option; 998 $phtml = $o->unparse_page_html($this->prow, $ov); 999 if (!$phtml || count($phtml) <= 1) 1000 return []; 1001 $phtype = array_shift($phtml); 1002 $aufold = $this->view_options[$o->id] == 1; 1003 1004 $ts = []; 1005 if ($o->display() === PaperOption::DISP_SUBMISSION) { 1006 $div = $aufold ? '<div class="xd fx8">' : '<div class="xd">'; 1007 if ($phtype === PaperOption::PAGE_HTML_NAME) { 1008 foreach ($phtml as $p) 1009 $ts[] = $div . '<span class="pavfn">' . $p . "</span></div>\n"; 1010 } else if ($phtype === PaperOption::PAGE_HTML_FULL) { 1011 foreach ($phtml as $p) 1012 $ts[] = $div . $p . "</div>\n"; 1013 } else { 1014 $x = $div . '<span class="pavfn">' . htmlspecialchars($o->title) . '</span>'; 1015 foreach ($phtml as $p) 1016 $x .= '<div class="pavb">' . $p . '</div>'; 1017 $ts[] = $x . "</div>\n"; 1018 } 1019 } else if ($o->display() !== PaperOption::DISP_TOPICS) { 1020 $div = $aufold ? '<div class="pgsm fx8">' : '<div class="pgsm">'; 1021 if ($phtype === PaperOption::PAGE_HTML_NAME) { 1022 foreach ($phtml as $p) 1023 $ts[] = $div . '<div class="pavt"><span class="pavfn">' . $p . "</span></div></div>\n"; 1024 } else if ($phtype === PaperOption::PAGE_HTML_FULL) { 1025 foreach ($phtml as $p) 1026 $ts[] = $div . $p . "</div>\n"; 1027 } else { 1028 $x = $div . '<div class="pavt"><span class="pavfn">' . htmlspecialchars($o->title) . '</span></div>'; 1029 foreach ($phtml as $p) 1030 $x .= '<div class="pavb">' . $p . '</div>'; 1031 $ts[] = $x . "</div>\n"; 1032 } 1033 } else { 1034 $div = $aufold ? '<div class="fx8">' : '<div>'; 1035 if ($phtype === PaperOption::PAGE_HTML_NAME) { 1036 foreach ($phtml as $p) 1037 $ts[] = $div . '<span class="papon">' . $p . "</span></div>\n"; 1038 } else if ($phtype === PaperOption::PAGE_HTML_FULL) { 1039 foreach ($phtml as $p) 1040 $ts[] = $div . $p . "</div>\n"; 1041 } else { 1042 foreach ($phtml as $p) { 1043 if (!empty($ts) 1044 || $p === "" 1045 || $p[0] !== "<" 1046 || !preg_match('/\A((?:<(?:div|p).*?>)*)([\s\S]*)\z/', $p, $cm)) 1047 $cm = [null, "", $p]; 1048 $ts[] = $div . $cm[1] . '<span class="papon">' . htmlspecialchars($o->title) . ':</span> ' . $cm[2] . "</div>\n"; 1049 } 1050 } 1051 } 1052 return $ts; 1053 } 1054 1055 private function paptab_topics() { 1056 if (!($tmap = $this->prow->named_topic_map())) 1057 return ""; 1058 $interests = $this->user->topic_interest_map(); 1059 $k = 0; 1060 $ts = []; 1061 foreach ($tmap as $tid => $tname) { 1062 $t = '<li class="topictp'; 1063 if (($i = get($interests, $tid))) 1064 $t .= ' topic' . $i; 1065 $x = htmlspecialchars($tname); 1066 if ($this->user->isPC && strpos($tname, "\"") === false) 1067 $x = Ht::link($x, hoturl("search", ["q" => "topic:\"$tname\""]), ["class" => "qq"]); 1068 $ts[] = $t . '">' . $x . '</li>'; 1069 if ($k < 2 && strlen($tname) > 50 && UnicodeHelper::utf8_glyphlen($tname) > 50) 1070 $k = 2; 1071 else if ($k < 1 && strlen($tname) > 20 && UnicodeHelper::utf8_glyphlen($tname) > 20) 1072 $k = 1; 1073 } 1074 if (count($ts) < 4) 1075 $k = "long"; 1076 else 1077 $k = get(["short", "medium", "long"], $k); 1078 return '<ul class="topict topict-' . $k . '">' . join("", $ts) . '</ul>'; 1079 } 1080 1081 private function paptabTopicsOptions() { 1082 $topicdata = $this->paptab_topics(); 1083 $optt = $optp = []; 1084 $optp_nfold = $optt_ndoc = $optt_nfold = 0; 1085 1086 foreach ($this->prow->options() as $ov) { 1087 $o = $ov->option; 1088 if ($o->display() !== PaperOption::DISP_SUBMISSION 1089 && $o->display() >= 0 1090 && get($this->view_options, $o->id) 1091 && ($oh = $this->unparse_option_html($ov))) { 1092 $aufold = $this->view_options[$o->id] == 1; 1093 if ($o->display() === PaperOption::DISP_TOPICS) { 1094 $optt = array_merge($optt, $oh); 1095 if ($aufold) 1096 $optt_nfold += count($oh); 1097 if ($o->has_document()) 1098 $optt_ndoc += count($oh); 1099 } else { 1100 $optp = array_merge($optp, $oh); 1101 if ($aufold) 1102 $optp_nfold += count($oh); 1103 } 1104 } 1105 } 1106 1107 if (!empty($optp)) { 1108 $div = count($optp) === $optp_nfold ? '<div class="pg fx8">' : '<div class="pg">'; 1109 echo $div, join("", $optp), "</div>\n"; 1110 } 1111 1112 if ($topicdata !== "" || !empty($optt)) { 1113 $infotypes = array(); 1114 if ($optt_ndoc > 0) 1115 $infotypes[] = "Attachments"; 1116 if (count($optt) !== $optt_ndoc) 1117 $infotypes[] = "Options"; 1118 $options_name = commajoin($infotypes); 1119 if ($topicdata !== "") 1120 array_unshift($infotypes, "Topics"); 1121 $tanda = commajoin($infotypes); 1122 1123 if ($this->allFolded) { 1124 $extra = array("fold" => "paper", "foldnum" => 5, 1125 "foldtitle" => "Toggle " . strtolower($tanda)); 1126 $eclass = " fx5"; 1127 } else { 1128 $extra = null; 1129 $eclass = ""; 1130 } 1131 1132 if ($topicdata !== "") { 1133 echo "<div class='pg'>", 1134 $this->papt("topics", array("Topics", $tanda), $extra), 1135 "<div class='pavb$eclass'>", $topicdata, "</div></div>\n\n"; 1136 $extra = null; 1137 $tanda = $options_name; 1138 } 1139 1140 if (!empty($optt)) { 1141 echo '<div class="pg', ($extra ? "" : $eclass), 1142 (count($optt) === $optt_nfold ? " fx8" : ""), '">', 1143 $this->papt("options", array($options_name, $tanda), $extra), 1144 "<div class=\"pavb$eclass\">", join("", $optt), "</div></div>\n\n"; 1145 } 1146 } 1147 } 1148 1149 private function editable_newcontact_row($num) { 1150 if ($num === '$') { 1151 $checked = true; 1152 $name = $email = ""; 1153 $cerror = false; 1154 } else { 1155 $checked = !$this->useRequest || $this->qreq["newcontact_active_{$num}"]; 1156 $email = (string) ($this->useRequest ? $this->qreq["newcontact_email_{$num}"] : ""); 1157 $name = (string) ($this->useRequest ? $this->qreq["newcontact_name_{$num}"] : ""); 1158 $cerror = $this->has_problem_at("contactAuthor") || $this->has_problem_at("contacts"); 1159 } 1160 $email = $email === "Email" ? "" : $email; 1161 $name = $name === "Name" ? "" : $name; 1162 1163 return '<div class="checki"><span class="checkc">' 1164 . Ht::checkbox("newcontact_active_{$num}", 1, $checked, ["data-default-checked" => 1]) 1165 . ' </span>' 1166 . Ht::entry("newcontact_name_{$num}", $name, ["size" => 30, "placeholder" => "Name", "class" => ($cerror ? "has-error " : "") . "want-focus js-autosubmit", "autocomplete" => "off"]) 1167 . ' ' 1168 . Ht::entry("newcontact_email_{$num}", $email, ["size" => 20, "placeholder" => "Email", "class" => ($cerror ? "has-error " : "") . "js-autosubmit", "autocomplete" => "off"]) 1169 . '</div>'; 1170 } 1171 1172 private function echo_editable_contact_author() { 1173 if ($this->prow) { 1174 list($aulist, $contacts) = $this->_analyze_authors(); 1175 $contacts = array_merge($aulist, $contacts); 1176 } else if (!$this->admin) { 1177 $contacts = [new Author($this->user)]; 1178 $contacts[0]->contactId = $this->user->contactId; 1179 Contact::set_sorter($contacts[0], $this->conf); 1180 } else 1181 $contacts = []; 1182 usort($contacts, "Contact::compare"); 1183 1184 $cerror = $this->has_problem_at("contactAuthor") || $this->has_problem_at("contacts"); 1185 echo Ht::hidden("has_contacts", 1), 1186 '<div id="foldcontactauthors" class="papeg">', 1187 '<div class="papet', 1188 ($cerror ? " error" : ""), 1189 '"><span class="papfn">', 1190 $this->field_name("Contacts"), 1191 '</span></div>'; 1192 1193 // Editable version 1194 echo $this->field_hint("Contacts", "These users can edit the submission and view reviews. All listed authors with site accounts are contacts. You can add contacts who aren’t in the author list or create accounts for authors who haven’t yet logged in.", !!$this->prow), 1195 '<div class="papev js-row-order"><div>'; 1196 1197 $req_cemail = []; 1198 if ($this->useRequest) { 1199 for ($cidx = 1; isset($this->qreq["contact_email_{$cidx}"]); ++$cidx) 1200 if ($this->qreq["contact_active_{$cidx}"]) 1201 $req_cemail[strtolower($this->qreq["contact_email_{$cidx}"])] = true; 1202 } 1203 1204 $cidx = 1; 1205 foreach ($contacts as $au) { 1206 if ($au->nonauthor 1207 && (strcasecmp($this->user->email, $au->email) != 0 || $this->allow_admin)) { 1208 $ctl = Ht::hidden("contact_email_{$cidx}", $au->email) 1209 . Ht::checkbox("contact_active_{$cidx}", 1, !$this->useRequest || isset($req_cemail[strtolower($au->email)]), ["data-default-checked" => true]); 1210 } else if ($au->contactId) { 1211 $ctl = Ht::hidden("contact_email_{$cidx}", $au->email) 1212 . Ht::hidden("contact_active_{$cidx}", 1) 1213 . Ht::checkbox(null, null, true, ["disabled" => true]); 1214 } else if ($au->email && validate_email($au->email)) { 1215 $ctl = Ht::hidden("contact_email_{$cidx}", $au->email) 1216 . Ht::checkbox("contact_active_{$cidx}", 1, $this->useRequest && isset($req_cemail[strtolower($au->email)]), ["data-default-checked" => ""]); 1217 } else 1218 continue; 1219 echo '<div class="checki"><label><span class="checkc">', $ctl, ' </span>', 1220 Text::user_html_nolink($au); 1221 if ($au->nonauthor) 1222 echo ' (<em>non-author</em>)'; 1223 if ($this->user->privChair 1224 && $au->contactId 1225 && $au->contactId != $this->user->contactId) 1226 echo ' ', actas_link($au); 1227 echo '</label></div>'; 1228 ++$cidx; 1229 } 1230 echo '</div><div data-row-template="', 1231 htmlspecialchars($this->editable_newcontact_row('$')), 1232 '">'; 1233 if ($this->useRequest) { 1234 for ($i = 1; isset($this->qreq["newcontact_email_{$i}"]); ++$i) 1235 echo $this->editable_newcontact_row($i); 1236 } 1237 echo '</div><div class="ug">', 1238 Ht::button("Add contact", ["class" => "ui btn row-order-ui addrow"]), 1239 "</div></div></div>\n\n"; 1240 } 1241 1242 private function echo_editable_anonymity() { 1243 if ($this->conf->submission_blindness() != Conf::BLIND_OPTIONAL 1244 || $this->editable !== "f") 1245 return; 1246 $pblind = !$this->prow || $this->prow->blind; 1247 $blind = $this->useRequest ? !!$this->qreq->blind : $pblind; 1248 $heading = '<span class="checkc">' . Ht::checkbox("blind", 1, $blind, ["data-default-checked" => $pblind]) . " </span>" . $this->field_name("Anonymous submission"); 1249 echo $this->editable_papt("blind", $heading, ["for" => "checkbox"]), 1250 $this->field_hint("Anonymous submission", "Check this box to submit anonymously (reviewers won’t be shown the author list). Make sure you also remove your name from the submission itself!"), 1251 $this->messages_for("blind"), 1252 "</div>\n\n"; 1253 } 1254 1255 private function echo_editable_collaborators() { 1256 if (!$this->conf->setting("sub_collab") 1257 || ($this->editable === "f" && !$this->admin)) 1258 return; 1259 $sub_pcconf = $this->conf->setting("sub_pcconf"); 1260 1261 echo $this->editable_papt("collaborators", $this->field_name($sub_pcconf ? "Other conflicts" : "Potential conflicts"), ["for" => "collaborators"]), 1262 '<div class="paphint"><div class="mmm">'; 1263 if ($this->conf->setting("sub_pcconf")) 1264 echo "List <em>other</em> people and institutions with which 1265 the authors have conflicts of interest. This will help us avoid 1266 conflicts when assigning external reviews. No need to list people 1267 at the authors’ own institutions."; 1268 else 1269 echo "List people and institutions with which the authors have 1270 conflicts of interest. ", $this->conf->message_html("conflictdef"), " 1271 Be sure to include conflicted <a href='", hoturl("users", "t=pc"), "'>PC members</a>. 1272 We use this information when assigning PC and external reviews."; 1273 echo "</div><div class=\"mmm\"><strong>List one conflict per line</strong>, using parentheses for affiliations and institutions. Examples: “Jelena Markovic (EPFL)”, “All (University of Southern California)”.</div></div>", 1274 $this->messages_for("collaborators"), 1275 '<div class="papev">', 1276 $this->editable_textarea("collaborators"), 1277 "</div></div>\n\n"; 1278 } 1279 1280 private function papstrip_tags_background_classes($viewable) { 1281 $t = "has-tag-classes pscopen"; 1282 if (($color = $this->prow->conf->tags()->styles($viewable, TagMap::STYLE_BG))) { 1283 TagMap::mark_pattern_fill($color); 1284 $t .= " " . join(" ", $color); 1285 } 1286 return $t; 1287 } 1288 1289 private function _papstripBegin($foldid = null, $folded = null, $extra = null) { 1290 if (!$this->npapstrip) { 1291 echo '<div class="pspcard_container"><div class="pspcard">', 1292 '<div class="pspcard_body"><div class="pspcard_fold">', 1293 '<div style="float:right;margin-left:1em"><span class="psfn">More ', expander(true), '</span></div>'; 1294 1295 if ($this->prow && ($viewable = $this->prow->viewable_tags($this->user))) { 1296 $tagger = new Tagger($this->user); 1297 echo '<div class="', $this->papstrip_tags_background_classes($viewable), '">', 1298 '<span class="psfn">Tags:</span> ', 1299 $tagger->unparse_and_link($viewable), 1300 '</div>'; 1301 } else 1302 echo '<hr class="c" />'; 1303 1304 echo '</div><div class="pspcard_open">'; 1305 Ht::stash_script('$(".pspcard_fold").click(function(evt){$(".pspcard_fold").hide();$(".pspcard_open").show();evt.preventDefault()})'); 1306 } 1307 echo '<div'; 1308 if ($foldid) 1309 echo " id=\"fold$foldid\""; 1310 echo ' class="psc'; 1311 if (!$this->npapstrip) 1312 echo " psc1"; 1313 if ($foldid) 1314 echo " fold", ($folded ? "c" : "o"); 1315 if ($extra) { 1316 if (isset($extra["class"])) 1317 echo " ", $extra["class"]; 1318 foreach ($extra as $k => $v) 1319 if ($k !== "class") 1320 echo "\" $k=\"", str_replace("\"", """, $v); 1321 } 1322 echo '">'; 1323 ++$this->npapstrip; 1324 } 1325 1326 private function papstripCollaborators() { 1327 if (!$this->conf->setting("sub_collab") || !$this->prow->collaborators 1328 || strcasecmp(trim($this->prow->collaborators), "None") == 0) 1329 return; 1330 $name = $this->conf->setting("sub_pcconf") ? "Other conflicts" : "Potential conflicts"; 1331 $fold = $this->conf->session("foldpscollab", 1) ? 1 : 0; 1332 1333 $data = $this->entryData("collaborators", "col"); 1334 if ($this->entryMatches || !$this->allFolded) 1335 $fold = 0; 1336 1337 $this->_papstripBegin("pscollab", $fold, ["data-fold-session" => "foldpscollab"]); 1338 echo $this->papt("collaborators", $name, 1339 ["type" => "ps", "fold" => "pscollab", "folded" => $fold]), 1340 "<div class='psv'><div class='fx'>", $data, 1341 "</div></div></div>\n\n"; 1342 } 1343 1344 private function echo_editable_topics() { 1345 if (!$this->conf->has_topics()) 1346 return; 1347 echo $this->editable_papt("topics", $this->field_name("Topics")), 1348 $this->field_hint("Topics", "Select any topics that apply to your submission."), 1349 $this->messages_for("topics"), 1350 '<div class="papev">', 1351 Ht::hidden("has_topics", 1), 1352 '<div class="ctable">'; 1353 $ptopics = $this->prow ? $this->prow->topic_map() : []; 1354 foreach ($this->conf->topic_map() as $tid => $tname) { 1355 $pchecked = isset($ptopics[$tid]); 1356 $checked = $this->useRequest ? isset($this->qreq["top$tid"]) : $pchecked; 1357 echo '<div class="ctelt"><div class="ctelti checki"><label><span class="checkc">', 1358 Ht::checkbox("top$tid", 1, $checked, ["data-default-checked" => $pchecked, "data-range-type" => "topic", "class" => "uix js-range-click topic-entry"]), 1359 ' </span>', $tname, '</label></div></div>'; 1360 } 1361 echo "</div></div></div>\n\n"; 1362 } 1363 1364 function echo_editable_option_papt(PaperOption $o, $heading = null, $for = null) { 1365 if (!$heading) 1366 $heading = $this->field_name(htmlspecialchars($o->title)); 1367 if ($for === null || $for === true) 1368 $for = $o->formid; 1369 echo $this->editable_papt($o->formid, $heading, ["id" => "{$o->formid}_div", "for" => $for], $o), 1370 $this->field_hint(htmlspecialchars($o->title), $o->description), 1371 $this->messages_for($o->formid), Ht::hidden("has_{$o->formid}", 1); 1372 } 1373 1374 private function echo_editable_pc_conflicts() { 1375 if (!$this->conf->setting("sub_pcconf") 1376 || ($this->editable === "f" && !$this->admin)) 1377 return; 1378 $pcm = $this->conf->full_pc_members(); 1379 if (empty($pcm)) 1380 return; 1381 1382 $selectors = $this->conf->setting("sub_pcconfsel"); 1383 $show_colors = $this->user->can_view_reviewer_tags($this->prow); 1384 1385 if ($selectors) { 1386 $ctypes = Conflict::$type_descriptions; 1387 $extra = array("class" => "pcconf-selector"); 1388 if ($this->admin) { 1389 $ctypes["xsep"] = null; 1390 $ctypes[CONFLICT_CHAIRMARK] = "Confirmed conflict"; 1391 $extra["optionstyles"] = array(CONFLICT_CHAIRMARK => "font-weight:bold"); 1392 } 1393 foreach ($ctypes as &$ctype) 1394 $ctype = $this->conf->_c("conflict_type", $ctype); 1395 unset($ctype); 1396 $author_ctype = $this->conf->_c("conflict_type", "Author"); 1397 } 1398 1399 echo $this->editable_papt("pcconf", $this->field_name("PC conflicts")), 1400 "<div class='paphint'>Select the PC members who have conflicts of interest with this submission. ", $this->conf->message_html("conflictdef"), "</div>\n", 1401 $this->messages_for("pcconf"), 1402 '<div class="papev">', 1403 Ht::hidden("has_pcconf", 1), 1404 '<div class="pc_ctable">'; 1405 foreach ($pcm as $id => $p) { 1406 $pct = $this->prow ? $this->prow->conflict_type($p) : 0; 1407 if ($this->useRequest) 1408 $ct = Conflict::constrain_editable($this->qreq["pcc$id"], $this->admin); 1409 else 1410 $ct = $pct; 1411 $pcconfmatch = null; 1412 if ($this->prow && $pct < CONFLICT_AUTHOR) 1413 $pcconfmatch = $this->prow->potential_conflict_html($p, $pct <= 0); 1414 1415 $label = '<span class="taghl">' . $this->user->name_html_for($p) . '</span>'; 1416 if ($p->affiliation) 1417 $label .= '<span class="pcconfaff">' . htmlspecialchars(UnicodeHelper::utf8_abbreviate($p->affiliation, 60)) . '</span>'; 1418 1419 echo '<div class="ctelt"><div class="ctelti'; 1420 if (!$selectors) 1421 echo ' checki'; 1422 echo ' clearfix'; 1423 if ($show_colors && ($classes = $p->viewable_color_classes($this->user))) 1424 echo ' ', $classes; 1425 if ($pct) 1426 echo ' boldtag'; 1427 if ($pcconfmatch) 1428 echo ' need-tooltip" data-tooltip-class="gray" data-tooltip="', str_replace('"', '"', $pcconfmatch[1]); 1429 echo '"><label>'; 1430 1431 $js = ["id" => "pcc$id"]; 1432 $disabled = $pct >= CONFLICT_AUTHOR 1433 || ($pct > 0 && !$this->admin && !Conflict::is_author_mark($pct)); 1434 if ($selectors) { 1435 echo '<span class="pcconf-editselector">'; 1436 if ($disabled) { 1437 echo '<strong>', ($pct >= CONFLICT_AUTHOR ? $author_ctype : "Conflict"), '</strong>', 1438 Ht::hidden("pcc$id", $pct, ["class" => "conflict-entry"]); 1439 } else { 1440 $js["class"] = "conflict-entry"; 1441 $js["data-default-value"] = Conflict::constrain_editable($pct, $this->admin); 1442 echo Ht::select("pcc$id", $ctypes, Conflict::constrain_editable($ct, $this->admin), $js); 1443 } 1444 echo '</span>', $label; 1445 } else { 1446 $js["disabled"] = $disabled; 1447 $js["data-default-checked"] = $pct > 0; 1448 $js["data-range-type"] = "pcc"; 1449 $js["class"] = "uix js-range-click conflict-entry"; 1450 echo '<span class="checkc">', 1451 Ht::checkbox("pcc$id", $ct > 0 ? $ct : CONFLICT_AUTHORMARK, 1452 $ct > 0, $js), 1453 ' </span>', $label; 1454 } 1455 echo "</label>"; 1456 if ($pcconfmatch) 1457 echo $pcconfmatch[0]; 1458 echo "</div></div>"; 1459 } 1460 echo "</div>\n</div></div>\n\n"; 1461 } 1462 1463 private function papstripPCConflicts() { 1464 assert(!$this->editable); 1465 if (!$this->prow) 1466 return; 1467 1468 $pcconf = array(); 1469 $pcm = $this->conf->pc_members(); 1470 foreach ($this->prow->pc_conflicts() as $id => $x) { 1471 $p = $pcm[$id]; 1472 $text = "<p class=\"odname\">" . $this->user->name_html_for($p) . "</p>"; 1473 if ($this->user->isPC && ($classes = $p->viewable_color_classes($this->user))) 1474 $text = "<div class=\"pscopen $classes taghh\">$text</div>"; 1475 $pcconf[$p->sort_position] = $text; 1476 } 1477 ksort($pcconf); 1478 if (!count($pcconf)) 1479 $pcconf[] = "<p class=\"odname\">None</p>"; 1480 $this->_papstripBegin(); 1481 echo $this->papt("pcconflict", "PC conflicts", array("type" => "ps")), 1482 "<div class='psv psconf'>", join("", $pcconf), "</div></div>\n"; 1483 } 1484 1485 private function _papstripLeadShepherd($type, $name, $showedit, $wholefold) { 1486 $editable = ($type === "manager" ? $this->user->privChair : $this->admin); 1487 1488 $field = $type . "ContactId"; 1489 if ($this->prow->$field == 0 && !$editable) 1490 return; 1491 $value = $this->prow->$field; 1492 1493 if ($wholefold === null) 1494 $this->_papstripBegin($type, true); 1495 else { 1496 echo '<div id="fold', $type, '" class="foldc">'; 1497 $this->_papstripBegin(null, true); 1498 } 1499 echo $this->papt($type, $name, array("type" => "ps", "fold" => $editable ? $type : false, "folded" => true)), 1500 '<div class="psv">'; 1501 $p = $this->conf->pc_member_by_id($value); 1502 $n = $p ? $this->user->name_html_for($p) : ($value ? "Unknown!" : ""); 1503 $text = '<p class="fn odname js-psedit-result">' . $n . '</p>'; 1504 echo '<div class="pcopen taghh'; 1505 if ($p && ($classes = $this->user->user_color_classes_for($p))) 1506 echo ' ', $classes; 1507 echo '">', $text, '</div>'; 1508 1509 if ($editable) { 1510 $selopt = [0]; 1511 foreach ($this->conf->pc_members() as $p) 1512 if (!$this->prow 1513 || $p->can_accept_review_assignment($this->prow) 1514 || $p->contactId == $value) 1515 $selopt[] = $p->contactId; 1516 $this->conf->stash_hotcrp_pc($this->user); 1517 echo '<form class="submit-ui fx"><div>', 1518 Ht::select($type, [], 0, ["class" => "psc-select need-pcselector want-focus", "style" => "width:99%", "data-pcselector-options" => join(" ", $selopt), "data-pcselector-selected" => $value]), 1519 '</div></form>'; 1520 Ht::stash_script('edit_paper_ui.prepare_psedit.call($$("fold' . $type . '"),{p:' . $this->prow->paperId . ',fn:"' . $type . '"})'); 1521 } 1522 1523 if ($wholefold === null) 1524 echo "</div></div>\n"; 1525 else 1526 echo "</div></div></div>\n"; 1527 } 1528 1529 private function papstripLead($showedit) { 1530 $this->_papstripLeadShepherd("lead", "Discussion lead", $showedit || $this->qreq->atab === "lead", null); 1531 } 1532 1533 private function papstripShepherd($showedit, $fold) { 1534 $this->_papstripLeadShepherd("shepherd", "Shepherd", $showedit || $this->qreq->atab === "shepherd", $fold); 1535 } 1536 1537 private function papstripManager($showedit) { 1538 $this->_papstripLeadShepherd("manager", "Paper administrator", $showedit || $this->qreq->atab === "manager", null); 1539 } 1540 1541 private function papstripTags() { 1542 if (!$this->prow || !$this->user->can_view_tags($this->prow)) 1543 return; 1544 $tags = $this->prow->all_tags_text(); 1545 $is_editable = $this->user->can_change_some_tag($this->prow); 1546 if ($tags === "" && !$is_editable) 1547 return; 1548 1549 // Note that tags MUST NOT contain HTML special characters. 1550 $tagger = new Tagger($this->user); 1551 $viewable = $this->prow->viewable_tags($this->user); 1552 1553 $tx = $tagger->unparse_and_link($viewable); 1554 $unfolded = $is_editable && ($this->has_problem_at("tags") || $this->qreq->atab === "tags"); 1555 1556 $this->_papstripBegin("tags", true); 1557 echo '<div class="', $this->papstrip_tags_background_classes($viewable), '">'; 1558 1559 if ($is_editable) { 1560 echo Ht::form(hoturl("paper", "p=" . $this->prow->paperId), ["data-pid" => $this->prow->paperId, "data-no-tag-report" => $unfolded ? 1 : null]); 1561 Ht::stash_script('edit_paper_ui.prepare_pstags.call($$("foldtags"))'); 1562 } 1563 1564 echo $this->papt("tags", "Tags", array("type" => "ps", "editfolder" => ($is_editable ? "tags" : 0))), 1565 '<div class="psv">'; 1566 if ($is_editable) { 1567 // tag report form 1568 $treport = PaperApi::tagreport($this->user, $this->prow); 1569 $tm0 = $tm1 = []; 1570 $tms = 0; 1571 foreach ($treport->tagreport as $tr) { 1572 $search = isset($tr->search) ? $tr->search : "#" . $tr->tag; 1573 $tm = Ht::link("#" . $tr->tag, hoturl("search", ["q" => $search]), ["class" => "q"]) . ": " . $tr->message; 1574 $tms = max($tms, $tr->status); 1575 $tm0[] = $tm; 1576 if ($tr->status > 0 && $this->prow->has_tag($tagger->expand($tr->tag))) 1577 $tm1[] = $tm; 1578 } 1579 1580 // uneditable 1581 echo '<div class="fn want-tag-report-warnings">'; 1582 if (!empty($tm1)) 1583 echo Ht::xmsg("warning", $tm1); 1584 echo '</div><div class="fn js-tag-result">', 1585 ($tx === "" ? "None" : $tx), '</div>'; 1586 1587 echo '<div class="fx js-tag-editor"><div class="want-tag-report">'; 1588 if (!empty($tm0)) 1589 echo Ht::xmsg($tms, $tm0); 1590 echo "</div>"; 1591 $editable = $tags; 1592 if ($this->prow) 1593 $editable = $this->prow->editable_tags($this->user); 1594 echo '<div style="position:relative">', 1595 '<textarea cols="20" rows="4" name="tags" style="width:97%;margin:0" class="want-focus">', 1596 $tagger->unparse($editable), 1597 "</textarea></div>", 1598 '<div class="aab aabr"><div class="aabut">', 1599 Ht::submit("save", "Save", ["class" => "btn btn-primary"]), 1600 '</div><div class="aabut">', 1601 Ht::submit("cancel", "Cancel"), 1602 "</div></div>", 1603 "<span class='hint'><a href='", hoturl("help", "t=tags"), "'>Learn more</a> <span class='barsep'>·</span> <strong>Tip:</strong> Twiddle tags like “~tag” are visible only to you.</span>", 1604 "</div>"; 1605 } else 1606 echo '<div class="js-tag-result">', ($tx === "" ? "None" : $tx), '</div>'; 1607 echo "</div>"; 1608 1609 if ($is_editable) 1610 echo "</form>"; 1611 if ($unfolded) 1612 echo Ht::unstash_script('fold("tags",0)'); 1613 echo "</div></div>\n"; 1614 } 1615 1616 function papstripOutcomeSelector() { 1617 $this->_papstripBegin("decision", $this->qreq->atab !== "decision"); 1618 echo $this->papt("decision", "Decision", array("type" => "ps", "fold" => "decision")), 1619 '<div class="psv"><form class="submit-ui fx"><div>'; 1620 if (isset($this->qreq->forceShow)) 1621 echo Ht::hidden("forceShow", $this->qreq->forceShow ? 1 : 0); 1622 echo decisionSelector($this->prow->outcome, null, " class=\"want-focus\" style=\"width:99%\""), 1623 '</div></form><p class="fn odname js-psedit-result">', 1624 htmlspecialchars($this->conf->decision_name($this->prow->outcome)), 1625 "</p></div></div>\n"; 1626 Ht::stash_script('edit_paper_ui.prepare_psedit.call($$("folddecision"),{p:' . $this->prow->paperId . ',fn:"decision"})'); 1627 } 1628 1629 function papstripReviewPreference() { 1630 $this->_papstripBegin("revpref"); 1631 echo $this->papt("revpref", "Review preference", array("type" => "ps")), 1632 "<div class=\"psv\"><form class=\"ui\"><div>"; 1633 $rp = unparse_preference($this->prow); 1634 $rp = ($rp == "0" ? "" : $rp); 1635 echo "<input id=\"revprefform_d\" type=\"text\" name=\"revpref", $this->prow->paperId, 1636 "\" size=\"4\" value=\"$rp\" class=\"revpref want-focus want-select\" />", 1637 "</div></form></div></div>\n"; 1638 Ht::stash_script("add_revpref_ajax(\"#revprefform_d\",true);shortcut(\"revprefform_d\").add()"); 1639 } 1640 1641 private function papstrip_tag_entry($id, $folds) { 1642 if (!$this->npapstrip_tag_entry) 1643 $this->_papstripBegin(null, null, ["class" => "psc_te"]); 1644 ++$this->npapstrip_tag_entry; 1645 echo '<div', ($id ? " id=\"fold{$id}\"" : ""), 1646 ' class="pste', ($folds ? " $folds" : ""), '">'; 1647 } 1648 1649 private function papstrip_tag_float($tag, $kind, $type) { 1650 $class = "is-nonempty-tags floatright"; 1651 if (($totval = $this->prow->tag_value($tag)) === false) { 1652 $totval = ""; 1653 $class .= " hidden"; 1654 } 1655 $reverse = $type !== "rank"; 1656 $extradiv = ""; 1657 if ($type === "vote" || $type === "approval") { 1658 $class .= " need-tooltip"; 1659 $extradiv = ' data-tooltip-dir="h" data-tooltip-info="votereport" data-tag="' . htmlspecialchars($tag) . '"'; 1660 } 1661 return '<div class="' . $class . '"' . $extradiv 1662 . '><a class="qq" href="' . hoturl("search", "q=" . urlencode("show:#$tag sort:" . ($reverse ? "-" : "") . "#$tag")) . '">' 1663 . '<span class="is-tag-index" data-tag-base="' . $tag . '">' . $totval . '</span> ' . $kind . '</a></div>'; 1664 } 1665 1666 private function papstrip_tag_entry_title($start, $tag, $value) { 1667 $title = $start . '<span class="fn is-nonempty-tags'; 1668 if ($value === "") 1669 $title .= ' hidden'; 1670 return $title . '">: <span class="is-tag-index" data-tag-base="' . $tag . '">' . $value . '</span></span>'; 1671 } 1672 1673 private function papstripRank($tag) { 1674 $id = "rank_" . html_id_encode($tag); 1675 if (($myval = $this->prow->tag_value($this->user->contactId . "~$tag")) === false) 1676 $myval = ""; 1677 $totmark = $this->papstrip_tag_float($tag, "overall", "rank"); 1678 1679 $this->papstrip_tag_entry($id, "foldc fold2c"); 1680 echo Ht::form("", ["id" => "{$id}form", "data-pid" => $this->prow->paperId]); 1681 if (isset($this->qreq->forceShow)) 1682 echo Ht::hidden("forceShow", $this->qreq->forceShow); 1683 echo $this->papt($id, $this->papstrip_tag_entry_title("#$tag rank", "~$tag", $myval), 1684 array("type" => "ps", "fold" => $id, "float" => $totmark)), 1685 '<div class="psv"><div class="fx">', 1686 Ht::entry("tagindex", $myval, 1687 array("size" => 4, "class" => "is-tag-index want-focus", 1688 "data-tag-base" => "~$tag")), 1689 ' <span class="barsep">·</span> ', 1690 '<a href="', hoturl("search", "q=" . urlencode("editsort:#~$tag")), '">Edit all</a>', 1691 " <div class='hint' style='margin-top:4px'><strong>Tip:</strong> <a href='", hoturl("search", "q=" . urlencode("editsort:#~$tag")), "'>Search “editsort:#~{$tag}”</a> to drag and drop your ranking, or <a href='", hoturl("offline"), "'>use offline reviewing</a> to rank many papers at once.</div>", 1692 "</div></div></form></div>\n"; 1693 Ht::stash_script('edit_paper_ui.prepare_pstagindex.call($$("' . $id . 'form"))'); 1694 } 1695 1696 private function papstripVote($tag, $allotment) { 1697 $id = "vote_" . html_id_encode($tag); 1698 if (($myval = $this->prow->tag_value($this->user->contactId . "~$tag")) === false) 1699 $myval = ""; 1700 $totmark = $this->papstrip_tag_float($tag, "total", "vote"); 1701 1702 $this->papstrip_tag_entry($id, "foldc fold2c"); 1703 echo Ht::form("", ["id" => "{$id}form", "data-pid" => $this->prow->paperId]); 1704 if (isset($this->qreq->forceShow)) 1705 echo Ht::hidden("forceShow", $this->qreq->forceShow); 1706 echo $this->papt($id, $this->papstrip_tag_entry_title("#$tag votes", "~$tag", $myval), 1707 array("type" => "ps", "fold" => $id, "float" => $totmark)), 1708 '<div class="psv"><div class="fx">', 1709 Ht::entry("tagindex", $myval, 1710 array("size" => 4, "class" => "is-tag-index want-focus", 1711 "data-tag-base" => "~$tag")), 1712 " of $allotment", 1713 ' <span class="barsep">·</span> ', 1714 '<a href="', hoturl("search", "q=" . urlencode("editsort:-#~$tag")), '">Edit all</a>', 1715 "</div></div></form></div>\n"; 1716 Ht::stash_script('edit_paper_ui.prepare_pstagindex.call($$("' . $id . 'form"))'); 1717 } 1718 1719 private function papstripApproval($tag) { 1720 $id = "approval_" . html_id_encode($tag); 1721 if (($myval = $this->prow->tag_value($this->user->contactId . "~$tag")) === false) 1722 $myval = ""; 1723 $totmark = $this->papstrip_tag_float($tag, "total", "approval"); 1724 1725 $this->papstrip_tag_entry(null, null); 1726 echo Ht::form("", ["id" => "{$id}form", "data-pid" => $this->prow->paperId]); 1727 if (isset($this->qreq->forceShow)) 1728 echo Ht::hidden("forceShow", $this->qreq->forceShow); 1729 echo $this->papt($id, 1730 Ht::checkbox("tagindex", "0", $myval !== "", 1731 array("class" => "is-tag-index want-focus", 1732 "data-tag-base" => "~$tag", 1733 "style" => "padding-left:0;margin-left:0;margin-top:0")) 1734 . " " . Ht::label("#$tag vote"), 1735 array("type" => "ps", "float" => $totmark)), 1736 "</form></div>\n\n"; 1737 Ht::stash_script('edit_paper_ui.prepare_pstagindex.call($$("' . $id . 'form"))'); 1738 } 1739 1740 private function papstripWatch() { 1741 $prow = $this->prow; 1742 $conflictType = $prow->conflict_type($this->user); 1743 if (!($prow->timeSubmitted > 0 1744 && ($conflictType >= CONFLICT_AUTHOR 1745 || $conflictType <= 0 1746 || $this->user->is_admin_force()) 1747 && $this->user->contactId > 0)) 1748 return; 1749 // watch note 1750 $watch = $this->conf->fetch_ivalue("select watch from PaperWatch where paperId=? and contactId=?", $prow->paperId, $this->user->contactId); 1751 1752 $this->_papstripBegin(); 1753 1754 echo '<form class="submit-ui"><div>', 1755 $this->papt("watch", 1756 Ht::checkbox("follow", 1, 1757 $this->user->following_reviews($prow, $watch), 1758 ["class" => "js-follow-change", 1759 "style" => "padding-left:0;margin-left:0"]) 1760 . " " . Ht::label("Email notification"), 1761 array("type" => "ps")), 1762 '<div class="pshint">Select to receive email on updates to reviews and comments.</div>', 1763 "</div></form></div>\n\n"; 1764 Ht::stash_script('$(".js-follow-change").on("change", handle_ui)'); 1765 } 1766 1767 1768 // Functions for editing 1769 1770 function deadlineSettingIs($dname) { 1771 $deadline = $this->conf->printableTimeSetting($dname, "span"); 1772 if ($deadline === "N/A") 1773 return ""; 1774 else if (time() < $this->conf->setting($dname)) 1775 return " The deadline is $deadline."; 1776 else 1777 return " The deadline was $deadline."; 1778 } 1779 1780 private function _deadline_override_message() { 1781 if ($this->admin) 1782 return " As an administrator, you can make changes anyway."; 1783 else 1784 return $this->_forceShow_message(); 1785 } 1786 private function _forceShow_message() { 1787 if (!$this->admin && $this->allow_admin) 1788 return " " . Ht::link("(Override your conflict)", SelfHref::make($this->qreq, ["forceShow" => 1]), ["class" => "nw"]); 1789 else 1790 return ""; 1791 } 1792 1793 private function _edit_message_new_paper() { 1794 global $Now; 1795 $startDeadline = $this->deadlineSettingIs("sub_reg"); 1796 $msg = ""; 1797 if (!$this->conf->timeStartPaper()) { 1798 $sub_open = $this->conf->setting("sub_open"); 1799 if ($sub_open <= 0 || $sub_open > $Now) 1800 $msg = "The conference site is not open for submissions." . $this->_deadline_override_message(); 1801 else 1802 $msg = 'The <a href="' . hoturl("deadlines") . '">deadline</a> for registering submissions has passed.' . $startDeadline . $this->_deadline_override_message(); 1803 if (!$this->admin) { 1804 $this->quit = true; 1805 return '<div class="merror">' . $msg . '</div>'; 1806 } 1807 $msg = Ht::xmsg("info", $msg); 1808 } 1809 $t1 = $this->conf->_("Enter information about your paper."); 1810 if ($startDeadline && !$this->conf->setting("sub_freeze")) 1811 $t2 = "You can make changes until the deadline, but thereafter incomplete submissions will not be considered."; 1812 else if (!$this->conf->opt("noPapers")) 1813 $t2 = "You don’t have to upload the PDF right away, but incomplete submissions will not be considered."; 1814 else 1815 $t2 = "Incomplete submissions will not be considered."; 1816 $t2 = $this->conf->_($t2); 1817 $msg .= Ht::xmsg("info", space_join($t1, $t2, $startDeadline)); 1818 if (($v = $this->conf->message_html("submit"))) 1819 $msg .= Ht::xmsg("info", $v); 1820 return $msg; 1821 } 1822 1823 private function _edit_message_for_author(PaperInfo $prow) { 1824 $can_view_decision = $prow->outcome != 0 && $this->user->can_view_decision($prow); 1825 if ($can_view_decision && $prow->outcome < 0) { 1826 return Ht::xmsg("warning", "The submission was not accepted." . $this->_forceShow_message()); 1827 } else if ($prow->timeWithdrawn > 0) { 1828 if ($this->user->can_revive_paper($prow)) 1829 return Ht::xmsg("warning", "The submission has been withdrawn, but you can still revive it." . $this->deadlineSettingIs("sub_update")); 1830 else 1831 return Ht::xmsg("warning", "The submission has been withdrawn." . $this->_forceShow_message()); 1832 } else if ($prow->timeSubmitted <= 0) { 1833 $whyNot = $this->user->perm_update_paper($prow); 1834 if (!$whyNot) { 1835 if ($this->conf->setting("sub_freeze")) 1836 $t = "This submission must be completed before it can be reviewed."; 1837 else if ($prow->paperStorageId <= 1 && !$this->conf->opt("noPapers")) 1838 $t = "This submission is not ready for review and will not be considered as is, but you can still make changes."; 1839 else 1840 $t = "This submission is not ready for review and will not be considered as is, but you can still mark it ready for review and make other changes if appropriate."; 1841 return Ht::xmsg("warning", $t . $this->deadlineSettingIs("sub_update")); 1842 } else if (isset($whyNot["updateSubmitted"]) 1843 && $this->user->can_finalize_paper($prow)) { 1844 return Ht::xmsg("warning", 'The submission is not ready for review. Although you cannot make any further changes, the current version can be still be submitted for review.' . $this->deadlineSettingIs("sub_sub") . $this->_deadline_override_message()); 1845 } else if (isset($whyNot["deadline"])) { 1846 if ($this->conf->deadlinesBetween("", "sub_sub", "sub_grace")) { 1847 return Ht::xmsg("warning", 'The site is not open for updates at the moment.' . $this->_deadline_override_message()); 1848 } else { 1849 return Ht::xmsg("warning", 'The <a href="' . hoturl("deadlines") . '">submission deadline</a> has passed and the submission will not be reviewed.' . $this->deadlineSettingIs("sub_sub") . $this->_deadline_override_message()); 1850 } 1851 } else { 1852 return Ht::xmsg("warning", 'The submission is not ready for review and can’t be changed further. It will not be reviewed.' . $this->_deadline_override_message()); 1853 } 1854 } else if ($this->user->can_update_paper($prow)) { 1855 if ($this->mode === "edit") 1856 return Ht::xmsg("confirm", 'The submission is ready and will be considered for review. You do not need to take any further action. However, you can still make changes if you wish.' . $this->deadlineSettingIs("sub_update")); 1857 } else if ($this->conf->collectFinalPapers() 1858 && $prow->outcome > 0 1859 && $can_view_decision) { 1860 if ($this->user->can_submit_final_paper($prow)) { 1861 if (($t = $this->conf->message_html("finalsubmit", array("deadline" => $this->deadlineSettingIs("final_soft"))))) 1862 return Ht::xmsg("info", $t); 1863 } else if ($this->mode === "edit") { 1864 return Ht::xmsg("warning", "The deadline for updating final versions has passed. You can still change contact information." . $this->_deadline_override_message()); 1865 } 1866 } else if ($this->mode === "edit") { 1867 $t = ""; 1868 if ($this->user->can_withdraw_paper($prow)) 1869 $t = " or withdraw it from consideration"; 1870 return Ht::xmsg("info", "The submission is under review and can’t be changed, but you can change its contacts$t." . $this->_deadline_override_message()); 1871 } 1872 return ""; 1873 } 1874 1875 private function editMessage() { 1876 if (!($prow = $this->prow)) 1877 return $this->_edit_message_new_paper(); 1878 1879 $m = ""; 1880 $has_author = $prow->has_author($this->user); 1881 $can_view_decision = $prow->outcome != 0 && $this->user->can_view_decision($prow); 1882 if ($has_author) 1883 $m .= $this->_edit_message_for_author($prow); 1884 else if ($this->conf->collectFinalPapers() 1885 && $prow->outcome > 0 && !$prow->can_author_view_decision()) 1886 $m .= Ht::xmsg("info", "The submission has been accepted, but its authors can’t see that yet. Once decisions are visible, the system will allow accepted authors to upload final versions."); 1887 else 1888 $m .= Ht::xmsg("info", "You aren’t a contact for this submission, but as an administrator you can still make changes."); 1889 if ($this->user->call_with_overrides(Contact::OVERRIDE_TIME, "can_update_paper", $prow) 1890 && ($v = $this->conf->message_html("submit"))) 1891 $m .= Ht::xmsg("info", $v); 1892 if ($this->edit_status && $this->edit_status->has_problem() 1893 && ($this->edit_status->has_problem_at("contacts") || $this->editable)) 1894 $m .= Ht::xmsg("warning", "There may be problems with this submission. Please scroll through the form and fix the problems if appropriate."); 1895 return $m; 1896 } 1897 1898 function _collectActionButtons() { 1899 $pid = $this->prow ? $this->prow->paperId : "new"; 1900 1901 // Withdrawn papers can be revived 1902 if ($this->_prow->timeWithdrawn > 0) { 1903 $revivable = $this->conf->timeFinalizePaper($this->_prow); 1904 if ($revivable) 1905 $b = Ht::submit("revive", "Revive submission", ["class" => "btn btn-primary"]); 1906 else { 1907 $b = "The <a href='" . hoturl("deadlines") . "'>deadline</a> for reviving withdrawn submissions has passed. Are you sure you want to override it?"; 1908 if ($this->admin) 1909 $b = array(Ht::button("Revive submission", ["class" => "btn ui js-override-deadlines", "data-override-text" => $b, "data-override-submit" => "revive"]), "(admin only)"); 1910 } 1911 return array($b); 1912 } 1913 1914 $buttons = array(); 1915 1916 if ($this->mode === "edit") { 1917 // check whether we can save 1918 $old_overrides = $this->user->set_overrides(0); 1919 if ($this->canUploadFinal) { 1920 $updater = "submitfinal"; 1921 $whyNot = $this->user->perm_submit_final_paper($this->prow); 1922 } else if ($this->prow) { 1923 $updater = "update"; 1924 $whyNot = $this->user->perm_update_paper($this->prow); 1925 } else { 1926 $updater = "update"; 1927 $whyNot = $this->user->perm_start_paper(); 1928 } 1929 $this->user->set_overrides($old_overrides); 1930 // produce button 1931 $save_name = $this->is_ready() ? "Save and resubmit" : "Save draft"; 1932 if (!$whyNot) 1933 $buttons[] = array(Ht::submit($updater, $save_name, ["class" => "btn btn-primary btn-savepaper"]), ""); 1934 else if ($this->admin) { 1935 $revWhyNot = filter_whynot($whyNot, ["deadline", "rejected"]); 1936 $x = whyNotText($revWhyNot) . " Are you sure you want to override the deadline?"; 1937 $buttons[] = array(Ht::button($save_name, ["class" => "btn btn-primary btn-savepaper ui js-override-deadlines", "data-override-text" => $x, "data-override-submit" => $updater]), "(admin only)"); 1938 } else if (isset($whyNot["updateSubmitted"]) 1939 && $this->user->can_finalize_paper($this->_prow)) { 1940 $buttons[] = array(Ht::submit("update", $save_name, ["class" => "btn btn-savepaper"])); 1941 } else if ($this->prow) { 1942 $buttons[] = array(Ht::submit("updatecontacts", "Save contacts", ["class" => "btn"]), ""); 1943 } 1944 if (!empty($buttons)) { 1945 $buttons[] = [Ht::submit("cancel", "Cancel", ["class" => "btn"])]; 1946 $buttons[] = ""; 1947 } 1948 } 1949 1950 // withdraw button 1951 if (!$this->prow 1952 || !$this->user->call_with_overrides(Contact::OVERRIDE_TIME, "can_withdraw_paper", $this->prow)) 1953 $b = null; 1954 else if ($this->prow->timeSubmitted <= 0) 1955 $b = Ht::submit("withdraw", "Withdraw"); 1956 else { 1957 $args = ["class" => "btn ui js-withdraw"]; 1958 if ($this->user->can_withdraw_paper($this->prow)) 1959 $args["data-withdrawable"] = "true"; 1960 if (($this->admin && !$this->prow->has_author($this->user)) 1961 || $this->conf->timeFinalizePaper($this->prow)) 1962 $args["data-revivable"] = "true"; 1963 $b = Ht::button("Withdraw", $args); 1964 } 1965 if ($b) { 1966 if (!$this->user->can_withdraw_paper($this->prow)) 1967 $b = array($b, "(admin only)"); 1968 $buttons[] = $b; 1969 } 1970 1971 return $buttons; 1972 } 1973 1974 function echoActions($top) { 1975 if ($this->admin && !$top) { 1976 $v = (string) $this->qreq->emailNote; 1977 echo '<div class="checki"><span class="checkc">', Ht::checkbox("doemail", 1, true, ["class" => "ignore-diff"]), " </span>", 1978 Ht::label("Email authors, including:"), " ", 1979 Ht::entry("emailNote", $v, ["id" => "emailNote", "size" => 30, "placeholder" => "Optional explanation", "class" => "ignore-diff js-autosubmit"]), 1980 "</div>\n"; 1981 } 1982 1983 $buttons = $this->_collectActionButtons(); 1984 1985 if ($this->admin && $this->prow) 1986 $buttons[] = array(Ht::button("Delete", ["class" => "btn ui js-delete-paper"]), "(admin only)"); 1987 1988 echo Ht::actions($buttons, array("class" => "aab aabr aabig")); 1989 } 1990 1991 1992 // Functions for overall paper table viewing 1993 1994 function _papstrip() { 1995 $prow = $this->prow; 1996 if (($prow->managerContactId || ($this->user->privChair && $this->mode === "assign")) 1997 && $this->user->can_view_manager($prow)) 1998 $this->papstripManager($this->user->privChair); 1999 $this->papstripTags(); 2000 $this->npapstrip_tag_entry = 0; 2001 foreach ($this->conf->tags() as $ltag => $t) 2002 if ($this->user->can_change_tag($prow, "~$ltag", null, 0)) { 2003 if ($t->approval) 2004 $this->papstripApproval($t->tag); 2005 else if ($t->vote) 2006 $this->papstripVote($t->tag, $t->vote); 2007 else if ($t->rank) 2008 $this->papstripRank($t->tag); 2009 } 2010 if ($this->npapstrip_tag_entry) 2011 echo "</div>"; 2012 $this->papstripWatch(); 2013 if ($this->user->can_view_conflicts($prow) && !$this->editable) 2014 $this->papstripPCConflicts(); 2015 if ($this->user->allow_view_authors($prow) && !$this->editable) 2016 $this->papstripCollaborators(); 2017 2018 $foldShepherd = $this->user->can_set_decision($prow) && $prow->outcome <= 0 2019 && $prow->shepherdContactId == 0 && $this->mode !== "assign"; 2020 if ($this->user->can_set_decision($prow)) 2021 $this->papstripOutcomeSelector(); 2022 if ($this->user->can_view_lead($prow)) 2023 $this->papstripLead($this->mode === "assign"); 2024 if ($this->user->can_view_shepherd($prow)) 2025 $this->papstripShepherd($this->mode === "assign", $foldShepherd); 2026 2027 if ($this->user->can_accept_review_assignment($prow) 2028 && $this->conf->timePCReviewPreferences() 2029 && ($this->user->roles & (Contact::ROLE_PC | Contact::ROLE_CHAIR))) 2030 $this->papstripReviewPreference(); 2031 echo Ht::unstash_script("$(\".need-pcselector\").each(populate_pcselector)"); 2032 } 2033 2034 function _paptabTabLink($text, $link, $image, $highlight) { 2035 return '<div class="' . ($highlight ? "papmodex" : "papmode") 2036 . '"><a href="' . $link . '" class="noul">' 2037 . Ht::img($image, "[$text]", "papmodeimg") 2038 . " <u" . ($highlight ? ' class="x"' : "") . ">" . $text 2039 . "</u></a></div>\n"; 2040 } 2041 2042 private function _paptabBeginKnown() { 2043 $prow = $this->prow; 2044 2045 // what actions are supported? 2046 $canEdit = $this->user->can_edit_paper($prow); 2047 $canReview = $this->user->can_review($prow, null); 2048 $canAssign = $this->admin; 2049 $canHome = ($canEdit || $canAssign || $this->mode === "contact"); 2050 2051 $t = ""; 2052 2053 // paper tabs 2054 if ($canEdit || $canReview || $canAssign || $canHome) { 2055 $t .= '<div class="submission_modes">'; 2056 2057 // home link 2058 $highlight = ($this->mode !== "assign" && $this->mode !== "edit" 2059 && $this->mode !== "contact" && $this->mode !== "re"); 2060 $a = ""; // ($this->mode === "edit" || $this->mode === "re" ? "&m=p" : ""); 2061 $t .= $this->_paptabTabLink("Main", hoturl("paper", "p=$prow->paperId$a"), "view48.png", $highlight); 2062 2063 if ($canEdit) 2064 $t .= $this->_paptabTabLink("Edit", hoturl("paper", "p=$prow->paperId&m=edit"), "edit48.png", $this->mode === "edit"); 2065 2066 if ($canReview) 2067 $t .= $this->_paptabTabLink("Review", hoturl("review", "p=$prow->paperId&m=re"), "review48.png", $this->mode === "re" && (!$this->editrrow || $this->editrrow->contactId == $this->user->contactId)); 2068 2069 if ($canAssign) 2070 $t .= $this->_paptabTabLink("Assign", hoturl("assign", "p=$prow->paperId"), "assign48.png", $this->mode === "assign"); 2071 2072 $t .= "</div>"; 2073 } 2074 2075 return $t; 2076 } 2077 2078 static private function _echo_clickthrough($ctype) { 2079 global $Conf, $Now; 2080 $data = $Conf->message_html("clickthrough_$ctype"); 2081 echo Ht::form(["class" => "ui"]), '<div>', $data; 2082 $buttons = [Ht::submit("Agree", ["class" => "btn btnbig btn-highlight ui js-clickthrough"])]; 2083 echo Ht::hidden("clickthrough_type", $ctype), 2084 Ht::hidden("clickthrough_id", sha1($data)), 2085 Ht::hidden("clickthrough_time", $Now), 2086 Ht::actions($buttons, ["class" => "aab aabig aabr"]), "</div></form>"; 2087 } 2088 2089 static function echo_review_clickthrough() { 2090 echo '<div class="revcard js-clickthrough-terms"><div class="revcard_head"><h3>Reviewing terms</h3></div><div class="revcard_body">', Ht::xmsg("error", "You must agree to these terms before you can save reviews."); 2091 self::_echo_clickthrough("review"); 2092 echo "</form></div></div>"; 2093 } 2094 2095 private function _echo_editable_form() { 2096 $form_js = ["id" => "paperform"]; 2097 if ($this->prow && $this->prow->timeSubmitted > 0) 2098 $form_js["data-submitted"] = $this->prow->timeSubmitted; 2099 if ($this->prow && !$this->editable) 2100 $form_js["data-contacts-only"] = 1; 2101 if ($this->useRequest) 2102 $form_js["class"] = "alert"; 2103 echo Ht::form(hoturl_post("paper", "p=" . ($this->prow ? $this->prow->paperId : "new") . "&m=edit"), $form_js); 2104 Ht::stash_script('$("#paperform").on("change", ".js-check-submittable", handle_ui)'); 2105 if ($this->prow 2106 && $this->prow->paperStorageId > 1 2107 && $this->prow->timeSubmitted > 0 2108 && !$this->conf->setting("sub_freeze")) 2109 Ht::stash_script('$("#paperform").on("submit", edit_paper_ui)'); 2110 Ht::stash_script('$(function(){$("#paperform input[name=paperUpload]").trigger("change")})'); 2111 } 2112 2113 private function make_echo_editable_option($o) { 2114 return (object) [ 2115 "position" => $o->form_position(), 2116 "option" => $o, 2117 "callback" => function () use ($o) { 2118 if ($o->edit_condition() 2119 && !$o->compile_edit_condition($this->_prow)) 2120 return; 2121 $ov = $this->_prow->option($o->id); 2122 $ov = $ov ? : new PaperOptionValue($this->prow, $o); 2123 $o->echo_editable_html($ov, $this->useRequest ? $this->qreq[$o->formid] : null, $this); 2124 } 2125 ]; 2126 } 2127 2128 private function _echo_editable_body() { 2129 global $ConfSitePATH; 2130 $this->_echo_editable_form(); 2131 echo '<div>'; 2132 2133 if (($m = $this->editMessage())) 2134 echo $m, '<div class="g"></div>'; 2135 if ($this->quit) { 2136 echo "</div></form>"; 2137 return; 2138 } 2139 2140 $this->echoActions(true); 2141 2142 $ofields = []; 2143 foreach ($this->conf->paper_opts->option_list_type(!$this->canUploadFinal) as $opt) 2144 if ((!$this->prow || get($this->view_options, $opt->id)) 2145 && !$opt->internal) 2146 $ofields[] = $this->make_echo_editable_option($opt); 2147 $gxt = new GroupedExtensions($this->user, ["etc/submissioneditgroups.json"], $this->conf->opt("submissionEditGroups"), $ofields); 2148 $this->edit_fields = array_values($gxt->groups()); 2149 for ($this->edit_fields_position = 0; 2150 $this->edit_fields_position < count($this->edit_fields); 2151 ++$this->edit_fields_position) { 2152 $uf = $this->edit_fields[$this->edit_fields_position]; 2153 $cb = get($uf, "callback"); 2154 if ($cb instanceof Closure) 2155 call_user_func($cb, $uf); 2156 else if (is_string($cb) && str_starts_with($cb, "*")) 2157 call_user_func([$this, substr($cb, 1)], $uf); 2158 else if ($cb) 2159 call_user_func($cb, $this, $uf); 2160 } 2161 2162 // Submit button 2163 $this->echo_editable_complete(); 2164 $this->echoActions(false); 2165 2166 echo "</div></form>"; 2167 } 2168 2169 function paptabBegin() { 2170 $prow = $this->prow; 2171 2172 if ($prow) 2173 $this->_papstrip(); 2174 if ($this->npapstrip) 2175 echo "</div></div></div></div>\n<div class=\"papcard\">"; 2176 else 2177 echo '<div class="pedcard">'; 2178 if ($this->editable) 2179 echo '<div class="pedcard_body">'; 2180 else 2181 echo '<div class="papcard_body">'; 2182 2183 $this->echoDivEnter(); 2184 if ($this->editable) { 2185 if (!$this->user->can_clickthrough("submit")) { 2186 echo '<div class="js-clickthrough-container">', 2187 '<div class="js-clickthrough-terms">', 2188 '<h3>Submission terms</h3>', 2189 Ht::xmsg("error", "You must agree to these terms to register a submission."); 2190 self::_echo_clickthrough("submit"); 2191 echo '</div><div class="js-clickthrough-body hidden">'; 2192 $this->_echo_editable_body(); 2193 echo '</div></div>'; 2194 } else 2195 $this->_echo_editable_body(); 2196 } else { 2197 if ($this->mode === "edit" && ($m = $this->editMessage())) 2198 echo $m, "<div class='g'></div>\n"; 2199 $status_info = $this->user->paper_status_info($this->prow); 2200 echo '<p class="xd"><span class="pstat ', $status_info[0], '">', 2201 htmlspecialchars($status_info[1]), "</span></p>"; 2202 $this->paptabDownload(); 2203 echo '<div class="paperinfo"><div class="paperinfo-row">'; 2204 $has_abstract = $this->paptabAbstract(); 2205 echo '<div class="paperinfo-c', ($has_abstract ? "r" : "b"), '">'; 2206 $this->paptabAuthors(!$this->editable && $this->mode === "edit" 2207 && $prow->timeSubmitted > 0); 2208 $this->paptabTopicsOptions(); 2209 echo '</div></div></div>'; 2210 } 2211 $this->echoDivExit(); 2212 2213 if (!$this->editable && $this->mode === "edit") { 2214 $this->_echo_editable_form(); 2215 $this->echo_editable_contact_author(); 2216 $this->echoActions(false); 2217 echo "</form>"; 2218 } else if (!$this->editable && $this->user->act_author_view($prow) 2219 && !$this->user->contactId) { 2220 echo '<hr class="papcard_sep" />', 2221 "To edit this submission, <a href=\"", hoturl("index"), "\">sign in using your email and password</a>."; 2222 } 2223 2224 Ht::stash_script("shortcut().add()"); 2225 if ($this->editable || $this->mode === "edit") 2226 Ht::stash_script('hiliter_children("#paperform", true)'); 2227 } 2228 2229 private function _paptabSepContaining($t) { 2230 if ($t !== "") 2231 echo '<hr class="papcard_sep" />', $t; 2232 } 2233 2234 function _paptabReviewLinks($rtable, $editrrow, $ifempty) { 2235 require_once("reviewtable.php"); 2236 2237 $t = ""; 2238 if ($rtable) 2239 $t .= reviewTable($this->prow, $this->all_rrows, $this->mycrows, 2240 $editrrow, $this->mode); 2241 $t .= reviewLinks($this->prow, $this->all_rrows, $this->mycrows, 2242 $editrrow, $this->mode, $this->allreviewslink); 2243 if (($empty = ($t === ""))) 2244 $t = $ifempty; 2245 if ($t) 2246 echo '<hr class="papcard_sep" />'; 2247 echo $t, "</div></div>\n"; 2248 return $empty; 2249 } 2250 2251 function _privilegeMessage() { 2252 $a = "<a href=\"" . SelfHref::make($this->qreq, ["forceShow" => 0]) . "\">"; 2253 return $a . Ht::img("override24.png", "[Override]", "dlimg") 2254 . "</a> You have used administrator privileges to view and edit reviews for this submission. (" . $a . "Unprivileged view</a>)"; 2255 } 2256 2257 private function include_comments() { 2258 return !$this->allreviewslink 2259 && (!empty($this->mycrows) 2260 || $this->user->can_comment($this->prow, null) 2261 || $this->conf->any_response_open); 2262 } 2263 2264 function paptabEndWithReviewsAndComments() { 2265 $prow = $this->prow; 2266 2267 if ($this->user->is_admin_force() 2268 && !$this->user->can_view_review($prow, null, false)) 2269 $this->_paptabSepContaining($this->_privilegeMessage()); 2270 else if ($this->user->contactId == $prow->managerContactId 2271 && !$this->user->privChair 2272 && $this->user->contactId > 0) 2273 $this->_paptabSepContaining("You are this submission’s administrator."); 2274 2275 $empty = $this->_paptabReviewLinks(true, null, "<div class='hint'>There are no reviews or comments for you to view.</div>"); 2276 if ($empty) 2277 return; 2278 2279 // text format link 2280 $viewable = array(); 2281 foreach ($this->viewable_rrows as $rr) 2282 if ($rr->reviewModified > 1) { 2283 $viewable[] = "reviews"; 2284 break; 2285 } 2286 foreach ($this->crows as $cr) 2287 if ($this->user->can_view_comment($prow, $cr)) { 2288 $viewable[] = "comments"; 2289 break; 2290 } 2291 if (count($viewable)) 2292 echo '<div class="notecard"><div class="notecard_body">', 2293 "<a href='", hoturl("review", "p=$prow->paperId&m=r&text=1"), "' class='xx'>", 2294 Ht::img("txt24.png", "[Text]", "dlimg"), 2295 " <u>", ucfirst(join(" and ", $viewable)), 2296 " in plain text</u></a></div></div>\n"; 2297 2298 $this->render_rc($this->reviews_and_comments()); 2299 } 2300 2301 function reviews_and_comments() { 2302 $a = []; 2303 foreach ($this->viewable_rrows as $rrow) 2304 if ($rrow->reviewSubmitted || $rrow->reviewModified > 1) 2305 $a[] = $rrow; 2306 if ($this->include_comments()) 2307 $a = array_merge($a, $this->mycrows ? : []); 2308 usort($a, "PaperInfo::review_or_comment_compare"); 2309 return $a; 2310 } 2311 2312 private function has_response($respround) { 2313 foreach ($this->mycrows as $cr) 2314 if (($cr->commentType & COMMENTTYPE_RESPONSE) 2315 && $cr->commentRound == $respround) 2316 return true; 2317 return false; 2318 } 2319 2320 private function render_rc($rcs) { 2321 $s = ""; 2322 $ncmt = 0; 2323 $rf = $this->conf->review_form(); 2324 foreach ($rcs as $rc) 2325 if (isset($rc->reviewId)) { 2326 $rcj = $rf->unparse_review_json($this->prow, $rc, $this->user); 2327 $s .= "review_form.add_review(" . json_encode_browser($rcj) . ");\n"; 2328 } else { 2329 ++$ncmt; 2330 $rcj = $rc->unparse_json($this->user); 2331 $s .= "papercomment.add(" . json_encode_browser($rcj) . ");\n"; 2332 } 2333 2334 if ($this->include_comments()) { 2335 $cs = []; 2336 if ($this->user->can_comment($this->prow, null)) { 2337 $ct = $this->prow->has_author($this->user) ? COMMENTTYPE_BYAUTHOR : 0; 2338 $cs[] = new CommentInfo((object) ["commentType" => $ct], $this->prow); 2339 } 2340 if ($this->admin || $this->prow->has_author($this->user)) { 2341 foreach ($this->conf->resp_rounds() as $rrd) 2342 if (!$this->has_response($rrd->number)) { 2343 $crow = CommentInfo::make_response_template($rrd->number, $this->prow); 2344 if ($this->user->can_respond($this->prow, $crow)) 2345 $cs[] = $crow; 2346 } 2347 } 2348 foreach ($cs as $c) { 2349 ++$ncmt; 2350 $s .= "papercomment.add(" . json_encode_browser($c->unparse_json($this->user)) . ");\n"; 2351 } 2352 } 2353 2354 if ($ncmt) 2355 CommentInfo::echo_script($this->prow); 2356 echo Ht::unstash_script($s); 2357 } 2358 2359 function paptabComments() { 2360 if ($this->include_comments()) 2361 $this->render_rc($this->mycrows); 2362 } 2363 2364 function paptabEndWithReviewMessage() { 2365 if ($this->editable) { 2366 echo "</div></div>\n"; 2367 return; 2368 } 2369 2370 $m = array(); 2371 if ($this->all_rrows 2372 && ($whyNot = $this->user->perm_view_review($this->prow, null))) 2373 $m[] = "You can’t see the reviews for this submission. " . whyNotText($whyNot); 2374 if ($this->prow 2375 && !$this->conf->time_review_open() 2376 && $this->prow->review_type($this->user)) { 2377 if ($this->rrow) 2378 $m[] = "You can’t edit your review because the site is not open for reviewing."; 2379 else 2380 $m[] = "You can’t begin your assigned review because the site is not open for reviewing."; 2381 } 2382 if (count($m)) 2383 $this->_paptabSepContaining(join("<br />", $m)); 2384 2385 $this->_paptabReviewLinks(false, null, ""); 2386 } 2387 2388 function paptabEndWithEditableReview() { 2389 $prow = $this->prow; 2390 $act_pc = $this->user->act_pc($prow); 2391 2392 // review messages 2393 $whyNot = $this->user->perm_view_review($prow, null, false); 2394 $msgs = array(); 2395 if (!$this->rrow && !$this->prow->review_type($this->user)) 2396 $msgs[] = "You haven’t been assigned to review this submission, but you can review it anyway."; 2397 if ($whyNot && $this->user->is_admin_force()) { 2398 $msgs[] = $this->_privilegeMessage(); 2399 } else if ($whyNot && isset($whyNot["reviewNotComplete"]) 2400 && ($this->user->isPC || $this->conf->setting("extrev_view"))) { 2401 $nother = 0; 2402 $myrrow = null; 2403 foreach ($this->all_rrows as $rrow) 2404 if ($this->user->is_my_review($rrow)) 2405 $myrrow = $rrow; 2406 else if ($rrow->reviewSubmitted) 2407 ++$nother; 2408 if ($nother > 0) { 2409 if ($myrrow && $myrrow->timeApprovalRequested > 0) 2410 $msgs[] = $this->conf->_("You’ll be able to see %d other reviews once yours is approved.", $nother); 2411 else 2412 $msgs[] = $this->conf->_("You’ll be able to see %d other reviews once you complete your own.", $nother); 2413 } 2414 } 2415 if (count($msgs) > 0) 2416 $this->_paptabSepContaining(join("<br />\n", $msgs)); 2417 2418 // links 2419 $this->_paptabReviewLinks(true, $this->editrrow, ""); 2420 2421 // review form, possibly with deadline warning 2422 $opt = array("edit" => $this->mode === "re"); 2423 2424 if ($this->editrrow 2425 && ($this->user->is_owned_review($this->editrrow) || $this->admin) 2426 && !$this->conf->time_review($this->editrrow, $act_pc, true)) { 2427 if ($this->admin) 2428 $override = " As an administrator, you can override this deadline."; 2429 else { 2430 $override = ""; 2431 if ($this->editrrow->reviewSubmitted) 2432 $opt["edit"] = false; 2433 } 2434 if ($this->conf->time_review_open()) 2435 $opt["editmessage"] = "The <a href='" . hoturl("deadlines") . "'>review deadline</a> has passed, so the review can no longer be changed.$override"; 2436 else 2437 $opt["editmessage"] = "The site is not open for reviewing, so the review cannot be changed.$override"; 2438 } else if (!$this->user->can_review($prow, $this->editrrow)) 2439 $opt["edit"] = false; 2440 2441 // maybe clickthrough 2442 $need_clickthrough = $opt["edit"] && !$this->user->can_clickthrough("review"); 2443 $rf = $this->conf->review_form(); 2444 if ($need_clickthrough) { 2445 echo '<div class="js-clickthrough-container">'; 2446 self::echo_review_clickthrough(); 2447 echo '<div class="js-clickthrough-body">'; 2448 } 2449 $rf->show($prow, $this->editrrow, $opt, $this->review_values); 2450 if ($need_clickthrough) 2451 echo '</div></div>'; 2452 } 2453 2454 2455 // Functions for loading papers 2456 2457 static private function _maybeSearchPaperId($qreq) { 2458 global $Conf, $Me, $Now; 2459 2460 // if a number, don't search 2461 if ((string) $qreq->paperId !== "") { 2462 if (ctype_digit($qreq->paperId) && $qreq->paperId[0] !== "0") 2463 return false; 2464 if (preg_match('/^\s*#?([1-9]\d*)\s*$/s', $qreq->paperId, $m)) { 2465 $qreq->paperId = $m[1]; 2466 return false; 2467 } 2468 } 2469 2470 // if a complex request, or a form upload, don't search 2471 foreach ($qreq->keys() as $k) 2472 if (!in_array($k, ["p", "paperId", "m", "mode", "forceShow", "go", "actas", "t"])) 2473 return false; 2474 2475 // if no paper ID set, find one 2476 if (!isset($qreq->paperId)) { 2477 $q = "select min(Paper.paperId) from Paper "; 2478 if ($Me->isPC) 2479 $q .= "where timeSubmitted>0"; 2480 else if ($Me->has_review()) 2481 $q .= "join PaperReview on (PaperReview.paperId=Paper.paperId and PaperReview.contactId=$Me->contactId)"; 2482 else 2483 $q .= "join ContactInfo on (ContactInfo.paperId=Paper.paperId and ContactInfo.contactId=$Me->contactId and ContactInfo.conflictType>=" . CONFLICT_AUTHOR . ")"; 2484 $result = $Conf->q_raw($q); 2485 if (($paperId = edb_row($result))) 2486 $qreq->paperId = $paperId[0]; 2487 return false; 2488 } 2489 2490 // if invalid contact, don't search 2491 if ($Me->is_empty()) 2492 return false; 2493 2494 // actually try to search 2495 if ($qreq->paperId === "(All)") 2496 $qreq->paperId = ""; 2497 $search = new PaperSearch($Me, ["q" => $qreq->paperId, "t" => $qreq->get("t", 0)]); 2498 $ps = $search->paper_ids(); 2499 if (count($ps) == 1) { 2500 $list = $search->session_list_object(); 2501 $qreq->paperId = $qreq->p = $list->ids[0]; 2502 // DISABLED: check if the paper is in the current list 2503 unset($qreq->ls); 2504 $list->set_cookie(); 2505 // ensure URI makes sense ("paper/2" not "paper/searchterm") 2506 SelfHref::redirect($qreq); 2507 return true; 2508 } else { 2509 $t = $qreq->t ? "&t=" . urlencode($qreq->t) : ""; 2510 go(hoturl("search", "q=" . urlencode($qreq->paperId) . $t)); 2511 exit; 2512 } 2513 } 2514 2515 static function clean_request(Qrequest $qreq) { 2516 if (!isset($qreq->paperId) && isset($qreq->p)) 2517 $qreq->paperId = $qreq->p; 2518 if (!isset($qreq->reviewId) && isset($qreq->r)) 2519 $qreq->reviewId = $qreq->r; 2520 if (!isset($qreq->commentId) && isset($qreq->c)) 2521 $qreq->commentId = $qreq->c; 2522 if (!isset($qreq->reviewId) 2523 && preg_match(',\A/\d+[A-Z]+\z,i', Navigation::path())) 2524 $qreq->reviewId = substr(Navigation::path(), 1); 2525 else if (!isset($qreq->paperId) && ($pc = Navigation::path_component(0))) 2526 $qreq->paperId = $pc; 2527 if (!isset($qreq->paperId) && isset($qreq->reviewId) 2528 && preg_match('/^(\d+)[A-Z]+$/i', $qreq->reviewId, $m)) 2529 $qreq->paperId = $m[1]; 2530 } 2531 2532 static function paperRow(Qrequest $qreq, &$whyNot) { 2533 global $Conf, $Me; 2534 2535 self::clean_request($qreq); 2536 if (isset($qreq->paperId) && $qreq->paperId === "new") 2537 return null; 2538 2539 $sel = array(); 2540 if (isset($qreq->paperId) 2541 || (!isset($qreq->reviewId) && !isset($qreq->commentId))) { 2542 self::_maybeSearchPaperId($qreq); 2543 $sel["paperId"] = $qreq->get("paperId", 1); 2544 } else if (isset($qreq->reviewId)) 2545 $sel["reviewId"] = $qreq->reviewId; 2546 else if (isset($qreq->commentId)) 2547 $sel["commentId"] = $qreq->commentId; 2548 2549 $sel["topics"] = $sel["options"] = true; 2550 if (($Me->isPC && $Conf->timePCReviewPreferences()) || $Me->privChair) 2551 $sel["reviewerPreference"] = true; 2552 if ($Me->isPC || $Conf->setting("tag_rank")) 2553 $sel["tags"] = true; 2554 2555 if (!($prow = $Conf->paperRow($sel, $Me, $whyNot))) 2556 return null; 2557 $rrow = null; 2558 if (isset($sel["reviewId"])) 2559 $rrow = $prow->review_of_id($sel["reviewId"]); 2560 if (($whyNot = $Me->perm_view_paper($prow)) 2561 || (!isset($qreq->paperId) 2562 && !$Me->can_view_review($prow, $rrow) 2563 && !$Me->privChair)) { 2564 // Don't allow querier to probe review/comment<->paper mapping 2565 if (!isset($qreq->paperId)) 2566 $whyNot = array("invalidId" => "paper"); 2567 return null; 2568 } 2569 if (!isset($qreq->paperId)) 2570 $qreq->paperId = $prow->paperId; 2571 return $prow; 2572 } 2573 2574 function resolveReview($want_review) { 2575 $this->prow->ensure_full_reviews(); 2576 $this->all_rrows = $this->prow->reviews_by_display(); 2577 2578 $this->viewable_rrows = array(); 2579 $round_mask = 0; 2580 $min_view_score = VIEWSCORE_MAX; 2581 foreach ($this->all_rrows as $rrow) 2582 if ($this->user->can_view_review($this->prow, $rrow)) { 2583 $this->viewable_rrows[] = $rrow; 2584 if ($rrow->reviewRound !== null) 2585 $round_mask |= 1 << (int) $rrow->reviewRound; 2586 $min_view_score = min($min_view_score, $this->user->view_score_bound($this->prow, $rrow)); 2587 } 2588 $rf = $this->conf->review_form(); 2589 Ht::stash_script("review_form.set_form(" . json_encode_browser($rf->unparse_json($round_mask, $min_view_score)) . ")"); 2590 2591 $rrid = strtoupper((string) $this->qreq->reviewId); 2592 while ($rrid !== "" && $rrid[0] === "0") 2593 $rrid = substr($rrid, 1); 2594 2595 $this->rrow = $myrrow = $approvable_rrow = null; 2596 foreach ($this->viewable_rrows as $rrow) { 2597 if ($rrid !== "" 2598 && (strcmp($rrow->reviewId, $rrid) == 0 2599 || ($rrow->reviewOrdinal 2600 && strcmp($rrow->paperId . unparseReviewOrdinal($rrow->reviewOrdinal), $rrid) == 0))) 2601 $this->rrow = $rrow; 2602 if ($rrow->contactId == $this->user->contactId 2603 || (!$myrrow && $this->user->is_my_review($rrow))) 2604 $myrrow = $rrow; 2605 if ($rrow->requestedBy == $this->user->contactId 2606 && !$rrow->reviewSubmitted 2607 && $rrow->timeApprovalRequested) 2608 $approvable_rrow = $rrow; 2609 } 2610 2611 if ($this->rrow) 2612 $this->editrrow = $this->rrow; 2613 else if (!$approvable_rrow || ($myrrow && $myrrow->reviewModified && !$this->prefer_approvable)) 2614 $this->editrrow = $myrrow; 2615 else 2616 $this->editrrow = $approvable_rrow; 2617 2618 if ($want_review && $this->user->can_review($this->prow, $this->editrrow, false)) 2619 $this->mode = "re"; 2620 } 2621 2622 function resolveComments() { 2623 $this->crows = $this->mycrows = array(); 2624 if ($this->prow) { 2625 $this->crows = $this->prow->all_comments(); 2626 $this->mycrows = $this->prow->viewable_comments($this->user); 2627 } 2628 } 2629 2630 function all_reviews() { 2631 return $this->all_rrows; 2632 } 2633 2634 function viewable_comments() { 2635 return $this->mycrows; 2636 } 2637 2638 function fixReviewMode() { 2639 $prow = $this->prow; 2640 if ($this->mode === "re" && $this->rrow 2641 && !$this->user->can_review($prow, $this->rrow, false) 2642 && ($this->rrow->contactId != $this->user->contactId 2643 || $this->rrow->reviewSubmitted)) 2644 $this->mode = "p"; 2645 if ($this->mode === "p" && $this->rrow 2646 && !$this->user->can_view_review($prow, $this->rrow)) 2647 $this->rrow = $this->editrrow = null; 2648 if ($this->mode === "p" && !$this->rrow && !$this->editrrow 2649 && $this->user->can_review($prow, null, false)) { 2650 $viewable_rrow = $my_rrow = null; 2651 foreach ($this->all_rrows as $rrow) { 2652 if ($this->user->can_view_review($prow, $rrow)) 2653 $viewable_rrow = $rrow; 2654 if ($rrow->contactId == $this->user->contactId 2655 || (!$my_rrow && $this->user->is_my_review($rrow))) 2656 $my_rrow = $rrow; 2657 } 2658 if (!$viewable_rrow) { 2659 $this->mode = "re"; 2660 $this->editrrow = $my_rrow; 2661 } 2662 } 2663 if ($this->mode === "p" && $prow && empty($this->viewable_rrows) 2664 && empty($this->mycrows) 2665 && $prow->has_author($this->user) 2666 && !$this->allow_admin 2667 && ($this->conf->timeFinalizePaper($prow) || $prow->timeSubmitted <= 0)) 2668 $this->mode = "edit"; 2669 } 2670} 2671