1<?php
2// helpers.php -- HotCRP non-class helper functions
3// Copyright (c) 2006-2018 Eddie Kohler; see LICENSE.
4
5function defappend(&$var, $str) {
6    if (!isset($var))
7        $var = "";
8    $var .= $str;
9}
10
11function arrayappend(&$var, $value) {
12    if (isset($var))
13        $var[] = $value;
14    else
15        $var = array($value);
16}
17
18function mkarray($value) {
19    if (is_array($value))
20        return $value;
21    else
22        return array($value);
23}
24
25
26// string helpers
27
28function cvtint($value, $default = -1) {
29    $v = trim((string) $value);
30    if (is_numeric($v)) {
31        $ival = intval($v);
32        if ($ival == floatval($v))
33            return $ival;
34    }
35    return $default;
36}
37
38function cvtnum($value, $default = -1) {
39    $v = trim((string) $value);
40    if (is_numeric($v))
41        return floatval($v);
42    return $default;
43}
44
45
46// web helpers
47
48function hoturl_add_raw($url, $component) {
49    if (($pos = strpos($url, "#")) !== false) {
50        $component .= substr($url, $pos);
51        $url = substr($url, 0, $pos);
52    }
53    return $url . (strpos($url, "?") === false ? "?" : "&") . $component;
54}
55
56function hoturl_defaults($options = array()) {
57    foreach ($options as $k => $v)
58        if ($v !== null)
59            Conf::$hoturl_defaults[$k] = urlencode($v);
60        else
61            unset(Conf::$hoturl_defaults[$k]);
62    $ret = array();
63    if (Conf::$hoturl_defaults)
64        foreach (Conf::$hoturl_defaults as $k => $v)
65            $ret[$k] = urldecode($v);
66    return $ret;
67}
68
69function hoturl_site_relative($page, $options = null) {
70    global $Conf;
71    return $Conf->hoturl($page, $options, Conf::HOTURL_SITE_RELATIVE);
72}
73
74function hoturl($page, $options = null) {
75    global $Conf;
76    return $Conf->hoturl($page, $options);
77}
78
79function hoturl_post($page, $options = null) {
80    global $Conf;
81    return $Conf->hoturl($page, $options, Conf::HOTURL_POST);
82}
83
84function hoturl_absolute($page, $options = null) {
85    global $Conf;
86    return $Conf->hoturl($page, $options, Conf::HOTURL_ABSOLUTE);
87}
88
89function hoturl_absolute_nodefaults($page, $options = null) {
90    global $Conf;
91    return $Conf->hoturl($page, $options, Conf::HOTURL_ABSOLUTE | Conf::HOTURL_NO_DEFAULTS);
92}
93
94function hoturl_site_relative_raw($page, $options = null) {
95    return htmlspecialchars_decode(hoturl_site_relative($page, $options));
96}
97
98function hoturl_raw($page, $options = null) {
99    return htmlspecialchars_decode(hoturl($page, $options));
100}
101
102function hoturl_post_raw($page, $options = null) {
103    return htmlspecialchars_decode(hoturl_post($page, $options));
104}
105
106function hoturl_absolute_raw($page, $options = null) {
107    return htmlspecialchars_decode(hoturl_absolute($page, $options));
108}
109
110
111class SelfHref {
112    static private $argmap = null;
113    static private function set_argmap() {
114        self::$argmap = [
115            "p" => true, "paperId" => "p", "pap" => "p",
116            "r" => true, "reviewId" => "r",
117            "c" => true, "commentId" => "c",
118            "m" => true, "mode" => true,
119            "u" => true,
120            "g" => true,
121            "q" => true, "t" => true, "qa" => true, "qo" => true, "qx" => true, "qt" => true,
122            "fx" => true, "fy" => true,
123            "forceShow" => true, "ls" => true,
124            "tab" => true, "atab" => true, "sort" => true,
125            "group" => true, "monreq" => true, "noedit" => true,
126            "contact" => true, "reviewer" => true,
127            "editcomment" => true
128        ];
129    }
130    static function make(Qrequest $qreq = null, $params = [], $options = null) {
131        global $Qreq;
132        $qreq = $qreq ? : $Qreq;
133        if (self::$argmap === null)
134            self::set_argmap();
135
136        $x = [];
137        foreach ($qreq->make_array() as $k => $v) {
138            $ak = get(self::$argmap, $k);
139            if ($ak === true)
140                $ak = $k;
141            if ($ak
142                && ($ak === $k || !isset($qreq[$ak]))
143                && !array_key_exists($ak, $params)
144                && !is_array($v))
145                $x[$ak] = $v;
146        }
147        foreach ($params as $k => $v)
148            if ($v !== null)
149                $x[$k] = $v;
150
151        $page = Navigation::page();
152        if ($options && get($options, "site_relative")) {
153            if (get($options, "raw"))
154                return hoturl_site_relative_raw($page, $x);
155            else
156                return hoturl_site_relative($page, $x);
157        } else if ($options && get($options, "raw"))
158            return hoturl_raw($page, $x);
159        else
160            return hoturl($page, $x);
161    }
162    static function redirect(Qrequest $qreq = null, $params = []) {
163        Navigation::redirect(self::make($qreq, $params, ["raw" => true]));
164    }
165}
166
167function selfHref($params = [], $options = null) {
168    return SelfHref::make(null, $params, $options);
169}
170
171class JsonResult {
172    public $status;
173    public $content;
174    public $has_messages = false;
175
176    function __construct($values = null) {
177        if (is_int($values)) {
178            $this->status = $values;
179            if (func_num_args() === 2)
180                $values = func_get_arg(1);
181            else
182                $values = null;
183        }
184        if ($values === true || $values === false)
185            $this->content = ["ok" => $values];
186        else if ($values === null)
187            $this->content = [];
188        else if (is_object($values)) {
189            assert(!($values instanceof JsonResult));
190            $this->content = (array) $values;
191        } else if (is_string($values)) {
192            if ($this->status && $this->status > 299)
193                $this->content = ["ok" => false, "error" => $values];
194            else
195                $this->content = ["ok" => true, "response" => $values];
196        } else
197            $this->content = $values;
198    }
199    static function make($json, Conf $conf = null, $arg2 = null) {
200        if (is_int($json))
201            $json = new JsonResult($json, $arg2);
202        else if (!is_object($json) || !($json instanceof JsonResult))
203            $json = new JsonResult($json);
204        if (!$json->has_messages && $conf)
205            $json->transfer_messages($conf);
206        return $json;
207    }
208    function transfer_messages(Conf $conf, $div = false) {
209        if (session_id() !== ""
210            && ($msgs = $conf->session("msgs", []))) {
211            $conf->save_session("msgs", null);
212            $t = "";
213            foreach ($msgs as $msg) {
214                if (($msg[0] === "merror" || $msg[0] === "xmerror")
215                    && !isset($this->content["error"]))
216                    $this->content["error"] = $msg[1];
217                if ($div)
218                    $t .= Ht::xmsg($msg[0], $msg[1]);
219                else
220                    $t .= "<span class=\"$msg[0]\">$msg[1]</span>";
221            }
222            if ($t !== "")
223                $this->content["response"] = $t . get_s($this->content, "response");
224            $this->has_messages = true;
225        }
226    }
227}
228
229class JsonResultException extends Exception {
230    public $result;
231    static public $capturing = false;
232    function __construct($j) {
233        $this->result = $j;
234    }
235}
236
237function json_exit($json, $arg2 = null) {
238    global $Conf, $Qreq;
239    $json = JsonResult::make($json, $Conf, $arg2);
240    if (JsonResultException::$capturing)
241        throw new JsonResultException($json);
242    else {
243        if ($json->status)
244            http_response_code($json->status);
245        if (isset($_GET["text"]) && $_GET["text"])
246            header("Content-Type: text/plain; charset=utf-8");
247        else
248            header("Content-Type: application/json; charset=utf-8");
249        if ($Qreq && $Qreq->post_ok())
250            header("Access-Control-Allow-Origin: *");
251        echo json_encode_browser($json->content);
252        exit;
253    }
254}
255
256function csv_exit(CsvGenerator $csv) {
257    $csv->download_headers();
258    $csv->download();
259    exit;
260}
261
262function foldupbutton($foldnum = 0, $content = "", $js = null) {
263    if ($foldnum)
264        $js["data-fold-target"] = $foldnum;
265    $js["class"] = "ui q js-foldup";
266    return Ht::link(expander(null, $foldnum) . $content, "#", $js);
267}
268
269function expander($open, $foldnum = null) {
270    $f = $foldnum !== null;
271    $foldnum = ($foldnum !== 0 ? $foldnum : "");
272    $t = '<span class="expander">';
273    if ($open === null || !$open)
274        $t .= '<span class="in0' . ($f ? " fx$foldnum" : "") . '">' . Icons::ui_triangle(2) . '</span>';
275    if ($open === null || $open)
276        $t .= '<span class="in1' . ($f ? " fn$foldnum" : "") . '">' . Icons::ui_triangle(1) . '</span>';
277    return $t . '</span>';
278}
279
280function actas_link($cid, $contact = null) {
281    $contact = !$contact && is_object($cid) ? $cid : $contact;
282    $cid = is_object($contact) ? $contact->email : $cid;
283    return '<a href="' . selfHref(array("actas" => $cid))
284        . '" tabindex="-1">' . Ht::img("viewas.png", "[Act as]", array("title" => "Act as " . Text::name_text($contact))) . '</a>';
285}
286
287function decorateNumber($n) {
288    if ($n < 0)
289        return "&#8722;" . (-$n);
290    else if ($n > 0)
291        return $n;
292    else
293        return 0;
294}
295
296
297function _one_quicklink($id, $baseUrl, $urlrest, $listtype, $isprev) {
298    if ($listtype == "u") {
299        $result = Dbl::ql("select email from ContactInfo where contactId=?", $id);
300        $row = edb_row($result);
301        Dbl::free($result);
302        $paperText = htmlspecialchars($row ? $row[0] : $id);
303        $urlrest["u"] = urlencode($id);
304    } else {
305        $paperText = "#$id";
306        $urlrest["p"] = $id;
307    }
308    return "<a id=\"quicklink_" . ($isprev ? "prev" : "next")
309        . "\" class=\"x\" href=\"" . hoturl($baseUrl, $urlrest) . "\">"
310        . ($isprev ? Icons::ui_linkarrow(3) : "")
311        . $paperText
312        . ($isprev ? "" : Icons::ui_linkarrow(1))
313        . "</a>";
314}
315
316function goPaperForm($baseUrl = null, $args = array()) {
317    global $Conf, $Me;
318    if ($Me->is_empty())
319        return "";
320    $list = $Conf->active_list();
321    $x = Ht::form($Conf->hoturl($baseUrl ? : "paper"), ["method" => "get", "class" => "gopaper"]);
322    if ($baseUrl == "profile")
323        $x .= Ht::entry("u", "", array("id" => "quicksearchq", "size" => 15, "placeholder" => "User search", "class" => "usersearch need-autogrow"));
324    else
325        $x .= Ht::entry("p", "", array("id" => "quicksearchq", "size" => 10, "placeholder" => "(All)", "class" => "papersearch need-autogrow"));
326    foreach ($args as $k => $v)
327        $x .= Ht::hidden($k, $v);
328    $x .= "&nbsp; " . Ht::submit("Search") . "</form>";
329    return $x;
330}
331
332function rm_rf_tempdir($tempdir) {
333    assert(substr($tempdir, 0, 1) === "/");
334    exec("/bin/rm -rf " . escapeshellarg($tempdir));
335}
336
337function clean_tempdirs() {
338    $dir = sys_get_temp_dir() ? : "/";
339    while (substr($dir, -1) === "/")
340        $dir = substr($dir, 0, -1);
341    $dirh = opendir($dir);
342    $now = time();
343    while (($fname = readdir($dirh)) !== false)
344        if (preg_match('/\Ahotcrptmp\d+\z/', $fname)
345            && is_dir("$dir/$fname")
346            && ($mtime = @filemtime("$dir/$fname")) !== false
347            && $mtime < $now - 1800)
348            rm_rf_tempdir("$dir/$fname");
349    closedir($dirh);
350}
351
352function tempdir($mode = 0700) {
353    $dir = sys_get_temp_dir() ? : "/";
354    while (substr($dir, -1) === "/")
355        $dir = substr($dir, 0, -1);
356    for ($i = 0; $i < 100; $i++) {
357        $path = $dir . "/hotcrptmp" . mt_rand(0, 9999999);
358        if (mkdir($path, $mode)) {
359            register_shutdown_function("rm_rf_tempdir", $path);
360            return $path;
361        }
362    }
363    return false;
364}
365
366
367// text helpers
368function commajoin($what, $joinword = "and") {
369    $what = array_values($what);
370    $c = count($what);
371    if ($c == 0)
372        return "";
373    else if ($c == 1)
374        return $what[0];
375    else if ($c == 2)
376        return $what[0] . " " . $joinword . " " . $what[1];
377    else
378        return join(", ", array_slice($what, 0, -1)) . ", " . $joinword . " " . $what[count($what) - 1];
379}
380
381function prefix_commajoin($what, $prefix, $joinword = "and") {
382    return commajoin(array_map(function ($x) use ($prefix) {
383        return $prefix . $x;
384    }, $what), $joinword);
385}
386
387function numrangejoin($range) {
388    $a = [];
389    $format = null;
390    foreach ($range as $current) {
391        if ($format !== null
392            && sprintf($format, $intval + 1) === (string) $current) {
393            ++$intval;
394            $last = $current;
395            continue;
396        } else {
397            if ($format !== null && $first === $last)
398                $a[] = $first;
399            else if ($format !== null)
400                $a[] = $first . "–" . substr($last, $plen);
401            if ($current !== "" && ctype_digit($current)) {
402                $format = "%0" . strlen($current) . "d";
403                $plen = 0;
404                $first = $last = $current;
405                $intval = intval($current);
406            } else if (preg_match('/\A(\D*)(\d+)\z/', $current, $m)) {
407                $format = str_replace("%", "%%", $m[1]) . "%0" . strlen($m[2]) . "d";
408                $plen = strlen($m[1]);
409                $first = $last = $current;
410                $intval = intval($m[2]);
411            } else {
412                $format = null;
413                $a[] = $current;
414            }
415        }
416    }
417    if ($format !== null && $first === $last)
418        $a[] = $first;
419    else if ($format !== null)
420        $a[] = $first . "–" . substr($last, $plen);
421    return commajoin($a);
422}
423
424function pluralx($n, $what) {
425    if (is_array($n))
426        $n = count($n);
427    return $n == 1 ? $what : pluralize($what);
428}
429
430function pluralize($what) {
431    if ($what == "this")
432        return "these";
433    else if ($what == "has")
434        return "have";
435    else if ($what == "is")
436        return "are";
437    else if (str_ends_with($what, ")") && preg_match('/\A(.*?)(\s*\([^)]*\))\z/', $what, $m))
438        return pluralize($m[1]) . $m[2];
439    else if (preg_match('/\A.*?(?:s|sh|ch|[bcdfgjklmnpqrstvxz]y)\z/', $what)) {
440        if (substr($what, -1) == "y")
441            return substr($what, 0, -1) . "ies";
442        else
443            return $what . "es";
444    } else
445        return $what . "s";
446}
447
448function plural($n, $what) {
449    return (is_array($n) ? count($n) : $n) . ' ' . pluralx($n, $what);
450}
451
452function ordinal($n) {
453    $x = $n;
454    if ($x > 100)
455        $x = $x % 100;
456    if ($x > 20)
457        $x = $x % 10;
458    return $n . ($x < 1 || $x > 3 ? "th" : ($x == 1 ? "st" : ($x == 2 ? "nd" : "rd")));
459}
460
461function tabLength($text, $all) {
462    $len = 0;
463    for ($i = 0; $i < strlen($text); $i++)
464        if ($text[$i] == ' ')
465            $len++;
466        else if ($text[$i] == '\t')
467            $len += 8 - ($len % 8);
468        else if (!$all)
469            break;
470        else
471            $len++;
472    return $len;
473}
474
475function ini_get_bytes($varname, $value = null) {
476    $val = trim($value !== null ? $value : ini_get($varname));
477    $last = strlen($val) ? strtolower($val[strlen($val) - 1]) : ".";
478    return (int) ceil(floatval($val) * (1 << (+strpos(".kmg", $last) * 10)));
479}
480
481function filter_whynot($whyNot, $keys) {
482    $revWhyNot = [];
483    foreach ($whyNot as $k => $v) {
484        if ($k === "fail" || $k === "paperId" || $k === "conf" || in_array($k, $keys))
485            $revWhyNot[$k] = $v;
486    }
487    return $revWhyNot;
488}
489
490function whyNotText($whyNot, $text_only = false) {
491    global $Conf, $Now;
492    if (is_string($whyNot))
493        $whyNot = array($whyNot => 1);
494    $conf = get($whyNot, "conf") ? : $Conf;
495    $paperId = (isset($whyNot["paperId"]) ? $whyNot["paperId"] : -1);
496    $reviewId = (isset($whyNot["reviewId"]) ? $whyNot["reviewId"] : -1);
497    $ms = [];
498    $quote = $text_only ? function ($x) { return $x; } : "htmlspecialchars";
499    if (isset($whyNot["invalidId"])) {
500        $x = $whyNot["invalidId"] . "Id";
501        if (isset($whyNot[$x]))
502            $ms[] = $conf->_("Invalid " . $whyNot["invalidId"] . " number “%s”.", $quote($whyNot[$x]));
503        else
504            $ms[] = $conf->_("Invalid " . $whyNot["invalidId"] . " number.");
505    }
506    if (isset($whyNot["noPaper"]))
507        $ms[] = $conf->_("No such submission #%d.", $paperId);
508    if (isset($whyNot["dbError"]))
509        $ms[] = $whyNot["dbError"];
510    if (isset($whyNot["administer"]))
511        $ms[] = $conf->_("You can’t administer submission #%d.", $paperId);
512    if (isset($whyNot["permission"]))
513        $ms[] = $conf->_c("eperm", "Permission error.", $whyNot["permission"], $paperId);
514    if (isset($whyNot["pdfPermission"]))
515        $ms[] = $conf->_c("eperm", "Permission error.", "view_pdf", $paperId);
516    if (isset($whyNot["optionPermission"]))
517        $ms[] = $conf->_("You don’t have permission to view the %2\$s for submission #%1\$d.", $paperId, $whyNot["optionPermission"]->message_title);
518    if (isset($whyNot["optionNotAccepted"]))
519        $ms[] = $conf->_("Non-accepted submission #%d can have no %s.", $paperId, $whyNot["optionNotAccepted"]->message_title);
520    if (isset($whyNot["signin"]))
521        $ms[] = $conf->_c("eperm", "You have been signed out.", $whyNot["signin"], $paperId);
522    if (isset($whyNot["withdrawn"]))
523        $ms[] = $conf->_("Submission #%d has been withdrawn.", $paperId);
524    if (isset($whyNot["notWithdrawn"]))
525        $ms[] = $conf->_("Submission #%d is not withdrawn.", $paperId);
526    if (isset($whyNot["notSubmitted"]))
527        $ms[] = $conf->_("Submission #%d is only a draft.", $paperId);
528    if (isset($whyNot["rejected"]))
529        $ms[] = $conf->_("Submission #%d was not accepted for publication.", $paperId);
530    if (isset($whyNot["decided"]))
531        $ms[] = $conf->_("The review process for submission #%d has completed.", $paperId);
532    if (isset($whyNot["updateSubmitted"]))
533        $ms[] = $conf->_("Submission #%d can no longer be updated.", $paperId);
534    if (isset($whyNot["notUploaded"]))
535        $ms[] = $conf->_("A PDF upload is required to submit.");
536    if (isset($whyNot["reviewNotSubmitted"]))
537        $ms[] = $conf->_("This review is not yet ready for others to see.");
538    if (isset($whyNot["reviewNotComplete"]))
539        $ms[] = $conf->_("Your own review for #%d is not complete, so you can’t view other people’s reviews.", $paperId);
540    if (isset($whyNot["responseNotReady"]))
541        $ms[] = $conf->_("The authors’ response is not yet ready for reviewers to view.");
542    if (isset($whyNot["reviewsOutstanding"])) {
543        $ms[] = $conf->_("You will get access to the reviews once you complete your assigned reviews. If you can’t complete your reviews, please let the organizers know via the “Refuse review” links.");
544        if (!$text_only)
545            $ms[] = $conf->_("<a href=\"%s\">List assigned reviews</a>", hoturl("search", "q=&amp;t=r"));
546    }
547    if (isset($whyNot["reviewNotAssigned"]))
548        $ms[] = $conf->_("You are not assigned to review submission #%d.", $paperId);
549    if (isset($whyNot["deadline"])) {
550        $dname = $whyNot["deadline"];
551        if ($dname[0] == "s")
552            $open_dname = "sub_open";
553        else if ($dname[0] == "p" || $dname[0] == "e")
554            $open_dname = "rev_open";
555        else
556            $open_dname = false;
557        $start = $open_dname ? $conf->setting($open_dname, -1) : 1;
558        $end = $conf->setting($dname, -1);
559        if ($dname == "au_seerev") {
560            if ($conf->au_seerev == Conf::AUSEEREV_UNLESSINCOMPLETE) {
561                $ms[] = $conf->_("You will get access to the reviews for this submission when you have completed your own reviews.");
562                if (!$text_only)
563                    $ms[] = $conf->_("<a href=\"%s\">List your incomplete reviews</a>", hoturl("search", "t=rout&amp;q="));
564            } else
565                $ms[] = $conf->_c("etime", "Action not available.", $dname, $paperId);
566        } else if ($start <= 0 || $start == $end) {
567            $ms[] = $conf->_c("etime", "Action not available.", $open_dname, $paperId);
568        } else if ($start > 0 && $Now < $start) {
569            $ms[] = $conf->_c("etime", "Action not available until %3$s.", $open_dname, $paperId, $conf->printableTime($start, "span"));
570        } else if ($end > 0 && $Now > $end) {
571            $ms[] = $conf->_c("etime", "Deadline passed.", $dname, $paperId, $conf->printableTime($end, "span"));
572        } else
573            $ms[] = $conf->_c("etime", "Action not available.", "", $paperId);
574    }
575    if (isset($whyNot["override"]))
576        $ms[] = $conf->_("“Override deadlines” can override this restriction.");
577    if (isset($whyNot["blindSubmission"]))
578        $ms[] = $conf->_("Submission to this conference is blind.");
579    if (isset($whyNot["author"]))
580        $ms[] = $conf->_("You aren’t a contact for #%d.", $paperId);
581    if (isset($whyNot["conflict"]))
582        $ms[] = $conf->_("You have a conflict with #%d.", $paperId);
583    if (isset($whyNot["externalReviewer"]))
584        $ms[] = $conf->_("External reviewers cannot view other reviews.");
585    if (isset($whyNot["differentReviewer"]))
586        $ms[] = $conf->_("You didn’t write this review, so you can’t change it.");
587    if (isset($whyNot["unacceptableReviewer"]))
588        $ms[] = $conf->_("That user can’t be assigned to review #%d.", $paperId);
589    if (isset($whyNot["clickthrough"]))
590        $ms[] = $conf->_("You can’t do that until you agree to the terms.");
591    if (isset($whyNot["otherTwiddleTag"]))
592        $ms[] = $conf->_("Tag “#%s” doesn’t belong to you.", $quote($whyNot["tag"]));
593    if (isset($whyNot["chairTag"]))
594        $ms[] = $conf->_("Tag “#%s” can only be changed by administrators.", $quote($whyNot["tag"]));
595    if (isset($whyNot["voteTag"]))
596        $ms[] = $conf->_("The voting tag “#%s” shouldn’t be changed directly. To vote for this paper, change the “#~%1\$s” tag.", $quote($whyNot["tag"]));
597    if (isset($whyNot["voteTagNegative"]))
598        $ms[] = $conf->_("Negative votes aren’t allowed.");
599    if (isset($whyNot["autosearchTag"]))
600        $ms[] = $conf->_("Tag “#%s” cannot be changed since the system sets it automatically.", $quote($whyNot["tag"]));
601    if (empty($ms) && isset($whyNot["fail"]))
602        $ms[] = $conf->_c("eperm", "Permission error.", "unknown", $paperId);
603    // finish it off
604    if (isset($whyNot["forceShow"]) && !$text_only)
605        $ms[] = $conf->_("<a class=\"nw\" href=\"%s\">Override conflict</a>", selfHref(array("forceShow" => 1)));
606    if (!empty($ms) && isset($whyNot["listViewable"]) && !$text_only)
607        $ms[] = $conf->_("<a href=\"%s\">List the submissions you can view</a>", hoturl("search", "q="));
608    return join(" ", $ms);
609}
610
611function actionBar($mode = null, $qreq = null) {
612    global $Me, $Conf;
613    $forceShow = ($Me->is_admin_force() ? "&amp;forceShow=1" : "");
614
615    $paperArg = "p=*";
616    $xmode = array();
617    $listtype = "p";
618
619    $goBase = "paper";
620    if ($mode == "assign")
621        $goBase = "assign";
622    else if ($mode == "re")
623        $goBase = "review";
624    else if ($mode == "account") {
625        $listtype = "u";
626        if ($Me->privChair) {
627            $goBase = "profile";
628            $xmode["search"] = 1;
629        }
630    } else if ($qreq && ($qreq->m || $qreq->mode))
631        $xmode["m"] = $qreq->m ? : $qreq->mode;
632
633    $x = '<table class="vbar"><tr>';
634
635    // quicklinks
636    if (($list = $Conf->active_list())) {
637        $x .= '<td class="vbar quicklinks">';
638        if (($prev = $list->neighbor_id(-1)) !== false)
639            $x .= _one_quicklink($prev, $goBase, $xmode, $listtype, true) . " ";
640        if ($list->description) {
641            $url = $list->full_site_relative_url();
642            if ($url)
643                $x .= '<a id="quicklink_list" class="x" href="' . htmlspecialchars(Navigation::siteurl() . $url) . "\">" . $list->description . "</a>";
644            else
645                $x .= '<span id="quicklink_list">' . $list->description . '</span>';
646        }
647        if (($next = $list->neighbor_id(1)) !== false)
648            $x .= " " . _one_quicklink($next, $goBase, $xmode, $listtype, false);
649        $x .= '</td>';
650
651        if ($Me->privChair && $listtype == "p")
652            $x .= "  <td id=\"trackerconnect\" class=\"vbar\"><a id=\"trackerconnectbtn\" class=\"ui tracker-ui start tbtn need-tooltip\" href=\"\" data-tooltip=\"Start meeting tracker\">&#9759;</a><td>\n";
653    }
654
655    return $x . '<td class="vbar gopaper">' . goPaperForm($goBase, $xmode) . "</td></tr></table>";
656}
657
658function parseReviewOrdinal($text) {
659    $text = strtoupper($text);
660    if (ctype_alpha($text)) {
661        if (strlen($text) == 1)
662            return ord($text) - 64;
663        else if (strlen($text) == 2)
664            return (ord($text[0]) - 64) * 26 + ord($text[1]) - 64;
665    }
666    return -1;
667}
668
669function unparseReviewOrdinal($ord) {
670    if (!$ord)
671        return ".";
672    else if (is_object($ord)) {
673        if ($ord->reviewOrdinal)
674            return $ord->paperId . unparseReviewOrdinal($ord->reviewOrdinal);
675        else
676            return $ord->reviewId;
677    } else if ($ord <= 26)
678        return chr($ord + 64);
679    else
680        return chr(intval(($ord - 1) / 26) + 64) . chr((($ord - 1) % 26) + 65);
681}
682
683function downloadText($text, $filename, $inline = false) {
684    global $Conf;
685    $csvg = new CsvGenerator(CsvGenerator::TYPE_TAB);
686    $csvg->set_filename($Conf->download_prefix . $filename . $csvg->extension());
687    $csvg->set_inline($inline);
688    $csvg->download_headers();
689    if ($text !== false) {
690        $csvg->add_string($text);
691        $csvg->download();
692        exit;
693    }
694}
695
696function unparse_expertise($expertise) {
697    if ($expertise === null)
698        return "";
699    else
700        return $expertise > 0 ? "X" : ($expertise == 0 ? "Y" : "Z");
701}
702
703function unparse_preference($preference, $expertise = null) {
704    if (is_object($preference))
705        list($preference, $expertise) = array(get($preference, "reviewerPreference"),
706                                              get($preference, "reviewerExpertise"));
707    else if (is_array($preference))
708        list($preference, $expertise) = $preference;
709    if ($preference === null || $preference === false)
710        $preference = "0";
711    return $preference . unparse_expertise($expertise);
712}
713
714function unparse_preference_span($preference, $always = false) {
715    if (is_object($preference))
716        $preference = array(get($preference, "reviewerPreference"),
717                            get($preference, "reviewerExpertise"),
718                            get($preference, "topicInterestScore"));
719    else if (!is_array($preference))
720        $preference = array($preference, null, null);
721    $pv = (int) get($preference, 0);
722    $ev = get($preference, 1);
723    $tv = (int) get($preference, 2);
724    $type = 1;
725    if ($pv < 0 || (!$pv && $tv < 0))
726        $type = -1;
727    $t = "";
728    if ($pv || $ev !== null || $always)
729        $t .= "P" . decorateNumber($pv) . unparse_expertise($ev);
730    if ($tv && !$pv)
731        $t .= ($t ? " " : "") . "T" . decorateNumber($tv);
732    if ($t !== "")
733        $t = " <span class=\"asspref$type\">$t</span>";
734    return $t;
735}
736
737function decisionSelector($curOutcome = 0, $id = null, $extra = "") {
738    global $Conf;
739    $text = "<select" . ($id === null ? "" : " id='$id'") . " name='decision'$extra>\n";
740    $decs = $Conf->decision_map();
741    if (!isset($decs[$curOutcome]))
742        $curOutcome = null;
743    $outcomes = array_keys($decs);
744    if ($curOutcome === null)
745        $text .= "    <option value='' selected='selected'>Set decision...</option>\n";
746    foreach ($decs as $dnum => $dname)
747        $text .= "    <option value='$dnum'" . ($curOutcome == $dnum && $curOutcome !== null ? " selected='selected'" : "") . ">" . htmlspecialchars($dname) . "</option>\n";
748    return $text . "  </select>";
749}
750
751function review_type_icon($revtype, $unfinished = null, $title = null) {
752    // see also script.js:review_form
753    static $revtypemap = array(-3 => array("&minus;", "Refused"),
754                               -2 => array("A", "Author"),
755                               -1 => array("C", "Conflict"),
756                               1 => array("E", "External review"),
757                               2 => array("P", "PC review"),
758                               3 => array("2", "Secondary review"),
759                               4 => array("1", "Primary review"),
760                               5 => array("M", "Metareview"));
761    if (!$revtype)
762        return '<span class="rt0"></span>';
763    $x = $revtypemap[$revtype];
764    return '<span class="rto rt' . $revtype
765        . ($revtype > 0 && $unfinished ? "n" : "")
766        . '" title="' . ($title ? $title : $revtypemap[$revtype][1])
767        . '"><span class="rti">' . $revtypemap[$revtype][0] . '</span></span>';
768}
769
770function review_lead_icon() {
771    return '<span class="rto rtlead" title="Lead"><span class="rti">L</span></span>';
772}
773
774function review_shepherd_icon() {
775    return '<span class="rto rtshep" title="Shepherd"><span class="rti">S</span></span>';
776}
777
778function displayOptionsSet($sessionvar, $var = null, $val = null) {
779    global $Conf;
780    if (($x = $Conf->session($sessionvar)) !== null)
781        /* use session value */;
782    else if ($sessionvar === "pldisplay")
783        $x = $Conf->setting_data("pldisplay_default", "");
784    else
785        $x = "";
786    if ($x == null || strpos($x, " ") === false) {
787        if ($sessionvar == "pldisplay")
788            $x = $Conf->review_form()->default_display();
789        else if ($sessionvar == "uldisplay")
790            $x = " tags overAllMerit ";
791        else
792            $x = " ";
793    }
794
795    // set $var to $val in list
796    if ($var) {
797        $x = str_replace(" $var ", " ", $x);
798        if ($val)
799            $x .= "$var ";
800        if (($sessionvar === "pldisplay" || $sessionvar === "pfdisplay")
801            && ($f = $Conf->find_review_field($var))
802            && $var !== $f->id)
803            $x = str_replace(" {$f->id} ", " ", $x);
804    }
805
806    // store list in $_SESSION
807    $Conf->save_session($sessionvar, $x);
808    return $x;
809}
810
811
812if (!function_exists("random_bytes")) {
813    function random_bytes($length) {
814        $x = @file_get_contents("/dev/urandom", false, null, 0, $length);
815        if (($x === false || $x === "")
816            && function_exists("openssl_random_pseudo_bytes")) {
817            $x = openssl_random_pseudo_bytes($length, $strong);
818            $x = $strong ? $x : false;
819        }
820        return $x === "" ? false : $x;
821    }
822}
823
824function hotcrp_random_password($length = 14) {
825    $bytes = random_bytes($length + 10);
826    if ($bytes === false) {
827        $bytes = "";
828        while (strlen($bytes) < $length)
829            $bytes .= sha1(opt("conferenceKey") . pack("V", mt_rand()));
830    }
831
832    $l = "a e i o u y a e i o u y a e i o u y a e i o u y a e i o u y b c d g h j k l m n p r s t u v w trcrbrfrthdrchphwrstspswprslcl2 3 4 5 6 7 8 9 - @ _ + = ";
833    $pw = "";
834    $nvow = 0;
835    for ($i = 0;
836         $i < strlen($bytes) &&
837             strlen($pw) < $length + max(0, ($nvow - 3) / 3);
838         ++$i) {
839        $x = ord($bytes[$i]) % (strlen($l) / 2);
840        if ($x < 30)
841            ++$nvow;
842        $pw .= rtrim(substr($l, 2 * $x, 2));
843    }
844    return $pw;
845}
846
847
848function encode_token($x, $format = "") {
849    $s = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
850    $t = "";
851    if (is_int($x))
852        $format = "V";
853    if ($format)
854        $x = pack($format, $x);
855    $i = 0;
856    $have = 0;
857    $n = 0;
858    while ($have > 0 || $i < strlen($x)) {
859        if ($have < 5 && $i < strlen($x)) {
860            $n += ord($x[$i]) << $have;
861            $have += 8;
862            ++$i;
863        }
864        $t .= $s[$n & 31];
865        $n >>= 5;
866        $have -= 5;
867    }
868    if ($format == "V")
869        return preg_replace('/(\AA|[^A])A*\z/', '$1', $t);
870    else
871        return $t;
872}
873
874function decode_token($x, $format = "") {
875    $map = "//HIJKLMNO///////01234567/89:;</=>?@ABCDEFG";
876    $t = "";
877    $n = $have = 0;
878    $x = trim(strtoupper($x));
879    for ($i = 0; $i < strlen($x); ++$i) {
880        $o = ord($x[$i]);
881        if ($o >= 48 && $o <= 90 && ($out = ord($map[$o - 48])) >= 48)
882            $o = $out - 48;
883        else if ($o == 46 /*.*/ || $o == 34 /*"*/)
884            continue;
885        else
886            return false;
887        $n += $o << $have;
888        $have += 5;
889        while ($have >= 8 || ($n && $i == strlen($x) - 1)) {
890            $t .= chr($n & 255);
891            $n >>= 8;
892            $have -= 8;
893        }
894    }
895    if ($format == "V") {
896        $x = unpack("Vx", $t . "\x00\x00\x00\x00\x00\x00\x00");
897        return $x["x"];
898    } else if ($format)
899        return unpack($format, $t);
900    else
901        return $t;
902}
903