1<?php 2// paperstatus.php -- HotCRP helper for reading/storing papers as JSON 3// Copyright (c) 2008-2018 Eddie Kohler; see LICENSE. 4 5class PaperStatus extends MessageSet { 6 public $conf; 7 public $user; 8 private $uploaded_documents; 9 private $no_email = false; 10 private $export_ids = false; 11 private $hide_docids = false; 12 private $export_content = false; 13 private $disable_users = false; 14 private $allow_any_content_file = false; 15 private $content_file_prefix = false; 16 private $add_topics = false; 17 public $prow; 18 public $paperId; 19 private $_on_document_export = []; 20 private $_on_document_import = []; 21 22 public $diffs; 23 private $_paper_upd; 24 private $_topic_ins; 25 private $_option_delid; 26 private $_option_ins; 27 private $_new_conflicts; 28 private $_conflict_ins; 29 private $_paper_submitted; 30 private $_document_change; 31 32 function __construct(Conf $conf, Contact $user = null, $options = array()) { 33 $this->conf = $conf; 34 $this->user = $user; 35 foreach (array("no_email", "export_ids", "hide_docids", 36 "export_content", "disable_users", 37 "allow_any_content_file", "content_file_prefix", 38 "add_topics") as $k) 39 if (array_key_exists($k, $options)) 40 $this->$k = $options[$k]; 41 $this->_on_document_import[] = [$this, "document_import_check_filename"]; 42 $this->clear(); 43 } 44 45 function clear() { 46 parent::clear(); 47 $this->uploaded_documents = []; 48 $this->prow = null; 49 $this->diffs = []; 50 $this->_paper_upd = []; 51 $this->_topic_ins = null; 52 $this->_option_delid = $this->_option_ins = []; 53 $this->_new_conflicts = $this->_conflict_ins = null; 54 $this->_paper_submitted = $this->_document_change = null; 55 } 56 57 function on_document_export($cb) { 58 // arguments: $document_json, DocumentInfo $doc, $dtype, PaperStatus $pstatus 59 $this->_on_document_export[] = $cb; 60 } 61 62 function on_document_import($cb) { 63 // arguments: $document_json, $prow 64 $this->_on_document_import[] = $cb; 65 } 66 67 function user() { 68 return $this->user; 69 } 70 71 function paper_row() { 72 return $this->prow; 73 } 74 75 function _() { 76 return call_user_func_array([$this->conf->ims(), "x"], func_get_args()); 77 } 78 79 function document_to_json($dtype, $docid) { 80 if (!is_object($docid)) 81 $doc = $this->prow ? $this->prow->document($dtype, $docid) : null; 82 else { 83 $doc = $docid; 84 $docid = $doc->paperStorageId; 85 } 86 if (!$doc) 87 return null; 88 assert($doc instanceof DocumentInfo); 89 90 $d = (object) array(); 91 if ($docid && !$this->hide_docids) 92 $d->docid = $docid; 93 if ($doc->mimetype) 94 $d->mimetype = $doc->mimetype; 95 if ($doc->has_hash()) 96 $d->hash = $doc->text_hash(); 97 if ($doc->timestamp) 98 $d->timestamp = $doc->timestamp; 99 if ($doc->size) 100 $d->size = $doc->size; 101 if ($doc->filename) 102 $d->filename = $doc->filename; 103 $meta = null; 104 if (isset($doc->infoJson) && is_object($doc->infoJson)) 105 $meta = $doc->infoJson; 106 else if (isset($doc->infoJson) && is_string($doc->infoJson)) 107 $meta = json_decode($doc->infoJson); 108 if ($meta) 109 $d->metadata = $meta; 110 if ($this->export_content 111 && ($content = $doc->content()) !== false) 112 $d->content_base64 = base64_encode($content); 113 foreach ($this->_on_document_export as $cb) 114 if (call_user_func($cb, $d, $doc, $dtype, $this) === false) 115 return null; 116 if (!count(get_object_vars($d))) 117 $d = null; 118 return $d; 119 } 120 121 function paper_json($prow, $args = array()) { 122 if (is_int($prow)) 123 $prow = $this->conf->paperRow(["paperId" => $prow, "topics" => true, "options" => true], $this->user); 124 $original_user = $user = $this->user; 125 if (get($args, "forceShow")) 126 $user = null; 127 128 if (!$prow || ($user && !$user->can_view_paper($prow))) 129 return null; 130 $this->user = $user; 131 $original_no_msgs = $this->ignore_msgs; 132 $this->ignore_msgs = !get($args, "msgs"); 133 134 $this->prow = $prow; 135 $this->paperId = $prow->paperId; 136 137 $pj = (object) array(); 138 $pj->pid = (int) $prow->paperId; 139 $pj->title = $prow->title; 140 141 $submitted_status = "submitted"; 142 if ($prow->outcome != 0 143 && (!$user || $user->can_view_decision($prow))) { 144 $pj->decision = $this->conf->decision_name($prow->outcome); 145 if ($pj->decision === false) { 146 $pj->decision = (int) $prow->outcome; 147 $submitted_status = $pj->decision > 0 ? "accepted" : "rejected"; 148 } else 149 $submitted_status = $pj->decision; 150 } 151 152 if ($prow->timeWithdrawn > 0) { 153 $pj->status = "withdrawn"; 154 $pj->withdrawn = true; 155 $pj->withdrawn_at = (int) $prow->timeWithdrawn; 156 if (get($prow, "withdrawReason")) 157 $pj->withdraw_reason = $prow->withdrawReason; 158 } else if ($prow->timeSubmitted > 0) { 159 $pj->status = $submitted_status; 160 $pj->submitted = true; 161 } else { 162 $pj->status = "inprogress"; 163 $pj->draft = true; 164 } 165 if (($t = $prow->submitted_at())) 166 $pj->submitted_at = $t; 167 168 $can_view_authors = !$user 169 || $user->can_view_authors($prow); 170 if ($can_view_authors) { 171 $contacts = array(); 172 foreach ($prow->named_contacts() as $cflt) 173 $contacts[strtolower($cflt->email)] = $cflt; 174 175 $pj->authors = array(); 176 foreach ($prow->author_list() as $au) { 177 $aux = (object) array(); 178 if ($au->email) 179 $aux->email = $au->email; 180 if ($au->firstName) 181 $aux->first = $au->firstName; 182 if ($au->lastName) 183 $aux->last = $au->lastName; 184 if ($au->affiliation) 185 $aux->affiliation = $au->affiliation; 186 $lemail = strtolower((string) $au->email); 187 if ($lemail && ($cflt = get($contacts, $lemail)) 188 && $cflt->conflictType >= CONFLICT_AUTHOR) { 189 $aux->contact = true; 190 unset($contacts[$lemail]); 191 } 192 $pj->authors[] = $aux; 193 } 194 195 $other_contacts = array(); 196 foreach ($contacts as $cflt) 197 if ($cflt->conflictType >= CONFLICT_AUTHOR) { 198 $aux = (object) array("email" => $cflt->email); 199 if ($cflt->firstName) 200 $aux->first = $cflt->firstName; 201 if ($cflt->lastName) 202 $aux->last = $cflt->lastName; 203 if ($cflt->affiliation) 204 $aux->affiliation = $cflt->affiliation; 205 $other_contacts[] = $aux; 206 } 207 if (!empty($other_contacts)) 208 $pj->contacts = $other_contacts; 209 } 210 211 if ($this->conf->submission_blindness() == Conf::BLIND_OPTIONAL) 212 $pj->nonblind = !$prow->blind; 213 214 if ($prow->abstract !== "" || !$this->conf->opt("noAbstract")) 215 $pj->abstract = $prow->abstract; 216 217 $topics = array(); 218 foreach ($prow->named_topic_map() as $tid => $tname) 219 $topics[$this->export_ids ? $tid : $tname] = true; 220 if (!empty($topics)) 221 $pj->topics = (object) $topics; 222 223 if ($prow->paperStorageId > 1 224 && (!$user || $user->can_view_pdf($prow)) 225 && ($doc = $this->document_to_json(DTYPE_SUBMISSION, (int) $prow->paperStorageId))) 226 $pj->submission = $doc; 227 228 if ($prow->finalPaperStorageId > 1 229 && (!$user || $user->can_view_pdf($prow)) 230 && ($doc = $this->document_to_json(DTYPE_FINAL, (int) $prow->finalPaperStorageId))) 231 $pj->final = $doc; 232 if ($prow->timeFinalSubmitted > 0) { 233 $pj->final_submitted = true; 234 $pj->final_submitted_at = (int) $prow->timeFinalSubmitted; 235 } 236 237 $options = array(); 238 foreach ($this->conf->paper_opts->option_list() as $o) { 239 if ($user && !$user->can_view_paper_option($prow, $o)) 240 continue; 241 $ov = $prow->option($o->id) ? : new PaperOptionValue($prow, $o); 242 $oj = $o->unparse_json($ov, $this, $user); 243 if ($oj !== null) 244 $options[$this->export_ids ? $o->id : $o->json_key()] = $oj; 245 } 246 if (!empty($options)) 247 $pj->options = (object) $options; 248 249 if ($can_view_authors) { 250 $pcconflicts = array(); 251 foreach ($prow->pc_conflicts(true) as $id => $cflt) { 252 if (($ctname = get(Conflict::$type_names, $cflt->conflictType))) 253 $pcconflicts[$cflt->email] = $ctname; 254 } 255 if (!empty($pcconflicts)) 256 $pj->pc_conflicts = (object) $pcconflicts; 257 if ($prow->collaborators) 258 $pj->collaborators = $prow->collaborators; 259 } 260 261 // Now produce messages. 262 if (!$this->ignore_msgs 263 && $pj->title === "") 264 $this->error_at("title", $this->_("Each submission must have a title.")); 265 if (!$this->ignore_msgs 266 && (!isset($pj->abstract) || $pj->abstract === "") 267 && !$this->conf->opt("noAbstract")) 268 $this->error_at("abstract", $this->_("Each submission must have an abstract.")); 269 if (!$this->ignore_msgs 270 && $can_view_authors) { 271 $msg1 = $msg2 = false; 272 foreach ($prow->author_list() as $n => $au) { 273 if (strpos($au->email, "@") === false 274 && strpos($au->affiliation, "@") !== false) { 275 $msg1 = true; 276 $this->warning_at("author" . ($n + 1), null); 277 } else if ($au->firstName === "" && $au->lastName === "" 278 && $au->email === "" && $au->affiliation !== "") { 279 $msg2 = true; 280 $this->warning_at("author" . ($n + 1), null); 281 } 282 } 283 $max_authors = $this->conf->opt("maxAuthors"); 284 if (!$prow->author_list()) 285 $this->error_at("authors", $this->_("Each submission must have at least one author.", $max_authors)); 286 if ($max_authors > 0 && count($prow->author_list()) > $max_authors) 287 $this->error_at("authors", $this->_("Each submission can have at most %d authors.", $max_authors)); 288 if ($msg1) 289 $this->warning_at("authors", "You may have entered an email address in the wrong place. The first author field is for author name, the second for email address, and the third for affiliation."); 290 if ($msg2) 291 $this->warning_at("authors", "Please enter a name and optional email address for every author."); 292 } 293 if (!$this->ignore_msgs 294 && $can_view_authors 295 && $this->conf->setting("sub_collab") 296 && ($prow->outcome <= 0 || ($user && !$user->can_view_decision($prow)))) { 297 $field = $this->_($this->conf->setting("sub_pcconf") ? "Other conflicts" : "Potential conflicts"); 298 if (!$prow->collaborators) 299 $this->warning_at("collaborators", $this->_("Enter the authors’ external conflicts of interest in the %s field. If none of the authors have external conflicts, enter “None”.", $field)); 300 } 301 if (!$this->ignore_msgs 302 && $can_view_authors 303 && $this->conf->setting("sub_pcconf") 304 && ($prow->outcome <= 0 || ($user && !$user->can_view_decision($prow)))) { 305 $pcs = []; 306 foreach ($this->conf->full_pc_members() as $p) { 307 if (!$prow->has_conflict($p) 308 && $prow->potential_conflict($p)) 309 $pcs[] = Text::name_html($p); 310 } 311 if (!empty($pcs)) { 312 $this->warning_at("pcconf", $this->_("<p>You may have missed conflicts of interest for %s. These conflicts are highlighted below; hover for more information. Please verify that all conflicts are correctly marked.</p>", commajoin($pcs, "and")) 313 . $this->_('<p class="hint">This warning will not prevent submission.</p>')); 314 } 315 } 316 317 $this->ignore_msgs = $original_no_msgs; 318 $this->user = $original_user; 319 return $pj; 320 } 321 322 323 function error_at_option(PaperOption $o, $html) { 324 $this->error_at($o->field_key(), htmlspecialchars($o->name) . ": " . $html); 325 } 326 function warning_at_option(PaperOption $o, $html) { 327 $this->warning_at($o->field_key(), htmlspecialchars($o->name) . ": " . $html); 328 } 329 330 function format_error_at($key, $value) { 331 $this->error_at($key, "Format error [" . htmlspecialchars($key) . "]"); 332 error_log($this->conf->dbname . ": PaperStatus: format error $key " . gettype($value)); 333 } 334 335 336 function document_import_check_filename($docj, PaperOption $o, PaperStatus $pstatus) { 337 if (isset($docj->content_file) 338 && is_string($docj->content_file) 339 && !($docj instanceof DocumentInfo)) { 340 if (!$this->allow_any_content_file && preg_match(',\A/|(?:\A|/)\.\.(?:/|\z),', $docj->content_file)) { 341 $pstatus->error_at_option($o, "Bad content_file: only simple filenames allowed."); 342 return false; 343 } 344 if ((string) $this->content_file_prefix !== "") 345 $docj->content_file = $this->content_file_prefix . $docj->content_file; 346 } 347 } 348 349 function upload_document($docj, PaperOption $o) { 350 // $docj can be a DocumentInfo or a JSON. 351 // If it is a JSON, its format is set by document_to_json. 352 if (is_array($docj) && count($docj) === 1 && isset($docj[0])) 353 $docj = $docj[0]; 354 if (!is_object($docj)) { 355 $this->format_error_at($o->field_key(), $docj); 356 return null; 357 } else if (get($docj, "error") || get($docj, "error_html")) { 358 $this->error_at_option($o, get($docj, "error_html", "Upload error.")); 359 return null; 360 } 361 assert(!isset($docj->filter)); 362 363 // check on_document_import 364 foreach ($this->_on_document_import as $cb) 365 if (call_user_func($cb, $docj, $o, $this) === false) 366 return null; 367 368 // validate JSON 369 if ($docj instanceof DocumentInfo) 370 $doc = $docj; 371 else { 372 $doc = null; 373 if (!isset($docj->hash) && isset($docj->sha1)) 374 $dochash = (string) Filer::sha1_hash_as_text($docj->sha1); 375 else 376 $dochash = (string) get($docj, "hash"); 377 378 if ($this->prow 379 && ($docid = get($docj, "docid")) 380 && is_int($docid)) { 381 $result = $this->conf->qe("select * from PaperStorage where paperId=? and paperStorageId=? and documentType=?", $this->prow->paperId, $docid, $o->id); 382 $doc = DocumentInfo::fetch($result, $this->conf, $this->prow); 383 Dbl::free($result); 384 if (!$doc || ($dochash !== "" && !Filer::check_text_hash($doc->sha1, $dochash))) 385 $doc = null; 386 } 387 388 if (!$doc) { 389 $args = ["paperId" => $this->paperId, "sha1" => $dochash, "documentType" => $o->id]; 390 foreach (["timestamp", "mimetype", "content", "content_base64", 391 "content_file", "metadata", "filename"] as $k) 392 if (isset($docj->$k)) 393 $args[$k] = $docj->$k; 394 DocumentInfo::fix_mimetype($args); 395 $doc = new DocumentInfo($args, $this->conf, $this->prow); 396 } 397 } 398 399 // save 400 if ($doc->paperStorageId > 0 || $doc->save()) { 401 $this->uploaded_documents[] = $doc->paperStorageId; 402 return $doc; 403 } else { 404 error_log($doc->error_html); 405 $this->error_at_option($o, $doc->error_html); 406 return false; 407 } 408 } 409 410 private function normalize_string($pj, $k, $simplify) { 411 if (isset($pj->$k) && is_string($pj->$k)) { 412 $pj->$k = $simplify ? simplify_whitespace($pj->$k) : trim($pj->$k); 413 } else if (isset($pj->$k)) { 414 $this->format_error_at($k, $pj->$k); 415 unset($pj, $k); 416 } 417 } 418 419 private function normalize_author($pj, $au, &$au_by_lemail) { 420 $aux = Text::analyze_name($au); 421 $aux->first = simplify_whitespace($aux->firstName); 422 $aux->last = simplify_whitespace($aux->lastName); 423 $aux->email = simplify_whitespace($aux->email); 424 $aux->affiliation = simplify_whitespace($aux->affiliation); 425 // borrow from old author information 426 if ($aux->email && $aux->first === "" && $aux->last === "" && $this->prow 427 && ($old_au = $this->prow->author_by_email($aux->email))) { 428 $aux->first = get($old_au, "first", ""); 429 $aux->last = get($old_au, "last", ""); 430 if ($aux->affiliation === "") 431 $aux->affiliation = get($old_au, "affiliation", ""); 432 } 433 // set contactness and author index 434 if (is_object($au) && isset($au->contact)) 435 $aux->contact = !!$au->contact; 436 if (is_object($au) && isset($au->index) && is_int($au->index)) 437 $aux->index = $au->index; 438 else 439 $aux->index = count($pj->authors) + count($pj->bad_authors); 440 441 if ($aux->first !== "" || $aux->last !== "" 442 || $aux->email !== "" || $aux->affiliation !== "") 443 $pj->authors[] = $aux; 444 else 445 $pj->bad_authors[] = $aux; 446 if ($aux->email) { 447 $lemail = strtolower($aux->email); 448 $au_by_lemail[$lemail] = $aux; 449 if (!validate_email($lemail) 450 && (!$this->prow || !$this->prow->author_by_email($lemail))) 451 $pj->bad_email_authors[] = $aux; 452 } 453 } 454 455 private function normalize_topics($pj) { 456 $topics = $pj->topics; 457 unset($pj->topics); 458 if (is_string($topics)) 459 $topics = explode("\n", cleannl($topics)); 460 if (is_array($topics)) { 461 $new_topics = (object) array(); 462 foreach ($topics as $v) { 463 if ($v && (is_int($v) || is_string($v))) 464 $new_topics->$v = true; 465 else if ($v) 466 $this->format_error_at("topics", $v); 467 } 468 $topics = $new_topics; 469 } 470 if (is_object($topics)) { 471 $topic_map = $this->conf->topic_map(); 472 $pj->topics = (object) array(); 473 foreach ($topics as $k => $v) { 474 if (!$v) 475 /* skip */; 476 else if (isset($topic_map[$k])) 477 $pj->topics->$k = true; 478 else { 479 $tid = array_search($k, $topic_map, true); 480 if ($tid === false && $k !== "" && !ctype_digit($k)) { 481 $tmatches = []; 482 foreach ($topic_map as $tid => $tname) 483 if (strcasecmp($k, $tname) == 0) 484 $tmatches[] = $tid; 485 if (empty($tmatches) && $this->add_topics) { 486 $this->conf->qe("insert into TopicArea set topicName=?", $k); 487 if (!$this->conf->has_topics()) 488 $this->conf->save_setting("has_topics", 1); 489 $this->conf->invalidate_topics(); 490 $topic_map = $this->conf->topic_map(); 491 if (($tid = array_search($k, $topic_map, true)) !== false) 492 $tmatches[] = $tid; 493 } 494 $tid = (count($tmatches) == 1 ? $tmatches[0] : false); 495 } 496 if ($tid !== false) 497 $pj->topics->$tid = true; 498 else 499 $pj->bad_topics[] = $k; 500 } 501 } 502 } else if ($topics) 503 $this->format_error_at("topics", $topics); 504 } 505 506 private function normalize_options($pj, $options) { 507 // canonicalize option values to use IDs, not abbreviations 508 $pj->options = (object) array(); 509 foreach ($options as $id => $oj) { 510 $omatches = $this->conf->paper_opts->find_all($id); 511 if (count($omatches) != 1) 512 $pj->bad_options[$id] = true; 513 else { 514 $o = current($omatches); 515 // XXX setting decision in JSON? 516 if (($o->final && (!$this->prow || $this->prow->outcome <= 0)) 517 || $o->id <= 0) 518 continue; 519 $oid = $o->id; 520 $pj->options->$oid = $oj; 521 } 522 } 523 } 524 525 private function normalize_pc_conflicts($pj) { 526 $conflicts = get($pj, "pc_conflicts"); 527 $pj->pc_conflicts = (object) array(); 528 if (is_object($conflicts)) 529 $conflicts = (array) $conflicts; 530 foreach ($conflicts as $email => $ct) { 531 if (is_int($email) && is_string($ct)) 532 list($email, $ct) = array($ct, true); 533 if (!($pccid = $this->conf->pc_member_by_email($email))) 534 $pj->bad_pc_conflicts->$email = true; 535 else if (!is_bool($ct) && !is_int($ct) && !is_string($ct)) 536 $this->format_error_at("pc_conflicts", $ct); 537 else { 538 if (is_int($ct) && isset(Conflict::$type_names[$ct])) 539 $ctn = $ct; 540 else if ((is_bool($ct) || is_string($ct)) 541 && ($ctn = Conflict::parse($ct, CONFLICT_AUTHORMARK)) !== false) 542 /* OK */; 543 else { 544 $pj->bad_pc_conflicts->$email = $ct; 545 $ctn = Conflict::parse("other", 1); 546 } 547 $pj->pc_conflicts->$email = $ctn; 548 } 549 } 550 } 551 552 private function valid_contact($email) { 553 global $Me; 554 if ($email) { 555 if (validate_email($email) || strcasecmp($email, $Me->email) == 0) 556 return true; 557 foreach ($this->prow ? $this->prow->contacts(true) : [] as $cflt) 558 if (strcasecmp($cflt->email, $email) == 0) 559 return true; 560 } 561 return false; 562 } 563 564 private function normalize($pj) { 565 // Errors prevent saving 566 global $Now; 567 568 // Title, abstract 569 $this->normalize_string($pj, "title", true); 570 $this->normalize_string($pj, "abstract", false); 571 $this->normalize_string($pj, "collaborators", false); 572 if (isset($pj->collaborators)) { 573 $collab = rtrim(cleannl($pj->collaborators)); 574 if (!$this->prow || $collab !== rtrim(cleannl($this->prow->collaborators))) { 575 $old_collab = $collab; 576 $collab = (string) AuthorMatcher::fix_collaborators($old_collab); 577 if ($collab !== $old_collab) { 578 $name = $this->conf->setting("sub_pcconf") ? "Other conflicts" : "Potential conflicts"; 579 $this->warning_at("collaborators", "$name changed to follow our required format. You may want to look them over."); 580 } 581 } 582 $pj->collaborators = $collab; 583 } 584 585 // Authors 586 $au_by_lemail = []; 587 $pj->bad_authors = $pj->bad_email_authors = []; 588 if (isset($pj->authors)) { 589 if (is_array($pj->authors)) 590 $input_authors = $pj->authors; 591 else { 592 $this->format_error_at("authors", $pj->authors); 593 $input_authors = []; 594 } 595 $pj->authors = []; 596 foreach ($input_authors as $k => $au) { 597 if (is_string($au) || is_object($au)) 598 $this->normalize_author($pj, $au, $au_by_lemail); 599 else 600 $this->format_error_at("authors", $au); 601 } 602 } 603 604 // Status 605 foreach (array("withdrawn_at", "submitted_at", "final_submitted_at") as $k) 606 if (isset($pj->$k)) { 607 if (is_numeric($pj->$k)) 608 $pj->$k = (int) $pj->$k; 609 else if (is_string($pj->$k)) 610 $pj->$k = $this->conf->parse_time($pj->$k, $Now); 611 else 612 $pj->$k = false; 613 if ($pj->$k === false || $pj->$k < 0) 614 $pj->$k = $Now; 615 } 616 617 // Blindness 618 if (isset($pj->nonblind)) { 619 if (($x = friendly_boolean($pj->nonblind)) !== null) 620 $pj->nonblind = $x; 621 else { 622 $this->format_error_at("nonblind", $pj->nonblind); 623 unset($pj->nonblind); 624 } 625 } 626 627 // Topics 628 $pj->bad_topics = array(); 629 if (isset($pj->topics)) 630 $this->normalize_topics($pj); 631 632 // Options 633 $pj->bad_options = array(); 634 if (isset($pj->options)) { 635 if (is_associative_array($pj->options) || is_object($pj->options)) 636 $this->normalize_options($pj, $pj->options); 637 else if (is_array($pj->options) && count($pj->options) == 1 && is_object($pj->options[0])) 638 $this->normalize_options($pj, $pj->options[0]); 639 else if ($pj->options === false) 640 $pj->options = (object) array(); 641 else { 642 $this->format_error_at("options", $pj->options); 643 unset($pj->options); 644 } 645 } 646 647 // PC conflicts 648 $pj->bad_pc_conflicts = (object) array(); 649 if (get($pj, "pc_conflicts") 650 && (is_object($pj->pc_conflicts) || is_array($pj->pc_conflicts))) 651 $this->normalize_pc_conflicts($pj); 652 else if (get($pj, "pc_conflicts") === false) 653 $pj->pc_conflicts = (object) array(); 654 else if (isset($pj->pc_conflicts)) { 655 $this->format_error_at("pc_conflicts", $pj->pc_conflicts); 656 unset($pj->pc_conflicts); 657 } 658 659 // verify emails on authors marked as contacts 660 $pj->bad_contacts = array(); 661 foreach (get($pj, "authors") ? : array() as $au) 662 if (get($au, "contact") 663 && (!isset($au->email) || !$this->valid_contact($au->email))) 664 $pj->bad_contacts[] = $au; 665 666 // Contacts 667 $contacts = get($pj, "contacts"); 668 if ($contacts !== null) { 669 if (is_object($contacts) || is_array($contacts)) 670 $contacts = (array) $contacts; 671 else { 672 $this->format_error_at("contacts", $contacts); 673 $contacts = []; 674 } 675 $pj->contacts = []; 676 // verify emails on explicitly named contacts 677 foreach ($contacts as $k => $v) { 678 if (!$v) 679 continue; 680 if ($v === true) 681 $v = (object) array(); 682 else if (is_string($v) && is_int($k)) { 683 $v = trim($v); 684 if ($this->valid_contact($v)) 685 $v = (object) array("email" => $v); 686 else 687 $v = Text::analyze_name($v); 688 } 689 if (is_object($v) && !get($v, "email") && is_string($k)) 690 $v->email = $k; 691 if (is_object($v) && get($v, "email")) { 692 if ($this->valid_contact($v->email)) 693 $pj->contacts[] = (object) array_merge((array) get($au_by_lemail, strtolower($v->email)), (array) $v); 694 else 695 $pj->bad_contacts[] = $v; 696 } else 697 $this->format_error_at("contacts", $v); 698 } 699 } 700 701 // Inherit contactness 702 if (isset($pj->authors) && $this->prow) { 703 foreach ($this->prow->contacts(true) as $cflt) 704 if ($cflt->conflictType >= CONFLICT_CONTACTAUTHOR 705 && ($aux = get($au_by_lemail, strtolower($cflt->email))) 706 && !isset($aux->contact)) 707 $aux->contact = true; 708 } 709 // If user modifies paper, make them a contact (not just an author) 710 if ($this->prow 711 && $this->user 712 && !$this->user->allow_administer($this->prow) 713 && $this->prow->conflict_type($this->user) === CONFLICT_AUTHOR) { 714 if (!isset($pj->contacts)) { 715 $pj->contacts = []; 716 foreach ($this->prow->contacts(true) as $cflt) 717 if ($cflt->conflictType >= CONFLICT_CONTACTAUTHOR) 718 $pj->contacts[] = (object) ["email" => $cflt->email]; 719 } 720 if (!array_filter($pj->contacts, function ($cflt) { 721 return strcasecmp($this->user->email, $cflt->email) === 0; 722 })) 723 $pj->contacts[] = (object) ["email" => $this->user->email]; 724 } 725 } 726 727 static function check_title(PaperStatus $ps, $pj) { 728 $v = convert_to_utf8(get_s($pj, "title")); 729 if ($v === "" 730 && (isset($pj->title) || !$ps->prow || (string) $ps->prow->title === "")) 731 $ps->error_at("title", $ps->_("Each submission must have a title.")); 732 if (!$ps->prow 733 || (!$ps->has_error_at("title") 734 && isset($pj->title) 735 && $v !== (string) $ps->prow->title)) 736 $ps->save_paperf("title", $v, "title"); 737 } 738 739 static function check_abstract(PaperStatus $ps, $pj) { 740 $v = convert_to_utf8(get_s($pj, "abstract")); 741 if ($v === "" 742 && (isset($pj->abstract) || !$ps->prow || (string) $ps->prow->abstract === "")) { 743 if (!$ps->conf->opt("noAbstract")) 744 $ps->error_at("abstract", $ps->_("Each submission must have an abstract.")); 745 } 746 if (!$ps->prow 747 || (!$ps->has_error_at("abstract") 748 && isset($pj->abstract) 749 && $v !== (string) $ps->prow->abstract)) 750 $ps->save_paperf("abstract", $v, "abstract"); 751 } 752 753 static private function author_information($pj) { 754 $x = ""; 755 foreach ($pj && get($pj, "authors") ? $pj->authors : [] as $au) { 756 $x .= get($au, "first", get($au, "firstName", "")) . "\t" 757 . get($au, "last", get($au, "lastName", "")) . "\t" 758 . get($au, "email", "") . "\t" 759 . get($au, "affiliation", "") . "\n"; 760 } 761 return $x; 762 } 763 764 static function check_authors(PaperStatus $ps, $pj) { 765 $authors = get($pj, "authors"); 766 $max_authors = $ps->conf->opt("maxAuthors"); 767 if ((is_array($authors) && empty($authors)) 768 || ($authors === null && (!$ps->prow || !$ps->prow->author_list()))) 769 $ps->error_at("authors", $ps->_("Each submission must have at least one author.", $max_authors)); 770 if ($max_authors > 0 && is_array($authors) && count($authors) > $max_authors) 771 $ps->error_at("authors", $ps->_("Each submission can have at most %d authors.", $max_authors)); 772 if (!empty($pj->bad_authors)) 773 $ps->error_at("authors", $ps->_("Some authors ignored.")); 774 foreach ($pj->bad_email_authors as $aux) { 775 $ps->error_at("authors", null); 776 $ps->error_at("auemail" . $aux->index, $ps->_("“%s” is not a valid email address.", htmlspecialchars($aux->email))); 777 } 778 if (!$ps->prow || isset($pj->authors)) { 779 $v = convert_to_utf8(self::author_information($pj)); 780 if (!$ps->prow 781 || (!$ps->has_error_at("authors") 782 && $v !== $ps->prow->authorInformation)) 783 $ps->save_paperf("authorInformation", $v, "authors"); 784 } 785 } 786 787 static function check_collaborators(PaperStatus $ps, $pj) { 788 $v = convert_to_utf8(get_s($pj, "collaborators")); 789 if (!$ps->prow 790 || (isset($pj->collaborators) 791 && $v !== (string) $ps->prow->collaborators)) 792 $ps->save_paperf("collaborators", $v, "collaborators"); 793 } 794 795 static function check_nonblind(PaperStatus $ps, $pj) { 796 if ($ps->conf->submission_blindness() == Conf::BLIND_OPTIONAL 797 && (!$ps->prow 798 || (isset($pj->nonblind) 799 && !$pj->nonblind !== !!$ps->prow->blind))) { 800 $ps->save_paperf("blind", get($pj, "nonblind") ? 0 : 1, "nonblind"); 801 } 802 } 803 804 static function check_pdfs(PaperStatus $ps, $pj) { 805 // store documents (XXX should attach to paper even if error) 806 foreach (["submission", "final"] as $i => $k) { 807 if (isset($pj->$k) && $pj->$k) { 808 $pj->$k = $ps->upload_document($pj->$k, $ps->conf->paper_opts->get($i ? DTYPE_FINAL : DTYPE_SUBMISSION)); 809 } 810 if (!$ps->prow 811 || (isset($pj->$k) 812 && !$ps->has_error_at($i ? "final" : "paper"))) { 813 $null_id = $i ? 0 : 1; 814 $new_id = isset($pj->$k) && $pj->$k ? $pj->$k->paperStorageId : $null_id; 815 $prowk = $i ? "finalPaperStorageId" : "paperStorageId"; 816 if (($ps->prow ? $ps->prow->$prowk : $null_id) != $new_id) 817 $ps->save_paperf($prowk, $new_id, $k); 818 else if (!$ps->prow) 819 $ps->save_paperf($prowk, $new_id); 820 } 821 } 822 } 823 824 static function check_status(PaperStatus $ps, $pj) { 825 global $Now; 826 $pj_withdrawn = get($pj, "withdrawn"); 827 $pj_submitted = get($pj, "submitted"); 828 $pj_draft = get($pj, "draft"); 829 if ($pj_withdrawn === null 830 && $pj_submitted === null 831 && $pj_draft === null) { 832 $pj_status = get($pj, "status"); 833 if ($pj_status === "submitted") 834 $pj_submitted = true; 835 else if ($pj_status === "withdrawn") 836 $pj_withdrawn = true; 837 else if ($pj_status === "draft") 838 $pj_draft = true; 839 } 840 if ($ps->has_error() 841 && ($pj_submitted || $pj_draft === false) 842 && !$pj_withdrawn 843 && (!$ps->prow || $ps->prow->timeSubmitted == 0)) { 844 $pj_submitted = false; 845 $pj_draft = true; 846 } 847 848 $submitted = false; 849 if ($pj_withdrawn !== null 850 || $pj_submitted !== null 851 || $pj_draft !== null) { 852 if ($pj_submitted !== null) 853 $submitted = $pj_submitted; 854 else if ($pj_draft !== null) 855 $submitted = !$pj_draft; 856 else if ($ps->prow) 857 $submitted = $ps->prow->timeSubmitted != 0; 858 if (isset($pj->submitted_at)) 859 $submitted_at = $pj->submitted_at; 860 else if ($ps->prow) 861 $submitted_at = $ps->prow->submitted_at(); 862 else 863 $submitted_at = 0; 864 if ($pj_withdrawn) { 865 if ($submitted && $submitted_at <= 0) 866 $submitted_at = -100; 867 else if (!$submitted) 868 $submitted_at = 0; 869 else 870 $submitted_at = -$submitted_at; 871 if (!$ps->prow || $ps->prow->timeWithdrawn <= 0) { 872 $ps->save_paperf("timeWithdrawn", get($pj, "withdrawn_at") ? : $Now, "status"); 873 $ps->save_paperf("timeSubmitted", $submitted_at); 874 } else if (($ps->prow->submitted_at() > 0) !== $submitted) 875 $ps->save_paperf("timeSubmitted", $submitted_at, "status"); 876 } else if ($submitted) { 877 if (!$ps->prow || $ps->prow->timeSubmitted <= 0) { 878 if ($submitted_at <= 0 || $submitted_at === PaperInfo::SUBMITTED_AT_FOR_WITHDRAWN) 879 $submitted_at = $Now; 880 $ps->save_paperf("timeSubmitted", $submitted_at, "status"); 881 } 882 if ($ps->prow && $ps->prow->timeWithdrawn != 0) 883 $ps->save_paperf("timeWithdrawn", 0, "status"); 884 } else if ($ps->prow && ($ps->prow->timeWithdrawn > 0 || $ps->prow->timeSubmitted > 0)) { 885 $ps->save_paperf("timeSubmitted", 0, "status"); 886 $ps->save_paperf("timeWithdrawn", 0); 887 } 888 } 889 $ps->_paper_submitted = !$pj_withdrawn && $submitted; 890 } 891 892 static function check_final_status(PaperStatus $ps, $pj) { 893 global $Now; 894 if (isset($pj->final_submitted)) { 895 if ($pj->final_submitted) 896 $time = get($pj, "final_submitted_at") ? : $Now; 897 else 898 $time = 0; 899 if (!$ps->prow || $ps->prow->timeFinalSubmitted != $time) 900 $ps->save_paperf("timeFinalSubmitted", $time, "final_status"); 901 } 902 } 903 904 static function check_topics(PaperStatus $ps, $pj) { 905 if (!empty($pj->bad_topics)) 906 $ps->warning_at("topics", $ps->_("Unknown topics ignored (%2\$s).", count($pj->bad_topics), htmlspecialchars(join("; ", $pj->bad_topics)))); 907 if (isset($pj->topics)) { 908 $old_topics = $ps->prow ? $ps->prow->topic_list() : []; 909 $new_topics = array_map("intval", array_keys((array) $pj->topics)); 910 sort($old_topics); 911 sort($new_topics); 912 if ($old_topics !== $new_topics) { 913 $ps->diffs["topics"] = true; 914 $ps->_topic_ins = $new_topics; 915 } 916 } 917 } 918 919 static function execute_topics(PaperStatus $ps) { 920 if (isset($ps->_topic_ins)) { 921 $ps->conf->qe("delete from PaperTopic where paperId=?", $ps->paperId); 922 if (!empty($ps->_topic_ins)) { 923 $ti = array_map(function ($tid) use ($ps) { 924 return [$ps->paperId, $tid]; 925 }, $ps->_topic_ins); 926 $ps->conf->qe("insert into PaperTopic (paperId,topicId) values ?v", $ti); 927 } 928 } 929 } 930 931 static function check_options(PaperStatus $ps, $pj) { 932 if (!empty($pj->bad_options)) 933 $ps->warning_at("options", $ps->_("Unknown options ignored (%2\$s).", count($pj->bad_options), htmlspecialchars(join("; ", array_keys($pj->bad_options))))); 934 if (!isset($pj->options)) 935 return; 936 937 $parsed_options = array(); 938 foreach ($pj->options as $oid => $oj) { 939 $o = $ps->conf->paper_opts->get($oid); 940 $result = null; 941 if ($oj !== null) 942 $result = $o->store_json($oj, $ps); 943 if ($result === null || $result === false) 944 $result = []; 945 else if (!is_array($result)) 946 $result = [[$result]]; 947 else if (count($result) == 2 && !is_int($result[1])) 948 $result = [$result]; 949 if (!$ps->has_error_at($o->field_key())) 950 $parsed_options[$o->id] = $result; 951 } 952 953 ksort($parsed_options); 954 foreach ($parsed_options as $id => $parsed_vs) { 955 // old values 956 $ov = $od = []; 957 if ($ps->prow) { 958 list($ov, $od) = $ps->prow->option_value_data($id); 959 } 960 961 // new values 962 $nv = $nd = []; 963 foreach ($parsed_vs as $vx) { 964 $nv[] = is_int($vx) ? $vx : $vx[0]; 965 $nd[] = is_int($vx) ? null : get($vx, 1); 966 } 967 968 // save difference 969 if ($ov !== $nv || $od !== $nd) { 970 $opt = $ps->conf->paper_opts->get($id); 971 $ps->_option_delid[] = $id; 972 $ps->diffs[$opt->json_key()] = true; 973 for ($i = 0; $i < count($nv); ++$i) { 974 $qv0 = [-1, $id, $nv[$i], null, null]; 975 if ($nd[$i] !== null) { 976 $qv0[strlen($nd[$i]) < 32768 ? 3 : 4] = $nd[$i]; 977 } 978 $ps->_option_ins[] = $qv0; 979 } 980 if ($opt->has_document()) 981 $ps->_document_change = true; 982 } 983 } 984 } 985 986 static function execute_options(PaperStatus $ps) { 987 if (!empty($ps->_option_delid)) 988 $ps->conf->qe("delete from PaperOption where paperId=? and optionId?a", $ps->paperId, $ps->_option_delid); 989 if (!empty($ps->_option_ins)) { 990 foreach ($ps->_option_ins as &$x) 991 $x[0] = $ps->paperId; 992 $ps->conf->qe("insert into PaperOption (paperId, optionId, value, data, dataOverflow) values ?v", $ps->_option_ins); 993 } 994 } 995 996 static private function contacts_array($pj) { 997 $contacts = array(); 998 foreach (get($pj, "authors") ? : [] as $au) 999 if (get($au, "email") && validate_email($au->email)) { 1000 $c = clone $au; 1001 $contacts[strtolower($c->email)] = $c; 1002 } 1003 foreach (get($pj, "contacts") ? : array() as $v) { 1004 $lemail = strtolower($v->email); 1005 $c = (object) array_merge((array) get($contacts, $lemail), (array) $v); 1006 $c->contact = true; 1007 $contacts[$lemail] = $c; 1008 } 1009 return $contacts; 1010 } 1011 1012 function conflicts_array($pj) { 1013 $cflts = []; 1014 1015 // extract PC conflicts 1016 if (isset($pj->pc_conflicts)) { 1017 foreach ((array) $pj->pc_conflicts as $email => $type) 1018 $cflts[strtolower($email)] = $type; 1019 } else if ($this->prow) { 1020 foreach ($this->prow->conflicts(true) as $cflt) 1021 if ($cflt->conflictType < CONFLICT_AUTHOR) 1022 $cflts[strtolower($cflt->email)] = $cflt->conflictType; 1023 } 1024 1025 // extract contacts 1026 if (isset($pj->contacts)) { 1027 foreach ($pj->contacts as $aux) { 1028 $cflts[strtolower($aux->email)] = CONFLICT_CONTACTAUTHOR; 1029 } 1030 } else if ($this->prow) { 1031 foreach ($this->prow->contacts(true) as $cflt) { 1032 if ($cflt->conflictType == CONFLICT_CONTACTAUTHOR) 1033 $cflts[strtolower($cflt->email)] = CONFLICT_CONTACTAUTHOR; 1034 } 1035 } 1036 1037 // extract authors 1038 if (isset($pj->authors)) { 1039 foreach ($pj->authors as $aux) { 1040 if (isset($aux->email)) { 1041 $lemail = strtolower($aux->email); 1042 if (!isset($aux->contact)) 1043 $ctype = max(get_i($cflts, $lemail), CONFLICT_AUTHOR); 1044 else 1045 $ctype = $aux->contact ? CONFLICT_CONTACTAUTHOR : CONFLICT_AUTHOR; 1046 $cflts[$lemail] = $ctype; 1047 } 1048 } 1049 } else if ($this->prow) { 1050 foreach ($this->prow->contacts(true) as $cflt) { 1051 $lemail = strtolower($cflt->email); 1052 $cflts[$lemail] = max(get_i($cflts, $lemail), $cflt->conflictType); 1053 } 1054 foreach ($this->prow->author_list() as $au) 1055 if ($au->email !== "") { 1056 $lemail = strtolower($au->email); 1057 $cflts[$lemail] = max(get_i($cflts, $lemail), CONFLICT_AUTHOR); 1058 } 1059 } 1060 1061 // chair conflicts cannot be overridden 1062 if ($this->prow) { 1063 foreach ($this->prow->conflicts(true) as $cflt) { 1064 if ($cflt->conflictType == CONFLICT_CHAIRMARK) { 1065 $lemail = strtolower($cflt->email); 1066 if (get_i($cflts, $lemail) < CONFLICT_CHAIRMARK 1067 && $this->user 1068 && !$this->user->can_administer($this->prow)) 1069 $cflts[$lemail] = CONFLICT_CHAIRMARK; 1070 } 1071 } 1072 } 1073 1074 ksort($cflts); 1075 return $cflts; 1076 } 1077 1078 static private function check_contacts(PaperStatus $ps, $pj) { 1079 $cflts = $ps->conflicts_array($pj); 1080 if (!array_filter($cflts, function ($cflt) { return $cflt >= CONFLICT_CONTACTAUTHOR; }) 1081 && $ps->prow 1082 && array_filter($ps->prow->contacts(), function ($cflt) { return $cflt->conflictType >= CONFLICT_CONTACTAUTHOR; })) { 1083 $ps->error_at("contacts", $ps->_("Each submission must have at least one contact.")); 1084 } 1085 if ($ps->prow 1086 && $ps->user 1087 && !$ps->user->allow_administer($ps->prow) 1088 && get($cflts, strtolower($ps->user->email), 0) < CONFLICT_AUTHOR) { 1089 $ps->error_at("contacts", $ps->_("You can’t remove yourself as submission contact. (Ask another contact to remove you.)")); 1090 } 1091 foreach ($pj->bad_contacts as $reg) { 1092 if (!isset($reg->email)) 1093 $ps->error_at("contacts", $ps->_("Contact %s has no associated email.", Text::user_html($reg))); 1094 else 1095 $ps->error_at("contacts", $ps->_("Contact email %s is invalid.", htmlspecialchars($reg->email))); 1096 } 1097 } 1098 1099 static function check_conflicts(PaperStatus $ps, $pj) { 1100 if (isset($pj->contacts)) 1101 self::check_contacts($ps, $pj); 1102 1103 $ps->_new_conflicts = $new_cflts = $ps->conflicts_array($pj); 1104 $old_cflts = $ps->conflicts_array((object) []); 1105 foreach ($new_cflts + $old_cflts as $lemail => $v) { 1106 $new_ctype = get_i($new_cflts, $lemail); 1107 $old_ctype = get_i($old_cflts, $lemail); 1108 if ($new_ctype !== $old_ctype) { 1109 if ($new_ctype >= CONFLICT_AUTHOR || $old_ctype >= CONFLICT_AUTHOR) 1110 $ps->diffs["contacts"] = true; 1111 if (($new_ctype > 0 && $new_ctype < CONFLICT_AUTHOR) 1112 || ($old_ctype > 0 && $old_ctype < CONFLICT_AUTHOR)) 1113 $ps->diffs["pc_conflicts"] = true; 1114 } 1115 } 1116 } 1117 1118 static function postcheck_contacts(PaperStatus $ps, $pj) { 1119 if (isset($ps->diffs["contacts"]) && !$ps->has_error_at("contacts")) { 1120 foreach (self::contacts_array($pj) as $c) { 1121 $flags = (get($c, "contact") ? 0 : Contact::SAVE_IMPORT) 1122 | ($ps->no_email ? 0 : Contact::SAVE_NOTIFY); 1123 $c->disabled = !!$ps->disable_users; 1124 if (!Contact::create($ps->conf, $ps->user, $c, $flags) 1125 && !($flags & Contact::SAVE_IMPORT)) 1126 $ps->error_at("contacts", $ps->_("Could not create an account for contact %s.", Text::user_html($c))); 1127 } 1128 } 1129 if ((isset($ps->diffs["contacts"]) || isset($ps->diffs["pc_conflicts"])) 1130 && !$ps->has_error_at("contacts") 1131 && !$ps->has_error_at("pc_conflicts")) { 1132 $ps->_conflict_ins = []; 1133 if (!empty($ps->_new_conflicts)) { 1134 $result = $ps->conf->qe("select contactId, email from ContactInfo where email?a", array_keys($ps->_new_conflicts)); 1135 while (($row = edb_row($result))) 1136 $ps->_conflict_ins[] = [-1, $row[0], $ps->_new_conflicts[strtolower($row[1])]]; 1137 Dbl::free($result); 1138 } 1139 } 1140 } 1141 1142 static function execute_conflicts(PaperStatus $ps) { 1143 if ($ps->_conflict_ins !== null) { 1144 $ps->conf->qe("delete from PaperConflict where paperId=?", $ps->paperId); 1145 foreach ($ps->_conflict_ins as &$x) 1146 $x[0] = $ps->paperId; 1147 if (!empty($ps->_conflict_ins)) 1148 $ps->conf->qe("insert into PaperConflict (paperId,contactId,conflictType) values ?v", $ps->_conflict_ins); 1149 } 1150 } 1151 1152 private function save_paperf($f, $v, $diff = null) { 1153 assert(!isset($this->_paper_upd[$f])); 1154 $this->_paper_upd[$f] = $v; 1155 if ($diff) 1156 $this->diffs[$diff] = true; 1157 } 1158 1159 function prepare_save_paper_json($pj) { 1160 assert(!$this->hide_docids); 1161 assert(is_object($pj)); 1162 1163 $paperid = get($pj, "pid", get($pj, "id", null)); 1164 if ($paperid !== null && is_int($paperid) && $paperid <= 0) 1165 $paperid = null; 1166 if ($paperid !== null && !is_int($paperid)) { 1167 $key = isset($pj->pid) ? "pid" : "id"; 1168 $this->format_error_at($key, $paperid); 1169 return false; 1170 } 1171 1172 if (get($pj, "error") || get($pj, "error_html")) { 1173 $this->error_at("error", $this->_("Refusing to save submission with error")); 1174 return false; 1175 } 1176 1177 $this->clear(); 1178 $this->paperId = $paperid ? : -1; 1179 if ($paperid) 1180 $this->prow = $this->conf->paperRow(["paperId" => $paperid, "topics" => true, "options" => true], $this->user); 1181 if ($pj && $this->prow && $paperid !== $this->prow->paperId) { 1182 $this->error_at("pid", $this->_("Saving submission with different ID")); 1183 return false; 1184 } 1185 1186 // normalize and check format 1187 $this->normalize($pj); 1188 if ($this->has_error()) 1189 return false; 1190 1191 // save parts and track diffs 1192 self::check_title($this, $pj); 1193 self::check_abstract($this, $pj); 1194 self::check_authors($this, $pj); 1195 self::check_collaborators($this, $pj); 1196 self::check_nonblind($this, $pj); 1197 self::check_conflicts($this, $pj); 1198 self::check_pdfs($this, $pj); 1199 self::check_topics($this, $pj); 1200 self::check_options($this, $pj); 1201 self::check_status($this, $pj); 1202 self::check_final_status($this, $pj); 1203 self::postcheck_contacts($this, $pj); 1204 return true; 1205 } 1206 1207 private function unused_random_pid() { 1208 $n = max(100, 3 * $this->conf->fetch_ivalue("select count(*) from Paper")); 1209 while (1) { 1210 $pids = []; 1211 while (count($pids) < 10) 1212 $pids[] = mt_rand(1, $n); 1213 1214 $result = $this->conf->qe("select paperId from Paper where paperId?a", $pids); 1215 while ($result && ($row = $result->fetch_row())) 1216 $pids = array_values(array_diff($pids, [(int) $row[0]])); 1217 Dbl::free($result); 1218 1219 if (!empty($pids)) 1220 return $pids[0]; 1221 } 1222 } 1223 1224 function execute_save_paper_json($pj) { 1225 global $Now; 1226 if (!empty($this->_paper_upd)) { 1227 if ($this->conf->submission_blindness() == Conf::BLIND_NEVER) 1228 $this->save_paperf("blind", 0); 1229 else if ($this->conf->submission_blindness() != Conf::BLIND_OPTIONAL) 1230 $this->save_paperf("blind", 1); 1231 1232 $old_joindoc = $this->prow ? $this->prow->joindoc() : null; 1233 $old_joinid = $old_joindoc ? $old_joindoc->paperStorageId : 0; 1234 1235 $new_final_docid = get($this->_paper_upd, "finalPaperStorageId"); 1236 $new_sub_docid = get($this->_paper_upd, "paperStorageId"); 1237 if ($new_final_docid !== null || $new_sub_docid !== null) 1238 $this->_document_change = true; 1239 1240 if ($new_final_docid > 0) 1241 $new_joindoc = $pj->final; 1242 else if ($new_final_docid === null 1243 && $this->prow 1244 && $this->prow->finalPaperStorageId > 0) 1245 $new_joindoc = $this->prow->document(DTYPE_FINAL); 1246 else if ($new_sub_docid > 1) 1247 $new_joindoc = $pj->submission; 1248 else if ($new_sub_docid === null 1249 && $this->prow 1250 && $this->prow->paperStorageId > 1) 1251 $new_joindoc = $this->prow->document(DTYPE_SUBMISSION); 1252 else 1253 $new_joindoc = null; 1254 $new_joinid = $new_joindoc ? $new_joindoc->paperStorageId : 0; 1255 1256 if ($new_joindoc && $new_joinid != $old_joinid) { 1257 if ($new_joindoc->ensure_size()) 1258 $this->save_paperf("size", $new_joindoc->size); 1259 else 1260 $this->save_paperf("size", 0); 1261 $this->save_paperf("mimetype", $new_joindoc->mimetype); 1262 $this->save_paperf("sha1", $new_joindoc->binary_hash()); 1263 $this->save_paperf("timestamp", $new_joindoc->timestamp); 1264 if ($this->conf->sversion >= 145) 1265 $this->save_paperf("pdfFormatStatus", 0); 1266 } else if (!$this->prow || $new_joinid != $old_joinid) { 1267 $this->save_paperf("size", 0); 1268 $this->save_paperf("mimetype", ""); 1269 $this->save_paperf("sha1", ""); 1270 $this->save_paperf("timestamp", 0); 1271 if ($this->conf->sversion >= 145) 1272 $this->save_paperf("pdfFormatStatus", 0); 1273 } 1274 1275 $this->save_paperf("timeModified", $Now); 1276 1277 $need_insert = $this->paperId <= 0; 1278 if (!$need_insert) { 1279 $qv = array_values($this->_paper_upd); 1280 $qv[] = $this->paperId; 1281 $result = $this->conf->qe_apply("update Paper set " . join("=?, ", array_keys($this->_paper_upd)) . "=? where paperId=?", $qv); 1282 if ($result 1283 && $result->affected_rows === 0 1284 && !$this->conf->fetch_value("select paperId from Paper where paperId=?", $this->paperId)) { 1285 $this->_paper_upd["paperId"] = $this->paperId; 1286 $need_insert = true; 1287 } 1288 } 1289 if ($need_insert) { 1290 if (($random_pids = $this->conf->setting("random_pids"))) { 1291 $this->conf->qe("lock tables Paper write"); 1292 $this->_paper_upd["paperId"] = $this->unused_random_pid(); 1293 } 1294 $result = $this->conf->qe_apply("insert into Paper set " . join("=?, ", array_keys($this->_paper_upd)) . "=?", array_values($this->_paper_upd)); 1295 if ($random_pids) 1296 $this->conf->qe("unlock tables"); 1297 if (!$result || !$result->insert_id) 1298 return $this->error_at(false, $this->_("Could not create paper.")); 1299 $pj->pid = $this->paperId = (int) $result->insert_id; 1300 if (!empty($this->uploaded_documents)) 1301 $this->conf->qe("update PaperStorage set paperId=? where paperStorageId?a", $this->paperId, $this->uploaded_documents); 1302 } 1303 1304 // maybe update `papersub` settings 1305 $was_submitted = $this->prow && $this->prow->timeWithdrawn <= 0 && $this->prow->timeSubmitted > 0; 1306 if ($this->_paper_submitted != $was_submitted) 1307 $this->conf->update_papersub_setting($this->_paper_submitted ? 1 : -1); 1308 } 1309 1310 self::execute_conflicts($this); 1311 self::execute_topics($this); 1312 self::execute_options($this); 1313 1314 // update autosearch 1315 $this->conf->update_autosearch_tags($this->paperId); 1316 1317 // update document inactivity 1318 if ($this->_document_change) { 1319 $pset = $this->conf->paper_set(null, ["paperId" => $this->paperId, "options" => true]); 1320 foreach ($pset as $prow) 1321 $prow->mark_inactive_documents(); 1322 } 1323 1324 return true; 1325 } 1326 1327 function save_paper_json($pj) { 1328 if ($this->prepare_save_paper_json($pj)) { 1329 $this->execute_save_paper_json($pj); 1330 return $this->paperId; 1331 } else 1332 return false; 1333 } 1334} 1335