├── images
├── _.gif
├── edit.png
├── pdf.png
├── pdff.png
├── pdfx.png
├── txt.png
├── bendurt.png
├── buzzer.mp3
├── check.png
├── cross.png
├── edit48.png
├── generic.png
├── info45.png
├── pdf24.png
├── pdff24.png
├── pdffx.png
├── pdffx24.png
├── pdfx24.png
├── txt24.png
├── view48.png
├── viewas.png
├── assign48.png
├── comment48.png
├── extagsset.png
├── extracker.png
├── generic24.png
├── genericf.png
├── review24.png
├── review48.png
├── exassignone.png
├── extagcolors.png
├── extagseditkw.png
├── extagssearch.png
├── genericf24.png
├── postscript.png
├── postscript24.png
├── postscriptf.png
├── stophand45.png
├── exsearchaction.png
├── extagvotehover.png
├── pageresultsex.png
├── postscriptf24.png
├── quicksearchex.png
└── .htaccess
├── etc
├── sample.pdf
├── .htaccess
├── capabilityhandlers.json
├── reviewfieldtypes.json
└── autoassigners.json
├── test
├── sample50pg.pdf
├── ldap
│ ├── lldap_data
│ │ └── users.db
│ ├── compose.yaml
│ └── README.md
├── .htaccess
├── test04.php
├── test03.php
├── test07.php
├── test09.php
├── test01.php
├── test05.php
├── oauth
│ ├── composer.json
│ ├── db.json
│ ├── README.md
│ └── private.key
├── test06.php
├── t_invariants.php
├── cdb-options.php
├── check.sh
├── test02.php
├── test08.php
├── review0.txt
├── t_events.php
├── options.php
├── t_userapi.php
├── t_ht.php
├── t_mailer.php
├── t_s3.php
├── t_xtcheck.php
└── review1A.txt
├── .gitattributes
├── scripts
└── .htaccess
├── api.php
├── doc.php
├── log.php
├── stylesheets
└── .htaccess
├── assign.php
├── buzzer.php
├── graph.php
├── help.php
├── mail.php
├── oauth.php
├── paper.php
├── review.php
├── search.php
├── signin.php
├── users.php
├── offline.php
├── profile.php
├── settings.php
├── signout.php
├── authorize.php
├── autoassign.php
├── bulkassign.php
├── cacheable.php
├── deadlines.php
├── manageemail.php
├── newaccount.php
├── reviewprefs.php
├── scorechart.php
├── checkupdates.php
├── manualassign.php
├── mergeaccounts.php
├── resetpassword.php
├── conf
└── .htaccess
├── conflictassign.php
├── forgotpassword.php
├── lib
├── .htaccess
├── backupdb.sh
├── restoredb.sh
├── jsonexception.php
├── subprocess.php
├── collatorshim.php
├── memoryqsession.php
├── gmpshim.php
├── filer.php
└── phpqsession.php
├── src
├── .htaccess
├── settings
│ ├── s_shepherds.php
│ ├── s_users.php
│ ├── s_rf.php
│ ├── s_preference.php
│ ├── s_sf.php
│ ├── s_basics.php
│ ├── s_finalversions.php
│ └── s_messages.php
├── formulas
│ ├── f_now.php
│ ├── f_pdfsize.php
│ ├── f_timefield.php
│ ├── f_decision.php
│ ├── f_submittedat.php
│ ├── f_reviewer.php
│ ├── f_reviewwordcount.php
│ ├── f_optionvalue.php
│ ├── f_pagecount.php
│ ├── f_realnumberoption.php
│ ├── f_conflict.php
│ ├── f_optionpresent.php
│ ├── f_reviewround.php
│ ├── f_revtype.php
│ ├── f_topicscore.php
│ ├── f_pref.php
│ ├── f_topic.php
│ └── f_reviewermatch.php
├── search
│ ├── st_topic.php
│ ├── st_editfinal.php
│ ├── st_optiontext.php
│ ├── st_documentname.php
│ ├── st_optionpresent.php
│ ├── st_cmtafter.php
│ ├── st_badge.php
│ ├── st_realnumberoption.php
│ ├── st_perm.php
│ ├── st_optionvalue.php
│ ├── st_paperpc.php
│ ├── st_reconflict.php
│ ├── st_paperstatus.php
│ ├── st_documentcount.php
│ ├── st_sclass.php
│ ├── st_decision.php
│ ├── st_phase.php
│ └── st_color.php
├── pages
│ └── p_graph_procrastination.php
├── capabilities
│ └── cap_manageemail.php
├── listactions
│ ├── la_getpcassignments.php
│ ├── la_get.php
│ ├── la_getlead.php
│ ├── la_decide.php
│ ├── la_mail.php
│ └── la_getjsonrqc.php
├── papercolumns
│ ├── pc_timestamp.php
│ ├── pc_paperidorder.php
│ ├── pc_desirability.php
│ ├── pc_topics.php
│ ├── pc_commenters.php
│ ├── pc_pcconflicts.php
│ ├── pc_administrator.php
│ ├── pc_lead.php
│ ├── pc_preferencelist.php
│ ├── pc_reviewdelegation.php
│ └── pc_shepherd.php
├── notificationinfo.php
├── api
│ ├── api_follow.php
│ ├── api_graphdata.php
│ ├── api_job.php
│ ├── api_decision.php
│ ├── api_events.php
│ ├── api_formatcheck.php
│ ├── api_assign.php
│ └── api_paperpc.php
├── assigners
│ └── a_error.php
├── tagmessagereport.php
├── reviewfields
│ └── rf_text.php
├── help
│ ├── h_scoresort.php
│ └── h_votetags.php
├── options
│ ├── o_checkboxes.php
│ ├── o_title.php
│ └── o_topics.php
├── searchoperator.php
├── autoassigners
│ ├── aa_prefconflict.php
│ └── aa_clear.php
├── apihelpers.php
├── fieldchangeset.php
├── textformat.php
└── logentryfilter.php
├── batch
├── .htaccess
├── fileinfo.php
└── cli
│ └── cli_test.php
├── .phan
└── stubs
│ └── polyfills.phan_php
├── devel
├── d3
│ ├── index.js
│ ├── package.json
│ └── rollup.config.mjs
├── hotcrp.vim
├── apidoc
│ ├── subadmin.md
│ ├── redocly.yaml
│ ├── tags.md
│ └── search.md
├── hotcrp-daemonize.c
└── manual
│ ├── index.md
│ └── css.md
├── .gitignore
├── .user.ini
├── .eslintrc.json
├── package.json
├── LICENSE
├── .htaccess
└── .github
└── workflows
└── tests.yml
/images/_.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/_.gif
--------------------------------------------------------------------------------
/etc/sample.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/etc/sample.pdf
--------------------------------------------------------------------------------
/images/edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/edit.png
--------------------------------------------------------------------------------
/images/pdf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/pdf.png
--------------------------------------------------------------------------------
/images/pdff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/pdff.png
--------------------------------------------------------------------------------
/images/pdfx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/pdfx.png
--------------------------------------------------------------------------------
/images/txt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/txt.png
--------------------------------------------------------------------------------
/images/bendurt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/bendurt.png
--------------------------------------------------------------------------------
/images/buzzer.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/buzzer.mp3
--------------------------------------------------------------------------------
/images/check.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/check.png
--------------------------------------------------------------------------------
/images/cross.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/cross.png
--------------------------------------------------------------------------------
/images/edit48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/edit48.png
--------------------------------------------------------------------------------
/images/generic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/generic.png
--------------------------------------------------------------------------------
/images/info45.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/info45.png
--------------------------------------------------------------------------------
/images/pdf24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/pdf24.png
--------------------------------------------------------------------------------
/images/pdff24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/pdff24.png
--------------------------------------------------------------------------------
/images/pdffx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/pdffx.png
--------------------------------------------------------------------------------
/images/pdffx24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/pdffx24.png
--------------------------------------------------------------------------------
/images/pdfx24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/pdfx24.png
--------------------------------------------------------------------------------
/images/txt24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/txt24.png
--------------------------------------------------------------------------------
/images/view48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/view48.png
--------------------------------------------------------------------------------
/images/viewas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/viewas.png
--------------------------------------------------------------------------------
/images/assign48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/assign48.png
--------------------------------------------------------------------------------
/images/comment48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/comment48.png
--------------------------------------------------------------------------------
/images/extagsset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/extagsset.png
--------------------------------------------------------------------------------
/images/extracker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/extracker.png
--------------------------------------------------------------------------------
/images/generic24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/generic24.png
--------------------------------------------------------------------------------
/images/genericf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/genericf.png
--------------------------------------------------------------------------------
/images/review24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/review24.png
--------------------------------------------------------------------------------
/images/review48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/review48.png
--------------------------------------------------------------------------------
/test/sample50pg.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/test/sample50pg.pdf
--------------------------------------------------------------------------------
/images/exassignone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/exassignone.png
--------------------------------------------------------------------------------
/images/extagcolors.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/extagcolors.png
--------------------------------------------------------------------------------
/images/extagseditkw.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/extagseditkw.png
--------------------------------------------------------------------------------
/images/extagssearch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/extagssearch.png
--------------------------------------------------------------------------------
/images/genericf24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/genericf24.png
--------------------------------------------------------------------------------
/images/postscript.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/postscript.png
--------------------------------------------------------------------------------
/images/postscript24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/postscript24.png
--------------------------------------------------------------------------------
/images/postscriptf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/postscriptf.png
--------------------------------------------------------------------------------
/images/stophand45.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/stophand45.png
--------------------------------------------------------------------------------
/images/exsearchaction.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/exsearchaction.png
--------------------------------------------------------------------------------
/images/extagvotehover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/extagvotehover.png
--------------------------------------------------------------------------------
/images/pageresultsex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/pageresultsex.png
--------------------------------------------------------------------------------
/images/postscriptf24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/postscriptf24.png
--------------------------------------------------------------------------------
/images/quicksearchex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/images/quicksearchex.png
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | /scripts/*.min.js -diff
2 | /scripts/*.map -diff
3 | /test/ldap/lldap_data/*.db -diff
4 |
--------------------------------------------------------------------------------
/test/ldap/lldap_data/users.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Gobidev/hotcrp/master/test/ldap/lldap_data/users.db
--------------------------------------------------------------------------------
/images/.htaccess:
--------------------------------------------------------------------------------
1 |
2 | ExpiresActive On
3 | ExpiresDefault "access plus 10 years"
4 |
5 | FileETag none
6 |
--------------------------------------------------------------------------------
/scripts/.htaccess:
--------------------------------------------------------------------------------
1 |
2 | ExpiresActive On
3 | ExpiresDefault "access plus 10 years"
4 |
5 | FileETag none
6 |
--------------------------------------------------------------------------------
/api.php:
--------------------------------------------------------------------------------
1 |
2 | ExpiresActive On
3 | ExpiresDefault "access plus 10 years"
4 |
5 | FileETag none
6 |
--------------------------------------------------------------------------------
/assign.php:
--------------------------------------------------------------------------------
1 |
2 | Require all denied
3 |
4 |
5 | Order deny,allow
6 | Deny from all
7 |
8 |
--------------------------------------------------------------------------------
/conflictassign.php:
--------------------------------------------------------------------------------
1 |
2 | Require all denied
3 |
4 |
5 | Order deny,allow
6 | Deny from all
7 |
8 |
--------------------------------------------------------------------------------
/forgotpassword.php:
--------------------------------------------------------------------------------
1 |
2 | Require all denied
3 |
4 |
5 | Order deny,allow
6 | Deny from all
7 |
8 |
--------------------------------------------------------------------------------
/src/.htaccess:
--------------------------------------------------------------------------------
1 |
2 | Require all denied
3 |
4 |
5 | Order deny,allow
6 | Deny from all
7 |
8 |
--------------------------------------------------------------------------------
/test/.htaccess:
--------------------------------------------------------------------------------
1 |
2 | Require all denied
3 |
4 |
5 | Order deny,allow
6 | Deny from all
7 |
8 |
--------------------------------------------------------------------------------
/batch/.htaccess:
--------------------------------------------------------------------------------
1 |
2 | Require all denied
3 |
4 |
5 | Order deny,allow
6 | Deny from all
7 |
8 |
--------------------------------------------------------------------------------
/.phan/stubs/polyfills.phan_php:
--------------------------------------------------------------------------------
1 | /dev/null; then LIBDIR=./
7 | else LIBDIR=`echo "$0" | sed 's,^\(.*/\)[^/]*$,\1,'`; fi
8 | exec php ${LIBDIR}../batch/backupdb.php "$@"
9 |
--------------------------------------------------------------------------------
/lib/restoredb.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 | ## restoredb.sh -- HotCRP script to restore from backup
3 | ## Copyright (c) 2006-2022 Eddie Kohler; see LICENSE.
4 |
5 | # Now relies on PHP.
6 | if ! expr "$0" : '.*[/]' >/dev/null; then LIBDIR=./
7 | else LIBDIR=`echo "$0" | sed 's,^\(.*/\)[^/]*$,\1,'`; fi
8 | exec php ${LIBDIR}../batch/backupdb.php -r "$@"
9 |
--------------------------------------------------------------------------------
/devel/hotcrp.vim:
--------------------------------------------------------------------------------
1 | " Vim syntax file
2 | " Language: HotCRP Offline Review Forms
3 |
4 | if exists("b:current_syntax")
5 | finish
6 | endif
7 |
8 |
9 | " Matches
10 | syn match hotCrpSec "^==+==.*$"
11 | syn match hotCrpSubSec "^==-==.*$"
12 |
13 | let b:current_syntax = "hotcrp"
14 |
15 | hi def link hotCrpSec Comment
16 | hi def link hotCrpSubSec Constant
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | .DS_Store
3 | /Code/options.inc
4 | /composer.json
5 | /composer.lock
6 | /conf
7 | /devel/d3/d3.js
8 | /devel/d3/d3-hotcrp.min.js
9 | /devel/d3/node_modules
10 | /devel/hotcrp-daemonize
11 | /docs
12 | /filestore
13 | /logs
14 | /node_modules
15 | /package-lock.json
16 | /test/oauth/vendor
17 | /test/oauth/auths.json
18 | /test/oauth/composer.lock
19 | /vendor
20 |
--------------------------------------------------------------------------------
/src/settings/s_shepherds.php:
--------------------------------------------------------------------------------
1 | decisions page
3 | // Copyright (c) 2006-2022 Eddie Kohler; see LICENSE.
4 |
5 | class Shepherds_SettingParser extends SettingParser {
6 | static function print_visibility(SettingValues $sv) {
7 | $sv->print_checkbox("shepherd_visibility", "Show shepherd names to authors");
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/test/test05.php:
--------------------------------------------------------------------------------
1 | = upload_max_filesize.
5 | ;upload_max_filesize = 15M
6 | ;post_max_size = 20M
7 |
8 | ; Some pages involve a lot of post variables.
9 | ;max_input_vars = 4096
10 |
11 | ; A large memory_limit helps when sending very large zipped files.
12 | memory_limit = 128M
13 |
--------------------------------------------------------------------------------
/test/test06.php:
--------------------------------------------------------------------------------
1 | set_format($ff->kwdef->is_time ? Fexpr::FTIME : Fexpr::FDATE);
8 | }
9 | function compile(FormulaCompiler $state) {
10 | return $state->_add_now();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test/ldap/compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | lldap:
3 | image: lldap/lldap:stable
4 | ports:
5 | # LDAP
6 | - "17169:3890"
7 | # Web front end
8 | - "17170:17170"
9 | volumes:
10 | - "./lldap_data:/data"
11 | environment:
12 | - LLDAP_JWT_SECRET=ybke34;H~a0sb6iM9RTZ&I|~l,9qsF21
13 | - LLDAP_KEY_SEED=Lg57%.J*EG42YwYq}_}g@cFEoJ^E_F=r
14 | - LLDAP_KEY_FILE=
15 | - LLDAP_LDAP_USER_PASS=aequee0Oe1ee1A
16 |
--------------------------------------------------------------------------------
/src/formulas/f_pdfsize.php:
--------------------------------------------------------------------------------
1 | queryOptions["pdfSize"] = true;
8 | $prow = $state->_prow();
9 | return "(\$contact->can_view_pdf({$prow}) ? (int) {$prow}->primary_document_size() : null)";
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/test/t_invariants.php:
--------------------------------------------------------------------------------
1 | conf = $conf;
12 | }
13 |
14 | function test_invariants() {
15 | xassert(ConfInvariants::test_all($this->conf));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/search/st_topic.php:
--------------------------------------------------------------------------------
1 | conf->option_by_id(PaperOption::TOPICSID);
8 | $sword->set_compar_word($sword->word);
9 | return Option_SearchTerm::parse_option($sword, $srch, $opt);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "jquery": true
5 | },
6 | "extends": "eslint:recommended",
7 | "parserOptions": {
8 | "ecmaVersion": "latest"
9 | },
10 | "globals": {
11 | "Set": "readonly",
12 | "WeakMap": "readonly",
13 | "Promise": "readonly"
14 | },
15 | "rules": {
16 | "no-empty": 0,
17 | "no-constant-condition": ["error", {"checkLoops": false}]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/devel/d3/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "d3-hotcrp",
3 | "version": "0.0.8",
4 | "scripts": {
5 | "prepare": "rollup -c"
6 | },
7 | "devDependencies": {
8 | "@rollup/plugin-json": "6",
9 | "@rollup/plugin-node-resolve": "15",
10 | "@rollup/plugin-terser": "^0.4.0",
11 | "rollup": "3",
12 | "d3-array": "3",
13 | "d3-axis": "3",
14 | "d3-quadtree": "3",
15 | "d3-scale": "4",
16 | "d3-selection": "3",
17 | "d3-shape": "3"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "eslint": "^8.21.0"
4 | },
5 | "name": "hotcrp",
6 | "description": "HotCRP Conference Review Software",
7 | "version": "3.0.0",
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/kohler/hotcrp.git"
11 | },
12 | "author": "Eddie Kohler",
13 | "bugs": {
14 | "url": "https://github.com/kohler/hotcrp/issues"
15 | },
16 | "homepage": "https://github.com/kohler/hotcrp#readme"
17 | }
18 |
--------------------------------------------------------------------------------
/lib/jsonexception.php:
--------------------------------------------------------------------------------
1 | field = $field;
10 | }
11 | function compile(FormulaCompiler $state) {
12 | return "((int) " . $state->_prow() . "->" . $this->field . ")";
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/formulas/f_decision.php:
--------------------------------------------------------------------------------
1 | set_format(Fexpr::FDECISION);
8 | }
9 | function viewable_by(Contact $user) {
10 | return $user->can_view_some_decision();
11 | }
12 | function compile(FormulaCompiler $state) {
13 | return $state->_add_decision();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/test/cdb-options.php:
--------------------------------------------------------------------------------
1 | set_format($ff->kwdef->is_time ? Fexpr::FTIME : Fexpr::FDATE);
9 | }
10 | function compile(FormulaCompiler $state) {
11 | return '(' . $state->_prow() . '->submitted_at() ? : null)';
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/pages/p_graph_procrastination.php:
--------------------------------------------------------------------------------
1 | json()),
12 | ") });\n";
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/settings/s_users.php:
--------------------------------------------------------------------------------
1 | users page
3 | // Copyright (c) 2006-2022 Eddie Kohler; see LICENSE.
4 |
5 | class Users_SettingRenderer {
6 | static function print(SettingValues $sv) {
7 | echo '
"new", "role" => "pc"]), '" class="btn">Create PC accounts · ',
8 | "Select a user’s name to edit a profile.
\n";
9 | $pl = new ContactList($sv->user, false);
10 | echo $pl->table_html("pcadminx", $sv->conf->hoturl("users", "t=pcadmin"));
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/capabilities/cap_manageemail.php:
--------------------------------------------------------------------------------
1 | conf, TokenInfo::MANAGEEMAIL))
10 | ->set_user_id($viewer->contactId)
11 | ->set_invalid_after(3600 /* 1 hour */)
12 | ->set_expires_after(7200 /* 2 hours */)
13 | ->set_token_pattern("hcme_[16]");
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/test/oauth/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "clients": [
3 | {
4 | "client_id": "hotcrp-oauth-test",
5 | "client_secret": "Dudfield",
6 | "name": "HotCRP OAuth Test",
7 | "redirect_uri": "http://localhost:8080/testconf/oauth"
8 | }
9 | ],
10 | "users": [
11 | {
12 | "email": "fran@hotcrp-oauth.org",
13 | "email_verified": true,
14 | "given_name": "Fran",
15 | "family_name": "Framer",
16 | "name": "Fran Framer"
17 | },
18 | {
19 | "email": "paula@hotcrp-oauth.org",
20 | "email_verified": true,
21 | "given_name": "Paula",
22 | "family_name": "Books",
23 | "name": "Paula Books"
24 | }
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/src/listactions/la_getpcassignments.php:
--------------------------------------------------------------------------------
1 | is_manager();
8 | }
9 | function run(Contact $user, Qrequest $qreq, SearchSelection $ssel) {
10 | list($header, $items) = ListAction::pcassignments_csv_data($user, $ssel->selection());
11 | return $user->conf->make_csvg("pcassignments")->select($header)->append($items);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/test/check.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 |
3 | all=false
4 | if [ "$1" = "-a" -o "$1" = "--all" ]; then all=true; shift; fi
5 |
6 | a=0
7 | runcheck () {
8 | echo $1:
9 | php -d error_reporting=E_ALL "$@"
10 | z=$?
11 | echo
12 | if [ "$a" = 0 ]; then a=$z; fi
13 | }
14 |
15 | runcheck test/test01.php "$@"
16 | runcheck test/test02.php "$@"
17 | runcheck test/test03.php "$@"
18 | runcheck test/test04.php "$@"
19 | runcheck test/test05.php "$@"
20 | runcheck test/test06.php "$@"
21 | runcheck test/test07.php "$@"
22 | if $all; then
23 | runcheck test/test08.php "$@"
24 | fi
25 | runcheck test/test09.php "$@"
26 |
27 | exit $a
28 |
--------------------------------------------------------------------------------
/devel/d3/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | import json from "@rollup/plugin-json";
2 | import nodeResolve from "@rollup/plugin-node-resolve";
3 | import terser from "@rollup/plugin-terser";
4 |
5 | export default [{
6 | input: "index.js",
7 | plugins: [
8 | nodeResolve(),
9 | json(),
10 | terser({
11 | mangle: {
12 | reserved: [
13 | "InternMap",
14 | "InternSet"
15 | ]
16 | }
17 | })
18 | ],
19 | output: {
20 | file: "d3-hotcrp.min.js",
21 | name: "d3",
22 | format: "umd",
23 | indent: false,
24 | extend: true
25 | }
26 | }];
27 |
--------------------------------------------------------------------------------
/test/test02.php:
--------------------------------------------------------------------------------
1 | getfn,
12 | ["class" => "want-focus js-submit-action-info-get w-small-selector ignore-diff"])
13 | . $pl->action_submit("get", ["formmethod" => "get", "class" => "can-submit-all"]);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/formulas/f_reviewer.php:
--------------------------------------------------------------------------------
1 | set_format(Fexpr::FREVIEWER);
9 | }
10 | function inferred_index() {
11 | return Fexpr::IDX_REVIEW;
12 | }
13 | function viewable_by(Contact $user) {
14 | return $user->can_view_some_review_identity();
15 | }
16 | function compile(FormulaCompiler $state) {
17 | $state->queryOptions["reviewSignatures"] = true;
18 | return $state->review_identity_loop_cid();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/devel/apidoc/subadmin.md:
--------------------------------------------------------------------------------
1 | # post /assign
2 |
3 | > Assignments
4 |
5 |
6 | # get /{p}/decision
7 |
8 | > Retrieve submission decision
9 |
10 |
11 | # post /{p}/decision
12 |
13 | > Change submission decision
14 |
15 |
16 | # get /{p}/lead
17 |
18 | > Retrieve submission discussion lead
19 |
20 |
21 | # post /{p}/lead
22 |
23 | > Change submission discussion lead
24 |
25 |
26 | # get /{p}/manager
27 |
28 | > Retrieve submission administrator
29 |
30 |
31 | # post /{p}/manager
32 |
33 | > Change submission administrator
34 |
35 |
36 | # post /{p}/reviewround
37 |
38 | > Change review round
39 |
40 |
41 | # get /{p}/shepherd
42 |
43 | > Retrieve submission shepherd
44 |
45 |
46 | # post /{p}/shepherd
47 |
48 | > Change submission shepherd
49 |
--------------------------------------------------------------------------------
/devel/hotcrp-daemonize.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | int main(int argc, char** argv) {
9 | DIR* dir = opendir("/dev/fd");
10 | struct dirent* de;
11 | while (dir && (de = readdir(dir))) {
12 | if (!isdigit((unsigned char) de->d_name[0])) {
13 | continue;
14 | }
15 | char* ends;
16 | unsigned long u = strtoul(de->d_name, &ends, 10);
17 | if (*ends == 0 && (int) u != dirfd(dir) && (int) u > 2) {
18 | close((int) u);
19 | }
20 | }
21 | closedir(dir);
22 | if (fork() > 0) {
23 | exit(0);
24 | }
25 | setsid();
26 | execvp(argv[1], argv + 1);
27 | exit(127);
28 | }
29 |
--------------------------------------------------------------------------------
/test/test08.php:
--------------------------------------------------------------------------------
1 | timeSubmitted, 0) <=> max($b->timeSubmitted, 0);
11 | }
12 | function content_empty(PaperList $pl, PaperInfo $row) {
13 | return $row->timeSubmitted <= 0;
14 | }
15 | function content(PaperList $pl, PaperInfo $row) {
16 | if ($row->timeSubmitted > 0) {
17 | return $row->conf->unparse_time_log($row->timeSubmitted);
18 | }
19 | return "";
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/devel/apidoc/redocly.yaml:
--------------------------------------------------------------------------------
1 | apis:
2 | core@v1:
3 | root: ../openapi.json
4 | rules:
5 | operation-4xx-response: off
6 | operation-operationId: off
7 | no-empty-servers: off
8 | operation-summary: off
9 | tag-description: off
10 | info-license: off
11 |
12 | extends:
13 | - recommended
14 |
15 | theme:
16 | openapi:
17 | disableSearch: true
18 | expandResponses: 200,201
19 | theme:
20 | sidebar:
21 | backgroundColor: 'ivory'
22 | rightPanel:
23 | textColor: 'ivory'
24 | backgroundColor: '#001036'
25 | typography:
26 | fontFamily: 'sans-serif'
27 | links:
28 | color: '#0090cc'
29 | headings:
30 | fontFamily: 'sans-serif'
31 | code:
32 | fontFamily: 'SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, monospace'
33 |
--------------------------------------------------------------------------------
/test/review0.txt:
--------------------------------------------------------------------------------
1 | ==+== =====================================================================
2 | ==+== Begin Review #0
3 |
4 | ==+== Paper #0
5 |
6 | ==+== Review Readiness
7 | ==-== Enter "Ready" if the review is ready for others to see:
8 |
9 | Ready
10 |
11 | ==+== A. Overall merit
12 | ==-== Choices:
13 | ==-== 1. Reject
14 | ==-== 2. Weak reject
15 | ==-== 3. Weak accept
16 | ==-== No entry
17 | ==-== Enter your choice:
18 |
19 | (Your choice here)
20 |
21 | ==+== B. Reviewer expertise
22 | ==-== Choices:
23 | ==-== 1. No familiarity
24 | ==-== 2. Some familiarity
25 | ==-== 3. Knowledgeable
26 | ==-== 4. Expert
27 | ==-== Enter the number of your choice:
28 |
29 | (Your choice here)
30 |
31 | ==+== C. Paper summary
32 |
33 |
34 |
35 | ==+== D. Comments for authors
36 |
37 | ==+== Scratchpad (for unsaved private notes)
38 |
39 | ==+== End Review
40 |
--------------------------------------------------------------------------------
/test/t_events.php:
--------------------------------------------------------------------------------
1 | conf = $conf;
12 | }
13 |
14 | function test_events() {
15 | $u_mgbaker = $this->conf->checked_user_by_email("mgbaker@cs.stanford.edu");
16 | $evs = new PaperEvents($u_mgbaker);
17 | xassert_gt(count($evs->events(Conf::$now, 10)), 0);
18 |
19 | $u_diot = $this->conf->checked_user_by_email("ojuelegba@gmail.com");
20 | $evs = new PaperEvents($u_diot);
21 | foreach ($evs->events(Conf::$now, 10) as $x) {
22 | error_log(Conf::$now . " " . json_encode($x));
23 | }
24 | xassert_eqq(count($evs->events(Conf::$now, 10)), 0);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/test/oauth/README.md:
--------------------------------------------------------------------------------
1 | HotCRP OAuth test server
2 | ========================
3 |
4 | This server, built using https://github.com/thephpleague/oauth2-server and
5 | https://github.com/Nyholm/psr7, can be used to test HotCRP’s OAuth support.
6 |
7 |
8 | Usage
9 | -----
10 |
11 | 1. Install required libraries with `composer install`
12 |
13 | 2. Run the server with `php -S localhost:19382 oauth-provider.php`
14 |
15 | 3. Configure HotCRP to access the server by setting `$Opt["oAuthProviders"]`
16 | in `conf/options.php`:
17 |
18 | ```php
19 | $Opt["oAuthProviders"][] = [
20 | "name" => "local",
21 | "client_id" => "hotcrp-oauth-test",
22 | "client_secret" => "Dudfield",
23 | "auth_uri" => "http://localhost:19382/auth",
24 | "token_uri" => "http://localhost:19382/token",
25 | "redirect_uri" => "http://localhost:8080/testconf/oauth",
26 | "button_html" => "Sign in with local OAuth"
27 | ];
28 | ```
29 |
--------------------------------------------------------------------------------
/test/options.php:
--------------------------------------------------------------------------------
1 | user = $user;
23 | $this->flags = $flags;
24 | }
25 |
26 | /** @param int $flags
27 | * @return bool */
28 | function has($flags) {
29 | return ($this->flags & $flags) === $flags;
30 | }
31 |
32 | /** @return bool */
33 | function sent() {
34 | return ($this->flags & self::SENT) !== 0;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/api/api_follow.php:
--------------------------------------------------------------------------------
1 | u ?? $qreq->reviewer, $user, $prow);
9 | $following = friendly_boolean($qreq->following);
10 | if ($following === null) {
11 | return JsonResult::make_parameter_error("following", "Expected boolean");
12 | }
13 | $bits = Contact::WATCH_REVIEW_EXPLICIT | ($following ? Contact::WATCH_REVIEW : 0);
14 | $user->conf->qe("insert into PaperWatch set paperId=?, contactId=?, watch=? on duplicate key update watch=(watch&~?)|?",
15 | $prow->paperId, $reviewer->contactId, $bits,
16 | Contact::WATCH_REVIEW_EXPLICIT | Contact::WATCH_REVIEW, $bits);
17 | return ["ok" => true, "following" => $following];
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/formulas/f_reviewwordcount.php:
--------------------------------------------------------------------------------
1 | is_reviewer();
14 | }
15 | function compile(FormulaCompiler $state) {
16 | if ($state->index_type !== Fexpr::IDX_MY
17 | && VIEWSCORE_REVIEWER <= $state->user->permissive_view_score_bound()) {
18 | return "null";
19 | }
20 | $state->_ensure_review_word_counts();
21 | $rrow = $state->_rrow();
22 | $rrow_vsb = $state->_rrow_view_score_bound(true);
23 | return "(" . VIEWSCORE_AUTHORDEC . " > {$rrow_vsb} ? {$rrow}->reviewWordCount : null)";
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/api/api_graphdata.php:
--------------------------------------------------------------------------------
1 | x)) {
8 | return JsonResult::make_missing_error("x");
9 | }
10 | $fg = new FormulaGraph($user, $qreq->gtype ? : "scatter", $qreq->x, $qreq->y);
11 | if ($qreq->xorder) {
12 | $fg->set_xorder($qreq->xorder);
13 | }
14 |
15 | list($queries, $styles) = FormulaGraph::parse_queries($qreq);
16 | for ($i = 0; $i < count($queries); ++$i) {
17 | $fg->add_query($queries[$i], $styles[$i], isset($qreq->q1) ? "q$i" : "q");
18 | }
19 |
20 | if (!$fg->has_error()) {
21 | return ["ok" => true] + $fg->graph_json();
22 | } else {
23 | return new JsonResult(["ok" => false, "message_list" => $fg->message_list()]);
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/formulas/f_optionvalue.php:
--------------------------------------------------------------------------------
1 | option = $option;
11 | }
12 | function paper_options(&$oids) {
13 | $oids[$this->option->id] = true;
14 | }
15 | function viewable_by(Contact $user) {
16 | return $user->can_view_some_option($this->option);
17 | }
18 | function compile(FormulaCompiler $state) {
19 | $id = $this->option->id;
20 | $oval = "\$optvalue" . ($id < 0 ? "m" . -$id : $id);
21 | if ($state->check_gvar($oval)) {
22 | $ovv = $state->_add_option_value($this->option);
23 | $state->gstmt[] = "{$oval} = {$ovv} ? {$ovv}->value : null;";
24 | }
25 | return $oval;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib/subprocess.php:
--------------------------------------------------------------------------------
1 | $args
16 | * @return string */
17 | static function shell_quote_args($args) {
18 | $s = [];
19 | foreach ($args as $word) {
20 | $s[] = self::shell_quote_light($word);
21 | }
22 | return join(" ", $s);
23 | }
24 |
25 | /** @param list $args
26 | * @return list|string */
27 | static function args_to_command($args) {
28 | if (PHP_VERSION_ID < 70400) {
29 | return self::shell_quote_args($args);
30 | }
31 | return $args;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/settings/s_rf.php:
--------------------------------------------------------------------------------
1 | */
16 | public $values;
17 | public $ids;
18 | public $start;
19 | public $flip;
20 | public $scheme;
21 |
22 | // internal
23 | public $presence;
24 | /** @var list */
25 | public $xvalues;
26 | /** @var bool */
27 | public $existed = true;
28 | /** @var bool */
29 | public $deleted = false;
30 | }
31 |
32 | class RfValue_Setting {
33 | public $id;
34 | public $name;
35 | public $order;
36 | public $symbol;
37 |
38 | // internal
39 | public $old_value;
40 | /** @var bool */
41 | public $deleted = false;
42 | }
43 |
--------------------------------------------------------------------------------
/src/papercolumns/pc_paperidorder.php:
--------------------------------------------------------------------------------
1 | "__numericorder" . (++self::$type_uid), "sort" => true]);
12 | $this->order_term = $order_term;
13 | }
14 | function compare(PaperInfo $a, PaperInfo $b, PaperList $pl) {
15 | $ap = $this->order_term->index_of($a->paperId);
16 | $bp = $this->order_term->index_of($b->paperId);
17 | if ($ap !== false && $bp !== false) {
18 | return $ap <=> $bp;
19 | } else if ($ap !== false || $bp !== false) {
20 | return $ap === false ? 1 : -1;
21 | }
22 | return $a->paperId <=> $b->paperId;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/settings/s_preference.php:
--------------------------------------------------------------------------------
1 | name === "preference_min") {
8 | $v = $sv->newv($si);
9 | if ($v < -1000000) {
10 | $sv->error_at($si->name, "<0>Minimum preference must be at least -1000000");
11 | } else if ($v > 0) {
12 | $sv->error_at($si->name, "<0>Minimum preference cannot be greater than 0");
13 | }
14 | } else if ($si->name === "preference_max") {
15 | $v = $sv->newv($si);
16 | if ($v > 1000000) {
17 | $sv->error_at($si->name, "<0>Maximum preference must be at most 1000000");
18 | } else if ($v < 0) {
19 | $sv->error_at($si->name, "<0>Maximum preference cannot be less than 0");
20 | }
21 | }
22 | return false;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/search/st_editfinal.php:
--------------------------------------------------------------------------------
1 | negate();
14 | } else {
15 | $srch->lwarning($sword, "<0>Only “editfinal:yes” and “editfinal:no” are allowed");
16 | return new False_SearchTerm;
17 | }
18 | }
19 | function sqlexpr(SearchQueryInfo $sqi) {
20 | return "(Paper.outcome>0)";
21 | }
22 | function test(PaperInfo $row, $xinfo) {
23 | return $row->author_edit_state() === 2;
24 | }
25 | function about() {
26 | return self::ABOUT_PAPER;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/formulas/f_pagecount.php:
--------------------------------------------------------------------------------
1 | */
9 | static public $checkers = [];
10 | function __construct(FormulaCall $ff, Formula $formula) {
11 | parent::__construct("pagecount");
12 | $this->chkindex = 0;
13 | while ($this->chkindex < count(self::$checkers)
14 | && self::$checkers[$this->chkindex]->conf !== $formula->conf) {
15 | ++$this->chkindex;
16 | }
17 | if ($this->chkindex === count(self::$checkers)) {
18 | self::$checkers[] = new CheckFormat($formula->conf, CheckFormat::RUN_IF_NECESSARY_TIMEOUT);
19 | }
20 | }
21 | function compile(FormulaCompiler $state) {
22 | $doc = $state->_add_primary_document();
23 | return "({$doc} ? {$doc}->npages(PageCount_Fexpr::\$checkers[{$this->chkindex}]) : null)";
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/formulas/f_realnumberoption.php:
--------------------------------------------------------------------------------
1 | option = $option;
11 | }
12 | function paper_options(&$oids) {
13 | $oids[$this->option->id] = true;
14 | }
15 | function viewable_by(Contact $user) {
16 | return $user->can_view_some_option($this->option);
17 | }
18 | function compile(FormulaCompiler $state) {
19 | $id = $this->option->id;
20 | $oval = "\$optvalue" . ($id < 0 ? "m" . -$id : $id);
21 | if ($state->check_gvar($oval)) {
22 | $ovv = $state->_add_option_value($this->option);
23 | $state->gstmt[] = "{$oval} = {$ovv} && {$ovv}->value !== null ? floatval({$ovv}->data()) : null;";
24 | }
25 | return $oval;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/formulas/f_conflict.php:
--------------------------------------------------------------------------------
1 | ispc = is_object($ispc) ? $ispc->kwdef->is_pc : $ispc;
9 | $this->set_format(Fexpr::FBOOL);
10 | }
11 | function inferred_index() {
12 | return Fexpr::IDX_PC;
13 | }
14 | function compile(FormulaCompiler $state) {
15 | // XXX the actual search is different
16 | $idx = $state->loop_cid();
17 | if ($state->index_type === Fexpr::IDX_MY) {
18 | $rt = $state->_prow() . "->has_conflict($idx)";
19 | } else {
20 | $rt = "((" . $state->_add_conflict_types() . "[" . $idx . "] ?? 0) > "
21 | . CONFLICT_MAXUNCONFLICTED . ")";
22 | if ($this->ispc) {
23 | $rt = "(" . $state->_add_pc() . "[" . $idx . "] ?? false ? $rt : null)";
24 | }
25 | }
26 | return $rt;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/formulas/f_optionpresent.php:
--------------------------------------------------------------------------------
1 | option = $option;
11 | $this->set_format(Fexpr::FBOOL);
12 | }
13 | function paper_options(&$oids) {
14 | $oids[$this->option->id] = true;
15 | }
16 | function viewable_by(Contact $user) {
17 | return $user->can_view_some_option($this->option);
18 | }
19 | function compile(FormulaCompiler $state) {
20 | $id = $this->option->id;
21 | $ovp = "\$optpresent" . ($id < 0 ? "m" . -$id : $id);
22 | if ($state->check_gvar($ovp)) {
23 | $ovv = $state->_add_option_value($this->option);
24 | $state->gstmt[] = "{$ovp} = {$ovv} && {$ovv}->option->value_present({$ovv});";
25 | }
26 | return $ovp;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/assigners/a_error.php:
--------------------------------------------------------------------------------
1 | iswarning = $aj->name === "warning";
10 | }
11 | function paper_universe($req, AssignmentState $state) {
12 | return "none";
13 | }
14 | function allow_paper(PaperInfo $prow, AssignmentState $state) {
15 | return true;
16 | }
17 | function allow_user(PaperInfo $prow, Contact $contact, $req, AssignmentState $state) {
18 | return true;
19 | }
20 | function apply(PaperInfo $prow, Contact $contact, $req, AssignmentState $state) {
21 | $m = $req["message"] ?? ($this->iswarning ? "Warning" : "Error");
22 | if (!Ftext::is_ftext($m)) {
23 | $m = "<0>{$m}";
24 | }
25 | $state->msg_near($state->landmark(), $m, $this->iswarning ? 1 : 2);
26 | return false;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/papercolumns/pc_desirability.php:
--------------------------------------------------------------------------------
1 | user->is_manager())
11 | return false;
12 | if ($visible)
13 | $pl->qopts["allReviewerPreference"] = true;
14 | return true;
15 | }
16 | function compare(PaperInfo $a, PaperInfo $b, PaperList $pl) {
17 | return $a->desirability() <=> $b->desirability();
18 | }
19 | function content(PaperList $pl, PaperInfo $row) {
20 | $d = $row->desirability();
21 | return $d < 0 ? "−" /*U+2122*/ . (-$d) : (string) $d;
22 | }
23 | function text(PaperList $pl, PaperInfo $row) {
24 | return (string) $row->desirability();
25 | }
26 | function json(PaperList $pl, PaperInfo $row) {
27 | return $row->desirability();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/tagmessagereport.php:
--------------------------------------------------------------------------------
1 | */
11 | public $message_list;
12 | /** @var ?list */
13 | public $tags;
14 | /** @var ?list */
15 | public $tags_conflicted;
16 | /** @var ?string */
17 | public $tags_edit_text;
18 | /** @var ?string */
19 | public $tags_view_html;
20 | /** @var ?string */
21 | public $tag_decoration_html;
22 | /** @var ?string */
23 | public $color_classes;
24 | /** @var ?string */
25 | public $color_classes_conflicted;
26 | /** @var ?string */
27 | public $status_html;
28 |
29 | #[\ReturnTypeWillChange]
30 | function jsonSerialize() {
31 | $r = [];
32 | foreach (get_object_vars($this) as $k => $v) {
33 | if ($v !== null)
34 | $r[$k] = $v;
35 | }
36 | return $r;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/test/t_userapi.php:
--------------------------------------------------------------------------------
1 | conf = $conf;
15 | $this->user = $conf->root_user();
16 | }
17 |
18 | function test_disable() {
19 | $user = $this->conf->checked_user_by_email("marina@poema.ru");
20 | xassert_eqq($user->is_disabled(), false);
21 |
22 | $j = call_api("=account", $this->user, ["u" => "marina@poema.ru", "disable" => true], null);
23 | xassert($j->ok);
24 | $user = $this->conf->checked_user_by_email("marina@poema.ru");
25 | xassert_eqq($user->is_disabled(), true);
26 |
27 | $j = call_api("=account", $this->user, ["u" => "marina@poema.ru", "enable" => true], null);
28 | xassert($j->ok);
29 | $user = $this->conf->checked_user_by_email("marina@poema.ru");
30 | xassert_eqq($user->is_disabled(), false);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/formulas/f_reviewround.php:
--------------------------------------------------------------------------------
1 | set_format(Fexpr::FROUND);
8 | }
9 | function inferred_index() {
10 | return Fexpr::IDX_REVIEW;
11 | }
12 | function viewable_by(Contact $user) {
13 | return $user->is_reviewer();
14 | }
15 | function compile(FormulaCompiler $state) {
16 | $rrow = $state->_rrow();
17 | if ($state->index_type === Fexpr::IDX_MY) {
18 | return $state->define_gvar("myrevround", "{$rrow} ? {$rrow}->reviewRound : null");
19 | }
20 | $view_score = $state->user->permissive_view_score_bound();
21 | if (VIEWSCORE_REVIEWER <= $view_score) {
22 | return "null";
23 | }
24 | $state->queryOptions["reviewSignatures"] = true;
25 | $rrow_vsb = $state->_rrow_view_score_bound(false);
26 | return "(" . VIEWSCORE_REVIEWER . " > {$rrow_vsb} ? {$rrow}->reviewRound : null)";
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/formulas/f_revtype.php:
--------------------------------------------------------------------------------
1 | set_format(Fexpr::FREVTYPE);
9 | }
10 | function inferred_index() {
11 | return Fexpr::IDX_REVIEW;
12 | }
13 | function viewable_by(Contact $user) {
14 | return $user->is_reviewer();
15 | }
16 | function compile(FormulaCompiler $state) {
17 | if ($state->index_type === Fexpr::IDX_MY) {
18 | $rt = $state->define_gvar("myrevtype", $state->_prow() . "->review_type(\$contact)");
19 | } else {
20 | $view_score = $state->user->permissive_view_score_bound();
21 | if (VIEWSCORE_REVIEWER <= $view_score) {
22 | return "null";
23 | }
24 | $state->queryOptions["reviewSignatures"] = true;
25 | $rrow = $state->_rrow();
26 | return "({$rrow} ? {$rrow}->reviewType : null)";
27 | }
28 | return $rt;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/listactions/la_getlead.php:
--------------------------------------------------------------------------------
1 | type = $fj->type;
9 | }
10 | function allow(Contact $user, Qrequest $qreq) {
11 | return $user->isPC;
12 | }
13 | function run(Contact $user, Qrequest $qreq, SearchSelection $ssel) {
14 | $key = $this->type . "ContactId";
15 | $can_view = "can_view_" . $this->type;
16 | $texts = [];
17 | foreach ($ssel->paper_set($user) as $row) {
18 | if ($row->$key && $user->$can_view($row, true)) {
19 | $name = $user->conf->user_by_id($row->$key, USER_SLICE);
20 | $texts[] = [$row->paperId, $row->title, $name->firstName, $name->lastName, $name->email];
21 | }
22 | }
23 | return $user->conf->make_csvg($this->type . "s")
24 | ->select(["paper", "title", "given_name", "family_name", "{$this->type}email"])
25 | ->append($texts);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/reviewfields/rf_text.php:
--------------------------------------------------------------------------------
1 | */
6 | class Text_ReviewFieldSearch extends ReviewFieldSearch {
7 | /** @var int */
8 | public $op;
9 | /** @var TextPregexes */
10 | public $preg;
11 |
12 | /** @param Text_ReviewField $rf
13 | * @param int $op
14 | * @param TextPregexes $preg */
15 | function __construct($rf, $op, $preg) {
16 | parent::__construct($rf);
17 | $this->op = $op;
18 | $this->preg = $preg;
19 | }
20 |
21 | function sqlexpr() {
22 | return "tfields is not null";
23 | }
24 |
25 | function test_value($rrow, $fv) {
26 | $match = $fv !== null
27 | && $fv !== ""
28 | && $rrow->field_match_pregexes($this->preg, $this->rf->order);
29 | if (!$match) {
30 | if (($this->op & CountMatcher::RELALL) !== 0 && $fv !== null) {
31 | $this->finished = -1;
32 | }
33 | return false;
34 | }
35 | return true;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/api/api_job.php:
--------------------------------------------------------------------------------
1 | job ?? "")) === "") {
9 | return JsonResult::make_missing_error("job");
10 | } else if (strlen($jobid) < 24
11 | || !preg_match('/\A\w+\z/', $jobid)) {
12 | return JsonResult::make_parameter_error("job");
13 | }
14 |
15 | try {
16 | $tok = Job_Capability::find($jobid, $user->conf);
17 | } catch (CommandLineException $ex) {
18 | $tok = null;
19 | }
20 | if (!$tok) {
21 | return JsonResult::make_not_found_error("job");
22 | }
23 |
24 | $ok = $tok->is_active();
25 | // XXX is it meaningfully safer to treat inactive tokens as not found?
26 | $answer = ["ok" => $ok] + (array) $tok->data();
27 | $answer["ok"] = $ok;
28 | $answer["update_at"] = $answer["update_at"] ?? $tok->timeUsed;
29 | return new JsonResult($ok ? 200 : 409, $answer);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/test/t_ht.php:
--------------------------------------------------------------------------------
1 | a b c ');
9 | xassert_eqq(Ht::select("x", [
10 | ["optgroup", "a"],
11 | "b",
12 | "c"
13 | ], 1),
14 | 'b c ');
15 | xassert_eqq(Ht::select("x", [
16 | 1 => ["optgroup" => "a", "label" => "One"],
17 | 2 => ["optgroup" => "a", "label" => "Two"],
18 | 3 => ["label" => "Three"]
19 | ], 2),
20 | 'One Two Three ');
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/formulas/f_topicscore.php:
--------------------------------------------------------------------------------
1 | isPC;
17 | }
18 | function compile(FormulaCompiler $state) {
19 | $state->queryOptions["topics"] = true;
20 | $prow = $state->_prow();
21 | if ($state->index_type === Fexpr::IDX_MY) {
22 | return $state->define_gvar("mytopicscore", "{$prow}->topic_interest_score(\$contact)");
23 | } else if ($state->user->can_view_pc()) {
24 | return "{$prow}->topic_interest_score(" . $state->loop_cid(true) . ")";
25 | } else {
26 | return "(" . $state->loop_cid() . " == " . $state->user->contactId
27 | . " ? {$prow}->topic_interest_score(" . $state->loop_cid() . ")"
28 | . " : null)";
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/settings/s_sf.php:
--------------------------------------------------------------------------------
1 | */
39 | public $xvalues;
40 | }
41 |
42 | class SfValue_Setting {
43 | public $id;
44 | public $name;
45 | public $order;
46 |
47 | // internal
48 | public $old_value;
49 | /** @var bool */
50 | public $deleted = false;
51 | }
52 |
--------------------------------------------------------------------------------
/test/t_mailer.php:
--------------------------------------------------------------------------------
1 | conf = $conf;
12 | }
13 |
14 | function run_send_template(MailRecipients $mr, $template, $qreq = []) {
15 | if (!($qreq instanceof Qrequest)) {
16 | $qreq = (new Qrequest("POST", $qreq))->set_user($mr->user)->approve_token();
17 | }
18 | ob_start();
19 | try {
20 | $ms = new MailSender($mr, $qreq, 2);
21 | $ms->set_template($template);
22 | $ms->set_no_print(true)->set_send_all(true);
23 | $ms->prepare_sending_mailid();
24 | $ms->run();
25 | } catch (PageCompletion $unused) {
26 | }
27 | ob_end_clean();
28 | }
29 |
30 | function test_send() {
31 | MailChecker::clear();
32 | $user = $this->conf->checked_user_by_email("chair@_.com");
33 | $mr = (new MailRecipients($user))->set_recipients("au")->set_paper_ids([13, 14, 15, 16]);
34 | $this->run_send_template($mr, "@authors");
35 | MailChecker::check_db("t_mailer-send-1");
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/etc/capabilityhandlers.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "match": "0([1-9][0-9]*)a\\S+",
4 | "apply_function": "AuthorView_Capability::apply_old_author_view",
5 | "$comment": "XXX backward compat"
6 | },
7 | {
8 | "match": "hcav_?(\\d+)[a-zA-Z]+", "type": 4,
9 | "apply_function": "AuthorView_Capability::apply_author_view"
10 | },
11 | {
12 | "match": "([1-9][0-9]*)ra(\\S+)",
13 | "apply_function": "ReviewAccept_Capability::apply_old_review_acceptor",
14 | "$comment": "XXX backward compat"
15 | },
16 | {
17 | "match": "hcra_?([1-9][0-9]*)[a-zA-Z]+", "type": 5,
18 | "apply_function": "ReviewAccept_Capability::apply_review_acceptor"
19 | },
20 | {
21 | "match": "ra([1-9][0-9]*)([a-zA-Z]+)",
22 | "apply_function": "ReviewAccept_Capability::apply_old_review_acceptor"
23 | },
24 | {
25 | "match": "kiosk-([a-zA-Z0-9]+)",
26 | "apply_function": "MeetingTracker::apply_kiosk_capability",
27 | "$comment": "XXX backward compat"
28 | },
29 | {
30 | "match": "hckk_?([a-zA-Z0-9]+)",
31 | "apply_function": "MeetingTracker::apply_kiosk_capability"
32 | },
33 | {
34 | "type": 3, "cleanup_function": "Upload_API::cleanup"
35 | }
36 | ]
37 |
--------------------------------------------------------------------------------
/devel/apidoc/tags.md:
--------------------------------------------------------------------------------
1 | # get /{p}/tags
2 |
3 | > Retrieve submission tags
4 |
5 | * response_schema tag_response
6 |
7 |
8 | # post /{p}/tags
9 |
10 | > Change submission tags
11 |
12 | * response_schema tag_response
13 | * response_schema search_response
14 |
15 |
16 | # post /assigntags
17 |
18 | > Change several tags
19 |
20 | * param =tagassignment string:Comma-separated list of paper IDs and tag assignments
21 | * param ?=search search_parameter_specification
22 | * response_schema search_response
23 |
24 |
25 | # get /alltags
26 |
27 | > Retrieve all visible tags
28 |
29 | * response tags tag_list
30 |
31 |
32 | # get /taganno
33 |
34 | > Retrieve tag annotations
35 |
36 | * param tag tag
37 | * param ?search search_parameter_specification
38 | * response tag tag
39 | * response editable boolean
40 | * response anno [tag_annotation]
41 | * response_schema search_response
42 |
43 |
44 | # post /taganno
45 |
46 | > Change tag annotations
47 |
48 | * param +anno [tag_annotation]
49 | * response tag tag
50 | * response editable boolean
51 | * response anno [tag_annotation]
52 | * response_schema search_response
53 |
54 |
55 | # get /{p}/tagmessages
56 |
57 | > Retrieve tag edit messages
58 |
59 |
60 | # get /{p}/votereport
61 |
62 | > Retrieve vote analysis
63 |
64 | * param tag tag
65 |
--------------------------------------------------------------------------------
/src/api/api_decision.php:
--------------------------------------------------------------------------------
1 | conf->decision_set();
8 | if ($qreq->method() !== "GET") {
9 | $aset = (new AssignmentSet($user))->set_override_conflicts(true);
10 | $aset->enable_papers($prow);
11 | if (is_numeric($qreq->decision) && $decset->contains(+$qreq->decision)) {
12 | $qreq->decision = $decset->get(+$qreq->decision)->name;
13 | }
14 | $aset->parse("paper,action,decision\n{$prow->paperId},decision," . CsvGenerator::quote($qreq->decision));
15 | if (!$aset->execute()) {
16 | return $aset->json_result();
17 | }
18 | $prow->load_decision();
19 | }
20 | $dec = $prow->viewable_decision($user);
21 | $jr = new JsonResult([
22 | "ok" => true,
23 | "decision" => $dec->id,
24 | "decision_html" => $dec->name_as(5)
25 | ]);
26 | if ($user->can_set_decision($prow)) {
27 | $jr->content["editable"] = true;
28 | }
29 | return $jr;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/test/t_s3.php:
--------------------------------------------------------------------------------
1 | opt("testS3Key"))
10 | || !($s3s = $conf->opt("testS3Secret"))
11 | || !in_array($tester, $conf->opt("testS3Testers") ?? [])) {
12 | return null;
13 | }
14 | $s3r = $conf->opt("testS3Region");
15 | $s3b = $conf->opt("testS3Bucket") ?? ("hotcrptest-" . strtolower(encode_token(random_bytes(8))));
16 | return S3Client::make([
17 | "key" => $s3k, "secret" => $s3s, "region" => $s3r,
18 | "bucket" => $s3b
19 | ]);
20 | }
21 |
22 | /** @param S3Client|array{?string,?string,?string,?string} $s3i
23 | * @return array{?string,?string,?string,?string} */
24 | static function install_s3_options(Conf $conf, $s3i) {
25 | $r = [];
26 | foreach (["s3_bucket", "s3_key", "s3_secret", "s3_region"] as $i => $k) {
27 | $v = $s3i instanceof S3Client ? $s3i->$k : $s3i[$i];
28 | $r[] = $conf->opt($k);
29 | $conf->set_opt($k, $v);
30 | }
31 | return $r;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/search/st_optiontext.php:
--------------------------------------------------------------------------------
1 | match = $match;
14 | }
15 | function debug_json() {
16 | return [
17 | "type" => $this->type,
18 | "option" => $this->option->search_keyword(),
19 | "match" => $this->match
20 | ];
21 | }
22 | function test(PaperInfo $row, $xinfo) {
23 | if ($this->user->can_view_option($row, $this->option)
24 | && ($ov = $row->option($this->option))
25 | && ($ov->data() ?? "") !== "") {
26 | $this->pregexes = $this->pregexes ?? Text::star_text_pregexes($this->match);
27 | return Text::match_pregexes($this->pregexes, (string) $ov->data(), null);
28 | } else {
29 | return false;
30 | }
31 | }
32 | function about() {
33 | return self::ABOUT_PAPER;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/test/t_xtcheck.php:
--------------------------------------------------------------------------------
1 | conf = $conf;
14 | }
15 |
16 | static function check() {
17 | ++self::$nchecks;
18 | return true;
19 | }
20 |
21 | function test_xt_check() {
22 | $xtp = new XtParams($this->conf, null);
23 | xassert($xtp->check("allow"));
24 | xassert(!$xtp->check("deny"));
25 | xassert($xtp->check("!deny"));
26 | xassert(!$xtp->check("! allow"));
27 | xassert($xtp->check("!!allow"));
28 | xassert($xtp->check("!!!deny"));
29 | xassert($xtp->check("allow || deny"));
30 | xassert(!$xtp->check("allow && deny"));
31 | xassert($xtp->check("!(allow && deny)"));
32 | xassert($xtp->check("!(allow && deny)"));
33 | xassert($xtp->check("!opt.sendEmail"));
34 | xassert(!$xtp->check("opt.sendEmail && XtCheck_Tester::check && allow"));
35 | xassert_eqq(self::$nchecks, 0);
36 | xassert($xtp->check("XtCheck_Tester::check && allow"));
37 | xassert_eqq(self::$nchecks, 1);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/test/ldap/README.md:
--------------------------------------------------------------------------------
1 | HotCRP LDAP test server
2 | =======================
3 |
4 | This directory contains a configuration for a [Light
5 | LDAP](https://github.com/lldap/lldap) server that can be used to test HotCRP’s
6 | LDAP support.
7 |
8 |
9 | Installation
10 | ------------
11 |
12 | 1. Run `docker compose up` in this directory.
13 |
14 | 2. (Optional) Create users using the LLDAP admin interface.
15 |
16 | HotCRP ships with an LLDAP SQLite database whose users are listed below in
17 | “Default users.” If you want to change these users or create other ones,
18 | sign in to the LLDAP administration server at `http://localhost:17170`
19 | using username `admin` and password `aequee0Oe1ee1A` .
20 |
21 | 3. Configure HotCRP to use the running LLDAP server for authentication by
22 | setting `$Opt["ldapLogin"]` in `conf/options.php`:
23 |
24 | ```php
25 | $Opt["ldapLogin"] = "ldap://localhost:17169/ uid=*,ou=people,dc=hotcrp,dc=org";
26 | ```
27 |
28 | 4. Sign in to HotCRP as one of the LDAP users.
29 |
30 |
31 | Default users
32 | -------------
33 |
34 | | User | Name | Mail | Password |
35 | |----------|--------------|------------------------|----------------|
36 | | fran | Fran Framer | fran@hotcrp-ldap.org | raec3ohL5u |
37 | | paula | Paula Books | paula@hotcrp-ldap.org | gi1eiluoCh |
38 |
--------------------------------------------------------------------------------
/src/api/api_events.php:
--------------------------------------------------------------------------------
1 | is_reviewer()) {
11 | return JsonResult::make_permission_error();
12 | }
13 | $from = $qreq->from;
14 | if (!$from || !ctype_digit($from)) {
15 | $from = Conf::$now;
16 | }
17 | $when = $from;
18 | $rf = $user->conf->review_form();
19 | $events = new PaperEvents($user);
20 | $rows = [];
21 | $more = false;
22 | foreach ($events->events($when, 11) as $xr) {
23 | if (count($rows) == 10) {
24 | $more = true;
25 | } else {
26 | if ($xr->crow) {
27 | $rows[] = $xr->crow->unparse_flow_entry($user);
28 | } else {
29 | $rows[] = $rf->unparse_flow_entry($xr->prow, $xr->rrow, $user);
30 | }
31 | $when = $xr->eventTime;
32 | }
33 | }
34 | return new JsonResult([
35 | "ok" => true, "from" => (int) $from, "to" => (int) $when - 1,
36 | "rows" => $rows, "more" => $more
37 | ]);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/papercolumns/pc_topics.php:
--------------------------------------------------------------------------------
1 | conf->has_topics()) {
13 | return false;
14 | }
15 | if ($visible) {
16 | $pl->qopts["topics"] = 1;
17 | }
18 | // only managers can see other users’ topic interests
19 | $this->interest_contact = $pl->reviewer_user();
20 | if ($this->interest_contact->contactId !== $pl->user->contactId
21 | && !$pl->user->is_manager()) {
22 | $this->interest_contact = null;
23 | }
24 | return true;
25 | }
26 | function content_empty(PaperList $pl, PaperInfo $row) {
27 | return $row->topicIds === "";
28 | }
29 | function content(PaperList $pl, PaperInfo $row) {
30 | return $pl->conf->topic_set()->unparse_list_html($row->topic_list(), $this->interest_contact ? $this->interest_contact->topic_interest_map() : null);
31 | }
32 | function text(PaperList $pl, PaperInfo $row) {
33 | return $row->unparse_topics_text();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/search/st_documentname.php:
--------------------------------------------------------------------------------
1 | want = $want;
17 | $this->match = $match;
18 | }
19 | function debug_json() {
20 | return [$this->type, $this->option->search_keyword(), $this->match];
21 | }
22 | function test(PaperInfo $row, $xinfo) {
23 | if ($this->user->can_view_option($row, $this->option)
24 | && ($ov = $row->option($this->option))) {
25 | $this->pregexes = $this->pregexes ?? Text::star_text_pregexes($this->match);
26 | foreach ($ov->document_set() as $d) {
27 | $m = $this->pregexes->match($d->filename, null);
28 | if ($m === $this->want)
29 | return true;
30 | }
31 | }
32 | return false;
33 | }
34 | function about() {
35 | return self::ABOUT_PAPER;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/batch/fileinfo.php:
--------------------------------------------------------------------------------
1 | run());
8 | }
9 |
10 | class Fileinfo_Batch {
11 | /** @var list */
12 | public $files = [];
13 |
14 | /** @return int */
15 | function run() {
16 | if (empty($this->files)) {
17 | $this->files[] = "-";
18 | }
19 | foreach ($this->files as $file) {
20 | if ($file === "-") {
21 | $content = stream_get_contents(STDIN);
22 | } else {
23 | $content = file_get_contents($file);
24 | }
25 | fwrite(STDOUT, sprintf("%-39s %s\n", $file, json_encode(Mimetype::content_info($content))));
26 | }
27 | return 0;
28 | }
29 |
30 | /** @param list $argv
31 | * @return Fileinfo_Batch */
32 | static function make_args($argv) {
33 | $arg = (new Getopt)->long(
34 | "help,h !"
35 | )->description("Report HotCRP-derived file info.
36 | Usage: php batch/fileinfo.php FILES...")
37 | ->helpopt("help")
38 | ->parse($argv);
39 | $fib = new Fileinfo_Batch();
40 | $fib->files = $arg["_"];
41 | return $fib;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/search/st_optionpresent.php:
--------------------------------------------------------------------------------
1 | $this->type,
12 | "option" => $this->option->search_keyword()
13 | ];
14 | }
15 | function is_sqlexpr_precise() {
16 | return $this->option->always_visible()
17 | && $this->option->is_value_present_trivial();
18 | }
19 | function test(PaperInfo $row, $xinfo) {
20 | return $this->user->can_view_option($row, $this->option)
21 | && ($ov = $row->option($this->option))
22 | && $this->option->value_present($ov);
23 | }
24 | function script_expression(PaperInfo $row, $about) {
25 | if (($about & self::ABOUT_PAPER) === 0) {
26 | return parent::script_expression($row, $about);
27 | } else if ($this->user->can_view_option($row, $this->option)) {
28 | return $this->option->present_script_expression();
29 | } else {
30 | return false;
31 | }
32 | }
33 | function about() {
34 | return self::ABOUT_PAPER;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/api/api_formatcheck.php:
--------------------------------------------------------------------------------
1 | doc, $user);
9 | } catch (Exception $unused) {
10 | return JsonResult::make_error(404, "<0>Document not found");
11 | }
12 | if (($whynot = $docreq->perm_view_document($user))) {
13 | return JsonResult::make_message_list(isset($whynot["permission"]) ? 403 : 404, $whynot->message_list());
14 | }
15 | if (!($doc = $docreq->prow->document($docreq->dtype, $docreq->docid, true))) {
16 | return JsonResult::make_error(404, "<0>Document not found");
17 | }
18 | $runflag = friendly_boolean($qreq->soft) ? CheckFormat::RUN_IF_NECESSARY : CheckFormat::RUN_ALWAYS;
19 | $cf = new CheckFormat($user->conf, $runflag);
20 | $cf->check_document($doc);
21 | $ms = $cf->document_messages($doc);
22 | return [
23 | "ok" => $cf->check_ok(),
24 | "docid" => $doc->paperStorageId,
25 | "npages" => $cf->npages,
26 | "nwords" => $cf->nwords,
27 | "problem_fields" => $cf->problem_fields(),
28 | "has_error" => $cf->has_error(),
29 | "message_list" => $ms->message_list()
30 | ];
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/test/review1A.txt:
--------------------------------------------------------------------------------
1 | ==+== Testconf I Paper Review Form
2 | ==-== DO NOT CHANGE LINES THAT START WITH "==+==" UNLESS DIRECTED!
3 | ==-== For further guidance, or to upload this file when you are done, go to:
4 | ==-== http://hotcrp.lcdf.org/test/offline
5 |
6 | ==+== =====================================================================
7 | ==+== Begin Review #1A
8 | ==+== Reviewer: Mary Baker
9 | ==-== Updated 8 Aug 2017 6:46:19pm EDT
10 |
11 | ==+== Paper #1
12 | ==-== Title: Scalable Timers for Soft State Protocols
13 |
14 | ==+== Review Readiness
15 | ==-== Enter "Ready" if the review is ready for others to see:
16 |
17 | Ready
18 |
19 | ==+== A. Overall merit
20 | ==-== Choices:
21 | ==-== 1. Reject
22 | ==-== 2. Weak reject
23 | ==-== 3. Weak accept
24 | ==-== 4. Accept
25 | ==-== 5. Strong accept
26 | ==-== Enter the number of your choice:
27 |
28 | 4
29 |
30 | ==+== B. Reviewer expertise
31 | ==-== Choices:
32 | ==-== 1. No familiarity
33 | ==-== 2. Some familiarity
34 | ==-== 3. Knowledgeable
35 | ==-== 4. Expert
36 | ==-== Enter the number of your choice:
37 |
38 | 2
39 |
40 | ==+== C. Paper summary
41 |
42 |
43 |
44 | ==+== D. Comments for authors
45 |
46 |
47 |
48 | ==+== E. Comments for PC
49 | ==-== Hidden from authors.
50 |
51 |
52 |
53 |
54 | This is a test of leading whitespace
55 |
56 | It should be preserved
57 | And defended
58 |
59 | ==+== Scratchpad (for unsaved private notes)
60 |
61 | ==+== End Review
62 |
--------------------------------------------------------------------------------
/src/help/h_scoresort.php:
--------------------------------------------------------------------------------
1 | Some paper search results include columns with score graphs. Click on a score
9 | column heading to sort the paper list using that score. Search > View
10 | options changes how scores are sorted. There are five choices:
11 |
12 |
13 |
14 | Counts (default)
15 |
16 | Sort by the number of highest scores, then the number of second-highest
17 | scores, then the number of third-highest scores, and so on. To sort a paper
18 | with fewer reviews than others, HotCRP adds phantom reviews with scores just
19 | below the paper’s lowest real score. Also known as Minshall score.
20 |
21 | Average
22 | Sort by the average (mean) score.
23 |
24 | Median
25 | Sort by the median score.
26 |
27 | Variance
28 | Sort by the variance in scores.
29 |
30 | Max − min
31 | Sort by the difference between the largest and smallest scores (a good
32 | measure of differences of opinion).
33 |
34 | My score
35 | Sort by your score. In the score graphs, your score is highlighted with a
36 | darker colored square.
37 |
38 | ";
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/settings/s_basics.php:
--------------------------------------------------------------------------------
1 | info page
3 | // Copyright (c) 2006-2022 Eddie Kohler; see LICENSE.
4 |
5 | class Basics_SettingParser extends SettingParser {
6 | static function print_names(SettingValues $sv) {
7 | $sv->print_entry_group("conference_abbreviation", null, [
8 | "hint" => "Examples: “HotOS XIV”, “NSDI '14”"
9 | ]);
10 | $sv->print_entry_group("conference_name", null, [
11 | "hint" => "Example: “14th Workshop on Hot Topics in Operating Systems”"
12 | ]);
13 | $sv->print_entry_group("conference_url", null, [
14 | "hint" => "Example: “https://yourconference.org/”"
15 | ]);
16 | }
17 |
18 | static function print_email(SettingValues $sv) {
19 | $sv->print_entry_group("email_default_cc", null);
20 | $sv->print_entry_group("email_default_reply_to", null);
21 | }
22 |
23 | function apply_req(Si $si, SettingValues $sv) {
24 | if (($v = $sv->base_parse_req($si)) !== null
25 | && $sv->update($si->name, $v)
26 | && $sv->conf->contactdb()) {
27 | $sv->register_cleanup_function("update_shortName", function () use ($sv) {
28 | $conf = $sv->conf;
29 | Dbl::ql($conf->contactdb(), "update Conferences set shortName=?, longName=? where dbName=?", $conf->short_name, $conf->long_name, $conf->dbname);
30 | });
31 | }
32 | return true;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/listactions/la_decide.php:
--------------------------------------------------------------------------------
1 | can_set_some_decision();
8 | }
9 | static function render(PaperList $pl, Qrequest $qreq) {
10 | $opts = [];
11 | foreach ($pl->conf->decision_set() as $dec) {
12 | $opts[$dec->id] = $dec->name_as(5);
13 | }
14 | return ["Set to "
15 | . Ht::select("decision", $opts, "", ["class" => "want-focus js-submit-action-info-decide"])
16 | . $pl->action_submit("decide")];
17 | }
18 | function run(Contact $user, Qrequest $qreq, SearchSelection $ssel) {
19 | $aset = (new AssignmentSet($user))->set_override_conflicts(true);
20 | $did = $qreq->decision;
21 | if (is_numeric($did)
22 | && ($dec = $user->conf->decision_set()->get(+$did))) {
23 | $did = $dec->name;
24 | }
25 | $aset->parse("paper,action,decision\n" . join(" ", $ssel->selection()) . ",decision," . CsvGenerator::quote($did));
26 | if ($aset->execute()) {
27 | return new Redirection($user->conf->selfurl($qreq, ["atab" => "decide", "decision" => $qreq->decision], Conf::HOTURL_RAW | Conf::HOTURL_REDIRECTABLE));
28 | } else {
29 | $user->conf->feedback_msg($aset->message_list());
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/options/o_checkboxes.php:
--------------------------------------------------------------------------------
1 | assign_values($args->values ?? [], $args->ids ?? null);
13 | $this->compact = true;
14 | }
15 |
16 |
17 | function jsonSerialize() {
18 | $j = parent::jsonSerialize();
19 | $j->values = $this->values();
20 | if ($this->is_ids_nontrivial()) {
21 | $j->ids = $this->ids();
22 | }
23 | return $j;
24 | }
25 |
26 | function export_setting() {
27 | $sfs = parent::export_setting();
28 | $this->unparse_values_setting($sfs);
29 | return $sfs;
30 | }
31 |
32 | /** @return TopicSet */
33 | function topic_set() {
34 | return $this->values_topic_set();
35 | }
36 |
37 |
38 | function search_examples(Contact $viewer, $context) {
39 | $a = [$this->has_search_example()];
40 | if (($q = $this->value_search_keyword(2))) {
41 | $a[] = new SearchExample(
42 | $this, $this->search_keyword() . ":{value}",
43 | "<0>submission’s {title} field has value ‘{value}’",
44 | new FmtArg("value", $this->values[1])
45 | );
46 | }
47 | return $a;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/searchoperator.php:
--------------------------------------------------------------------------------
1 | type = $type;
35 | $this->subtype = $subtype;
36 | $this->precedence = $precedence;
37 | $this->flags = $flags;
38 | }
39 |
40 | /** @return bool */
41 | function unary() {
42 | return ($this->flags & self::F_UNARY) !== 0;
43 | }
44 |
45 | /** @param string $subtype
46 | * @return SearchOperator */
47 | function make_subtype($subtype) {
48 | assert(($this->flags & self::F_ALLOW_SUBTYPE) !== 0);
49 | return new SearchOperator($this->type, $this->precedence, ($this->flags & ~self::F_ALLOW_SUBTYPE) | self::F_SUBTYPE, $subtype);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/autoassigners/aa_prefconflict.php:
--------------------------------------------------------------------------------
1 | $pcids
7 | * @param list $papersel */
8 | function __construct(Contact $user, $pcids, $papersel) {
9 | parent::__construct($user, $pcids, $papersel);
10 | $this->set_assignment_action("conflict");
11 | }
12 |
13 | /** @param bool $exists_submitted
14 | * @return Dbl_Result */
15 | static function query_result(Conf $conf, $exists_submitted) {
16 | $qsuffix = $exists_submitted ? " and P.timeSubmitted>0 limit 1" : "";
17 | return $conf->ql_raw("select PRP.paperId, PRP.contactId, PRP.preference
18 | from PaperReviewPreference PRP
19 | join ContactInfo c on (c.contactId=PRP.contactId and c.roles!=0 and (c.roles&" . Contact::ROLE_PC . ")!=0)
20 | join Paper P on (P.paperId=PRP.paperId)
21 | left join PaperConflict PC on (PC.paperId=PRP.paperId and PC.contactId=PRP.contactId)
22 | where PRP.preference<=-100 and coalesce(PC.conflictType,0)<=" . CONFLICT_MAXUNCONFLICTED . "
23 | and P.timeWithdrawn<=0" . $qsuffix);
24 | }
25 |
26 | function run() {
27 | $result = self::query_result($this->conf, false);
28 | while (($row = $result->fetch_row())) {
29 | $this->assign1((int) $row[1], (int) $row[0]);
30 | }
31 | Dbl::free($result);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2006-2022 Eddie Kohler
2 | Copyright (c) 2006-2008 Regents of the University of California
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a
5 | copy of this software and associated documentation files (the "Software"),
6 | to deal in the Software without restriction, including without limitation
7 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 | and/or sell copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in
12 | all copies or substantial portions of the Software.
13 |
14 | The names and trademarks of copyright holders may not be used in
15 | advertising or publicity pertaining to the software without specific
16 | prior permission. Title to copyright in this software and any associated
17 | documentation will at all times remain with the copyright holders.
18 |
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 | DEALINGS IN THE SOFTWARE.
26 |
27 | HotCRP was originally derived from Dirk Grunwald's CRP (Copyright (c)
28 | 2002-2005 Dirk Grunwald et al., distributed under an "MIT license").
29 |
--------------------------------------------------------------------------------
/src/apihelpers.php:
--------------------------------------------------------------------------------
1 | contactId > 0 && $text === (string) $viewer->contactId)
14 | || ($viewer->has_email() && strcasecmp($text, $viewer->email) === 0)) {
15 | return $viewer;
16 | }
17 | if (ctype_digit($text)) {
18 | $u = $viewer->conf->user_by_id(intval($text), USER_SLICE);
19 | } else {
20 | $u = $viewer->conf->user_by_email($text, USER_SLICE);
21 | }
22 | if ($u) {
23 | return $u;
24 | } else if ($viewer->isPC) {
25 | JsonResult::make_not_found_error($field, "<0>User not found")->complete();
26 | } else {
27 | JsonResult::make_permission_error()->complete();
28 | }
29 | }
30 |
31 | /** @param ?string $text
32 | * @param ?PaperInfo $prow
33 | * @return Contact */
34 | static function parse_reviewer_for($text, Contact $viewer, $prow) {
35 | $u = self::parse_user($text, $viewer);
36 | if ($u->contactId === $viewer->contactId
37 | || ($prow ? $viewer->can_administer($prow) : $viewer->privChair)) {
38 | return $u;
39 | } else {
40 | JsonResult::make_permission_error()->complete();
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/listactions/la_mail.php:
--------------------------------------------------------------------------------
1 | template = $uf->mail_template ?? null;
10 | $this->recipients = $uf->recipients ?? null;
11 | }
12 | function allow(Contact $user, Qrequest $qreq) {
13 | return $user->is_manager() && $qreq->page() !== "reviewprefs";
14 | }
15 | static function render(PaperList $pl, Qrequest $qreq, ComponentSet $gex) {
16 | $sel_opt = ListAction::members_selector_options($gex, "mail");
17 | if (!empty($sel_opt)) {
18 | return Ht::select("mailfn", $sel_opt, $qreq->mailfn,
19 | ["class" => "want-focus js-submit-action-info-mail ignore-diff"])
20 | . $pl->action_submit("mail", ["class" => "can-submit-all", "formmethod" => "get"]);
21 | } else {
22 | return null;
23 | }
24 | }
25 | function run(Contact $user, Qrequest $qreq, SearchSelection $ssel) {
26 | $args = [];
27 | if ($ssel->equals_search(new PaperSearch($user, $qreq))) {
28 | $args["q"] = $qreq->q;
29 | $args["plimit"] = 1;
30 | } else {
31 | $args["p"] = join(" ", $ssel->selection());
32 | }
33 | $args["t"] = $qreq->t;
34 | $args["template"] = $this->template;
35 | $args["to"] = $this->recipients;
36 | return new Redirection($user->conf->hoturl("mail", $args));
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/etc/reviewfieldtypes.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "dropdown", "title": "Dropdown", "order": 300,
4 | "properties": {"values_text": true, "scheme": true, "required": true},
5 | "id_prefix": "s",
6 | "conversions": [{"from": "radio"}]
7 | },
8 | {
9 | "name": "radio", "title": "Radio buttons", "order": 200,
10 | "properties": {"values_text": true, "scheme": true, "required": true},
11 | "id_prefix": "s",
12 | "conversions": [{"from": "dropdown"}]
13 | },
14 | {
15 | "name": "text", "title": "Text", "order": 500,
16 | "sample": {"value": "Text entry"},
17 | "id_prefix": "t"
18 | },
19 | {
20 | "name": "checkbox", "title": "Checkbox", "order": 600,
21 | "properties": {"scheme": true, "required": true, "checkbox": true},
22 | "id_prefix": "s",
23 | "conversions": [
24 | {"to": "radio", "setting_function": "Checkbox_ReviewField::convert_to_score_setting"},
25 | {"to": "dropdown", "setting_function": "Checkbox_ReviewField::convert_to_score_setting"},
26 | {"from": "radio", "allow_function": "Checkbox_ReviewField::allow_convert_from_score", "setting_function": "Checkbox_ReviewField::convert_from_score_setting"},
27 | {"from": "dropdown", "allow_function": "Checkbox_ReviewField::allow_convert_from_score", "setting_function": "Checkbox_ReviewField::convert_from_score_setting"}
28 | ]
29 | },
30 | {
31 | "name": "checkboxes", "title": "Checkboxes", "order": 700,
32 | "properties": {"values_text": true, "scheme": true, "required": true},
33 | "id_prefix": "s"
34 | }
35 | ]
36 |
--------------------------------------------------------------------------------
/src/papercolumns/pc_commenters.php:
--------------------------------------------------------------------------------
1 | viewable_comments($pl->user);
11 | }
12 | function content(PaperList $pl, PaperInfo $row) {
13 | $crows = $row->viewable_comments($pl->user);
14 | $cnames = array_map(function ($cx) use ($pl) {
15 | $n = $t = $cx[0]->unparse_commenter_html($pl->user);
16 | if (($tags = $cx[0]->viewable_tags($pl->user))
17 | && ($color = $cx[0]->conf->tags()->color_classes($tags))) {
18 | $t = "{$n} ";
19 | }
20 | if ($cx[1] > 1) {
21 | $t .= " ({$cx[1]})";
22 | }
23 | return $t . $cx[2];
24 | }, CommentInfo::group_by_identity($crows, $pl->user, true));
25 | return join(" ", $cnames);
26 | }
27 | function text(PaperList $pl, PaperInfo $row) {
28 | $crows = $row->viewable_comments($pl->user);
29 | $cnames = array_map(function ($cx) use ($pl) {
30 | $t = $cx[0]->unparse_commenter_text($pl->user);
31 | if ($cx[1] > 1) {
32 | $t .= " ({$cx[1]})";
33 | }
34 | return $t . $cx[2];
35 | }, CommentInfo::group_by_identity($crows, $pl->user, false));
36 | return join(" ", $cnames);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/fieldchangeset.php:
--------------------------------------------------------------------------------
1 | */
7 | public $_m = [];
8 |
9 | const ABSENT = 0;
10 | const UNCHANGED = 1;
11 | const CHANGED = 2;
12 |
13 | /** @param ?string $s
14 | * @return $this */
15 | function mark_unchanged($s) {
16 | $this->apply($s, self::UNCHANGED);
17 | return $this;
18 | }
19 |
20 | /** @param ?string $s
21 | * @return $this */
22 | function mark_changed($s) {
23 | $this->apply($s, self::CHANGED);
24 | return $this;
25 | }
26 |
27 | /** @param string $src
28 | * @param string $dst
29 | * @return $this */
30 | function mark_synonym($src, $dst) {
31 | $this->_m[$src] = $this->_m[$dst] =
32 | ($this->_m[$src] ?? 0) | ($this->_m[$dst] ?? 0);
33 | return $this;
34 | }
35 |
36 | /** @param ?string $s
37 | * @param 1|2 $bit */
38 | private function apply($s, $bit) {
39 | foreach (explode(" ", $s ?? "") as $word) {
40 | if ($word !== "") {
41 | $this->_m[$word] = ($this->_m[$word] ?? 0) | $bit;
42 | if (($colon = strpos($word, ":")) !== false) {
43 | $px = substr($word, 0, $colon);
44 | $this->_m[$px] = ($this->_m[$px] ?? 0) | $bit;
45 | }
46 | }
47 | }
48 | }
49 |
50 | /** @param string $key
51 | * @return 0|1|2|3 */
52 | function test($key) {
53 | return $this->_m[$key] ?? 0;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/papercolumns/pc_pcconflicts.php:
--------------------------------------------------------------------------------
1 | user->can_view_some_conflicts()) {
11 | return false;
12 | }
13 | if ($visible) {
14 | $pl->qopts["allConflictType"] = 1;
15 | }
16 | return true;
17 | }
18 | function content_empty(PaperList $pl, PaperInfo $row) {
19 | return !$pl->user->can_view_conflicts($row);
20 | }
21 | function content(PaperList $pl, PaperInfo $row) {
22 | $y = [];
23 | $pcm = $row->conf->pc_members();
24 | foreach ($row->conflict_types() as $uid => $ctype) {
25 | if (!($pc = $pcm[$uid] ?? null)
26 | || !Conflict::is_conflicted($ctype)) {
27 | continue;
28 | }
29 | $y[$pc->pc_index] = $pl->user->reviewer_html_for($pc);
30 | }
31 | ksort($y);
32 | return join(", ", $y);
33 | }
34 | function text(PaperList $pl, PaperInfo $row) {
35 | $y = [];
36 | $pcm = $row->conf->pc_members();
37 | foreach ($row->conflict_types() as $uid => $ctype) {
38 | if (!($pc = $pcm[$uid] ?? null)
39 | || !Conflict::is_conflicted($ctype)) {
40 | continue;
41 | }
42 | $y[$pc->pc_index] = $pl->user->reviewer_text_for($pc);
43 | }
44 | ksort($y);
45 | return join("; ", $y);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/textformat.php:
--------------------------------------------------------------------------------
1 | format = $format;
21 | foreach ($settings as $k => $v) {
22 | $this->$k = $v;
23 | }
24 | }
25 | function description_text() {
26 | if ($this->description_text === null
27 | && $this->description !== null) {
28 | $this->description_text = Text::html_to_text($this->description);
29 | }
30 | return (string) $this->description_text;
31 | }
32 | function description_preview_html() {
33 | $d = [];
34 | if ((string) $this->description !== "") {
35 | $d[] = $this->description;
36 | } else if ((string) $this->description_text !== "") {
37 | $d[] = htmlspecialchars($this->description_text);
38 | }
39 | if ($this->has_preview) {
40 | $d[] = 'Preview ';
42 | }
43 | if ($d) {
44 | return ''
45 | . join(' · ', $d) . '
';
46 | } else {
47 | return "";
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/search/st_cmtafter.php:
--------------------------------------------------------------------------------
1 | user = $user;
17 | $this->cm = $cm;
18 | $this->time = $time;
19 | }
20 | static function parse($word, SearchWord $sword, PaperSearch $srch) {
21 | if (!preg_match('/\A(.++)(|(?:[!=<>]=?|≥|≤|≠)\d+)\z/', $word, $m)
22 | || ($t = $srch->conf->parse_time($m[1])) === false) {
23 | $srch->lwarning($sword, "<0>Expected ‘cmtafter:DATE’");
24 | return new False_SearchTerm;
25 | }
26 | $cm = new CountMatcher($m[2] !== "" ? $m[2] : ">0");
27 | return new CmtAfter_SearchTerm($srch->user, $cm, $t);
28 | }
29 | function sqlexpr(SearchQueryInfo $sqi) {
30 | $sqi->add_comment_signature_columns();
31 | if ($this->cm->test(0)) {
32 | return "true";
33 | }
34 | return "exists(select * from PaperComment where paperId=Paper.paperId and timeModified>={$this->time})";
35 | }
36 | function test(PaperInfo $row, $xinfo) {
37 | $n = 0;
38 | foreach ($row->viewable_comment_skeletons($this->user, true) as $crow) {
39 | if ($crow->mtime($this->user) >= $this->time)
40 | ++$n;
41 | }
42 | return $this->cm->test($n);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/search/st_badge.php:
--------------------------------------------------------------------------------
1 | user = $user;
15 | $this->word = $word;
16 | }
17 | function sqlexpr(SearchQueryInfo $sqi) {
18 | return 'exists (select * from PaperTag where paperId=Paper.paperId)';
19 | }
20 | function test(PaperInfo $row, $xinfo) {
21 | $tags = $row->viewable_tags($this->user);
22 | foreach ($row->conf->tags()->badges($tags) as $tb) {
23 | if ($this->word === "any" || $this->word === $tb[1])
24 | return true;
25 | }
26 | return false;
27 | }
28 | function debug_json() {
29 | return ["type" => $this->type, "style" => $this->word];
30 | }
31 | function about() {
32 | return self::ABOUT_PAPER;
33 | }
34 |
35 | static function parse($word, SearchWord $sword, PaperSearch $srch) {
36 | $word = strtolower($word);
37 | if ($word === "any" || $word === "none") {
38 | return (new Badge_SearchTerm($srch->user, "any"))->negate_if($word === "none");
39 | } else if (($ks = $srch->conf->tags()->known_badge($word))) {
40 | return new Badge_SearchTerm($srch->user, $ks->style);
41 | } else {
42 | $srch->lwarning($sword, "<0>Badge color ‘{$word}’ not found");
43 | return new False_SearchTerm;
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/search/st_realnumberoption.php:
--------------------------------------------------------------------------------
1 | compar = $relation;
15 | $this->value = $value;
16 | }
17 | function debug_json() {
18 | return [$this->type, $this->option->search_keyword()];
19 | }
20 | function test(PaperInfo $row, $xinfo) {
21 | return $this->user->can_view_option($row, $this->option)
22 | && ($ov = $row->option($this->option))
23 | && $ov->value !== null
24 | && CountMatcher::compare(floatval($ov->data()), $this->compar, $this->value);
25 | }
26 | function script_expression(PaperInfo $row, $about) {
27 | if (($about & self::ABOUT_PAPER) === 0) {
28 | return parent::script_expression($row, $about);
29 | } else if ($this->user->can_view_option($row, $this->option)) {
30 | if (($se = $this->option->value_script_expression())) {
31 | return ["type" => "compar", "child" => [$se, $this->value], "compar" => CountMatcher::unparse_relation($this->compar)];
32 | } else {
33 | return null;
34 | }
35 | } else {
36 | return false;
37 | }
38 | }
39 | function about() {
40 | return self::ABOUT_PAPER;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/test/oauth/private.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEpQIBAAKCAQEA5Q6puFDxKPiLjAkq4m4Y0hrmdM4rtYkdCiKbhtx7QHOxvdVU
3 | L9FC2TKduyQPIbPQtUO9h2N+gngLdpnszdD0Pkv6bMUu4OocJaR3TvXivverhtoc
4 | KJDWy77hKf2cAT+1AAz7TCP8RadKnGNzK1IFUigkIEQogoRiSqAaA1b5KXRsbRV/
5 | JsC5kf+z11ZkrBAL53phITtoc0i+SYa3NMMUINCFfjorrxzV2YswIvm9maY7Kyus
6 | tsrxbRt/74i6pQFlyaH3K9UP+KIrfK/Rl+2HdbG+XgDiWGL6HSyHw1ybx+RxZCkX
7 | 69vhyGDBe3tNjZSO876bVkHCJr5h6POCC+PDJwIDAQABAoIBAQCKFXDTIFiBbnQR
8 | k2U640wrPPQ47iEDawkKlxpTDo9up1A7NGNwACLgdNcJfg9xLclfvNqAx8X4OQ4Q
9 | DXLoEFNtSrhI4gYEqJ0XRDJ4c1qh7QSGYu4etlIGuadbfPuS9SjUQv8rQ3ZNNzCP
10 | XpSLRQLYKEK/ANe69ruaaTHFWaUTC2xrWtM0lSkgLIcdIicaIfEhyqdlFYQcoHqQ
11 | z2KwCDtOOv3pkJ31RWM0PpKray/wXmFlaI8lrGf7+HGPQYyXdd+XxtEeTGuCh5oZ
12 | 4L9pOc9c1ejMUZik4UeAk+V6XrzNlOBl8/W99RxelOK82ZxQDeSCdAV/phL+15gJ
13 | Ki6kZN2hAoGBAPJFA0bseptMYDfZagAF9AHAY6KNcrBqedD82ud863rETXWnrMws
14 | 8ghXVanoJCT9cUqFnO3lwwA0HEqZnECRatiQJjF8ZA3gqUtv2os3ocg3Jt8xrShx
15 | wLGETcj5mi1kFrPrn973VwVHYMX+IhIyOPhLUP7R4Ndy35b2Drg3U1JLAoGBAPIJ
16 | 9Vr9E8XasnzI3ALNtOncDihahgWlt4cKng1AzdueqvMxt9tp/TwEDP6O0z3fD/FF
17 | JyTys9M0y5gWlX6NziCxG+suvp+OolqpklF7h1Sfhbqw4t6V/PHwRa5x0MuraAw+
18 | 4+oSYoefL+xyC7OBcbGPNKsIpR4yhMi3AcRRoCkVAoGBAJSnngQl1HF4Is4CHNWY
19 | 0YlFmJ1Ed6wiGU8P5+4Eq6Tv0Kux0AiUR4qwtAKGS69ax+o3I/yhb86vKvDnYoYH
20 | 9Gyfvp+8uNP/F0IPhyTHZQCqPrLTE3HuopMKIISCC4VwlbGekcFJOV8m1g2HCzbp
21 | FCXeaPuCopjwhptlrdCBOiITAoGBAMzsL4ak5NvMSPgrm1LoVTb28CmsUvJvFw7H
22 | p39zEZfTI8uZmZ+0ggoRJ+tSg3lL5ZSRxw2aSzQT7BhNbq7iYtX8/bVGM3Cl88Gs
23 | 9kv0uWSlVzT0VHC+LpWsp2KFzJDUA9jyWkcw36kR1yJqgIuvmdIKfD4eqKYDgbbq
24 | cx2DOoXtAoGAN8DDBd4uUSWrJTSXE+i0OGPMmjh1Upx9R7ycFsv5d2CzAiyWnJ73
25 | 3uhl/VL7pSPFpljh4daA7T+bnCVs1yKQ5G8efXWlEJJoogzIgQU37c+T8NIvkfd3
26 | uXt6dejxKvWNHK/ydrUGxu3XJjpLJ7djohmC83TDa/asPKo1ZbL0stg=
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/.htaccess:
--------------------------------------------------------------------------------
1 | # Apache settings for HotCRP
2 |
3 | # These directives limit how large a paper can be uploaded.
4 | # post_max_size should be >= upload_max_filesize.
5 | #php_value upload_max_filesize 15M
6 | #php_value post_max_size 20M
7 |
8 | # Some pages involve a lot of post variables.
9 | #php_value max_input_vars 4096
10 |
11 | # A large memory_limit helps when sending very large zipped files.
12 | php_value memory_limit 128M
13 |
14 | # Default to UTF-8 (most scripts will override this with ).
15 | AddDefaultCharset UTF-8
16 |
17 | # Use index.php for directory access.
18 | DirectoryIndex index.php
19 |
20 | # Prevent access to SCM directory, logs, test, README, regardless of case.
21 | RedirectMatch 403 ^.*/(\..*|[Rr][Ee][Aa][Dd][Mm][Ee].*|[Ff][Ii][Ll][Ee][Ss][Tt][Oo][Rr][Ee]|[Dd][Oo][Cc][Ss]|[Cc][Oo][Nn][Ff]|[Cc][Oo][Dd][Ee]|[Ll][Oo][Gg][Ss])($|/.*$)
22 |
23 | # Don't use MultiViews, which can conflict with mod_rewrite suffixless URLs.
24 | Options -MultiViews
25 |
26 | # Add .php to suffixless URLs.
27 |
28 | RewriteEngine on
29 | RewriteBase /
30 | RewriteCond %{REQUEST_FILENAME}.php -f
31 | RewriteCond %{REQUEST_URI} ^(.*)$
32 | RewriteRule ^[^/]*$ %1.php [L,NE]
33 | RewriteCond %{REQUEST_FILENAME}.php -f
34 | RewriteCond %{REQUEST_URI},,$1,, ^(.*)(.*,,)\2$
35 | RewriteRule ^[^/]*(/.*)$ %1.php$1 [L,NE]
36 |
37 |
38 | # Uncomment this line to ONLY grant access via https. Requires mod_ssl.
39 | #
40 | # SSLRequireSSL
41 |
42 | # HTTP Authentication: To ask the server to authenticate users,
43 | # uncomment these lines and set $Opt["httpAuthLogin"] in
44 | # conf/options.php. The $Opt["httpAuthLogin"] value should correspond
45 | # to your AuthType and AuthName (AuthName is the "realm").
46 | #
47 | # AuthType Basic
48 | # AuthName "HotCRP"
49 | # AuthUserFile FILENAME
50 | # Require valid-user
51 |
--------------------------------------------------------------------------------
/src/search/st_perm.php:
--------------------------------------------------------------------------------
1 | user = $user;
14 | $this->perm = $perm;
15 | }
16 | static function parse($word, SearchWord $sword, PaperSearch $srch) {
17 | if (strcasecmp($word, "author-edit") === 0
18 | || strcasecmp($word, "author-write") === 0) {
19 | return new Perm_SearchTerm($srch->user, "author-write");
20 | } else if (strcasecmp($word, "author-edit-final") === 0
21 | || strcasecmp($word, "author-write-final") === 0) {
22 | return new Perm_SearchTerm($srch->user, "author-write-final");
23 | } else {
24 | $srch->lwarning($sword, "<0>Permission not found");
25 | return new False_SearchTerm;
26 | }
27 | }
28 | function sqlexpr(SearchQueryInfo $sqi) {
29 | if ($this->perm === "author-write-final") {
30 | return "(Paper.timeWithdrawn<=0 and Paper.outcome>0)";
31 | } else {
32 | return "(Paper.timeWithdrawn<=0)";
33 | }
34 | }
35 | function test(PaperInfo $row, $xinfo) {
36 | if ($this->perm === "author-write") {
37 | return $row->author_edit_state() !== 0;
38 | } else if ($this->perm === "author-write-final") {
39 | return $row->author_edit_state() === 2
40 | && $this->user->can_view_decision($row);
41 | } else {
42 | return false;
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/papercolumns/pc_administrator.php:
--------------------------------------------------------------------------------
1 | user->can_view_manager(null)) {
16 | return false;
17 | }
18 | $pl->conf->pc_set(); // prepare cache
19 | $this->nameflags = $this->user_view_option_name_flags($pl->conf);
20 | return true;
21 | }
22 | static private function cid(PaperList $pl, PaperInfo $row) {
23 | if ($row->managerContactId && $pl->user->can_view_manager($row)) {
24 | return $row->managerContactId;
25 | } else {
26 | return 0;
27 | }
28 | }
29 | function sort_name() {
30 | return $this->sort_name_with_options("format");
31 | }
32 | function compare(PaperInfo $a, PaperInfo $b, PaperList $pl) {
33 | $ianno = $this->nameflags & NAME_L ? Contact::SORTSPEC_LAST : Contact::SORTSPEC_FIRST;
34 | return $pl->user_compare(self::cid($pl, $a), self::cid($pl, $b), $ianno);
35 | }
36 | function content_empty(PaperList $pl, PaperInfo $row) {
37 | return !self::cid($pl, $row);
38 | }
39 | function content(PaperList $pl, PaperInfo $row) {
40 | return $pl->user_content($row->managerContactId, $row, $this->nameflags);
41 | }
42 | function text(PaperList $pl, PaperInfo $row) {
43 | return $pl->user_text($row->managerContactId, $this->nameflags);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/search/st_optionvalue.php:
--------------------------------------------------------------------------------
1 | compar = $relation;
15 | $this->value = $value;
16 | }
17 | function debug_json() {
18 | return [
19 | "type" => $this->type,
20 | "option" => $this->option->search_keyword(),
21 | "compar" => CountMatcher::unparse_relation($this->compar),
22 | "value" => $this->value
23 | ];
24 | }
25 | function test(PaperInfo $row, $xinfo) {
26 | return $this->user->can_view_option($row, $this->option)
27 | && ($ov = $row->option($this->option))
28 | && $ov->value !== null
29 | && CountMatcher::compare($ov->value, $this->compar, $this->value);
30 | }
31 | function script_expression(PaperInfo $row, $about) {
32 | if (($about & self::ABOUT_PAPER) === 0) {
33 | return parent::script_expression($row, $about);
34 | } else if (!$this->user->can_view_option($row, $this->option)) {
35 | return false;
36 | }
37 | if (($se = $this->option->value_script_expression())) {
38 | return ["type" => "compar", "child" => [$se, $this->value], "compar" => CountMatcher::unparse_relation($this->compar)];
39 | }
40 | return null;
41 | }
42 | function about() {
43 | return self::ABOUT_PAPER;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/settings/s_finalversions.php:
--------------------------------------------------------------------------------
1 | final versions page
3 | // Copyright (c) 2006-2022 Eddie Kohler; see LICENSE.
4 |
5 | class FinalVersions_SettingParser extends SettingParser {
6 | static function print(SettingValues $sv) {
7 | if ($sv->oldv("final_soft") === $sv->oldv("final_done")) {
8 | $sv->set_oldv("final_soft", null);
9 | }
10 | echo '';
11 | $sv->print_checkbox('final_open', '
Collect final versions of accepted submissions ', ["class" => "uich js-foldup", "group_class" => "form-g", "group_open" => true]);
12 | echo '
';
13 | $sv->print_entry_group("final_soft", "Deadline", ["horizontal" => true]);
14 | $sv->print_entry_group("final_done", "Hard deadline", ["horizontal" => true]);
15 | $sv->print_entry_group("final_grace", "Grace period", ["horizontal" => true]);
16 | echo '
';
17 | $sv->print_message_minor("final_edit_message", "Instructions");
18 | Banal_SettingParser::print("final", $sv);
19 | echo " \n\n";
20 | }
21 |
22 | static function crosscheck(SettingValues $sv) {
23 | if ($sv->has_interest("final_open")
24 | && $sv->oldv("final_open")
25 | && ($sv->oldv("final_soft") || $sv->oldv("final_done"))
26 | && (!$sv->oldv("final_done") || $sv->oldv("final_done") > Conf::$now)
27 | && !$sv->conf->time_all_author_view_decision()) {
28 | $sv->warning_at(null, "<5>The system is set to collect final versions, but authors cannot submit final versions until they can see decisions. You may want to update the " . $sv->setting_link("“Can authors see decisions” setting", "decision_visibility_author") . ".");
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/lib/collatorshim.php:
--------------------------------------------------------------------------------
1 | setAttribute(self::STRENGTH, $strength); // which does nothing
23 | }
24 | /** @param int $name
25 | * @param int $value */
26 | function setAttribute($name, $value) {
27 | if ($name === self::NUMERIC_COLLATION) {
28 | $this->numeric = $value;
29 | }
30 | }
31 | /** @param string $a
32 | * @param string $b
33 | * @return -1|0|1 */
34 | function compare($a, $b) {
35 | if ($this->numeric) {
36 | return strnatcasecmp($a, $b);
37 | } else {
38 | return strcasecmp($a, $b);
39 | }
40 | }
41 | /** @param list &$v */
42 | function sort(&$v) {
43 | if ($this->numeric) {
44 | sort($v, SORT_NATURAL | SORT_FLAG_CASE);
45 | } else {
46 | sort($v, SORT_FLAG_CASE);
47 | }
48 | }
49 | /** @param array &$v */
50 | function asort(&$v) {
51 | if ($this->numeric) {
52 | asort($v, SORT_NATURAL | SORT_FLAG_CASE);
53 | } else {
54 | asort($v, SORT_FLAG_CASE);
55 | }
56 | }
57 | }
58 |
59 | if (!class_exists("Collator", false)) {
60 | class_alias("CollatorShim", "Collator");
61 | }
62 |
--------------------------------------------------------------------------------
/lib/memoryqsession.php:
--------------------------------------------------------------------------------
1 | */
7 | private $a;
8 |
9 | /** @param ?string $sid
10 | * @param array $a */
11 | function __construct($sid = null, $a = []) {
12 | $this->sid = $sid ?? "sess_" . base64_encode(random_bytes(15));
13 | $this->sopen = true;
14 | $this->a = $a;
15 | }
16 |
17 | function all() {
18 | return $this->a;
19 | }
20 |
21 | function clear() {
22 | assert($this->sopen);
23 | $this->a = [];
24 | }
25 |
26 | function has($key) {
27 | return $this->sopen && isset($this->a[$key]);
28 | }
29 |
30 | function get($key) {
31 | return $this->sopen ? $this->a[$key] ?? null : null;
32 | }
33 |
34 | function set($key, $value) {
35 | assert($this->sopen);
36 | $this->a[$key] = $value;
37 | }
38 |
39 | function unset($key) {
40 | assert($this->sopen);
41 | unset($this->a[$key]);
42 | }
43 |
44 | function has2($key1, $key2) {
45 | return $this->sopen && isset($this->a[$key1][$key2]);
46 | }
47 |
48 | function get2($key1, $key2) {
49 | return $this->sopen ? $this->a[$key1][$key2] ?? null : null;
50 | }
51 |
52 | function set2($key1, $key2, $value) {
53 | assert($this->sopen);
54 | $this->a[$key1][$key2] = $value;
55 | }
56 |
57 | function unset2($key1, $key2) {
58 | assert($this->sopen);
59 | if (isset($this->a[$key1])) {
60 | unset($this->a[$key1][$key2]);
61 | if (empty($this->a[$key1])) {
62 | unset($this->a[$key1]);
63 | }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/autoassigners/aa_clear.php:
--------------------------------------------------------------------------------
1 | $pcids
10 | * @param list $papersel
11 | * @param array $subreq
12 | * @param object $gj */
13 | function __construct(Contact $user, $pcids, $papersel, $subreq, $gj) {
14 | parent::__construct($user, $pcids, $papersel);
15 | $t = $gj->type ?? $subreq["type"] ?? null;
16 | if (in_array($t, ["conflict", "lead", "shepherd"], true)) {
17 | $this->type = $t;
18 | } else if (is_string($t) && ($x = ReviewInfo::parse_type($t, true))) {
19 | $this->type = $x;
20 | } else {
21 | $this->error_at("type", "<0>Expected review type, ‘conflict’, ‘lead’, or ‘shepherd’");
22 | }
23 | }
24 |
25 | function run() {
26 | if (is_int($this->type)) {
27 | $q = "select paperId, contactId from PaperReview where reviewType=" . $this->type;
28 | $action = "noreview";
29 | } else if ($this->type === "conflict") {
30 | $q = "select paperId, contactId from PaperConflict where conflictType>" . CONFLICT_MAXUNCONFLICTED . " and conflictType<" . CONFLICT_AUTHOR;
31 | $action = "noconflict";
32 | } else {
33 | $q = "select paperId, {$this->type}ContactId from Paper where {$this->type}ContactId!=0";
34 | $action = "no" . $this->type;
35 | }
36 | $this->set_assignment_action($action);
37 | $result = $this->conf->qe_raw($q);
38 | while (($row = $result->fetch_row())) {
39 | $this->assign1((int) $row[1], (int) $row[0]);
40 | }
41 | Dbl::free($result);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/papercolumns/pc_lead.php:
--------------------------------------------------------------------------------
1 | override = PaperColumn::OVERRIDE_IFEMPTY;
11 | }
12 | function view_option_schema() {
13 | return self::user_view_option_schema();
14 | }
15 | function prepare(PaperList $pl, $visible) {
16 | if (!$pl->user->can_view_lead(null)
17 | || (!$pl->conf->has_any_lead_or_shepherd() && !$visible)) {
18 | return false;
19 | }
20 | $pl->conf->pc_set(); // prepare cache
21 | $this->nameflags = $this->user_view_option_name_flags($pl->conf);
22 | return true;
23 | }
24 | static private function cid(PaperList $pl, PaperInfo $row) {
25 | if ($row->leadContactId > 0 && $pl->user->can_view_lead($row)) {
26 | return $row->leadContactId;
27 | } else {
28 | return 0;
29 | }
30 | }
31 | function sort_name() {
32 | return $this->sort_name_with_options("format");
33 | }
34 | function compare(PaperInfo $a, PaperInfo $b, PaperList $pl) {
35 | $ianno = $this->nameflags & NAME_L ? Contact::SORTSPEC_LAST : Contact::SORTSPEC_FIRST;
36 | return $pl->user_compare(self::cid($pl, $a), self::cid($pl, $b), $ianno);
37 | }
38 | function content_empty(PaperList $pl, PaperInfo $row) {
39 | return !self::cid($pl, $row);
40 | }
41 | function content(PaperList $pl, PaperInfo $row) {
42 | return $pl->user_content($row->leadContactId, $row, $this->nameflags);
43 | }
44 | function text(PaperList $pl, PaperInfo $row) {
45 | return $pl->user_text($row->leadContactId, $this->nameflags);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/search/st_paperpc.php:
--------------------------------------------------------------------------------
1 | user = $user;
15 | $this->kind = $kind;
16 | $this->fieldname = $kind . "ContactId";
17 | $this->match = $match;
18 | }
19 | static function parse($word, SearchWord $sword, PaperSearch $srch) {
20 | if (($word === "any" || $word === "" || $word === "yes") && !$sword->quoted) {
21 | $match = "!=0";
22 | } else if (($word === "none" || $word === "no") && !$sword->quoted) {
23 | $match = "=0";
24 | } else {
25 | $match = $srch->matching_uids($word, $sword->quoted, true);
26 | }
27 | // XXX what about track admin privilege?
28 | $qt = [new PaperPC_SearchTerm($srch->user, $sword->kwdef->pcfield, $match)];
29 | if ($sword->kwdef->pcfield === "manager"
30 | && $word === "me"
31 | && !$sword->quoted
32 | && $srch->user->privChair) {
33 | $qt[] = new PaperPC_SearchTerm($srch->user, $qt[0]->kind, "=0");
34 | }
35 | return $qt;
36 | }
37 | function sqlexpr(SearchQueryInfo $sqi) {
38 | $sqi->add_column($this->fieldname, "Paper.{$this->fieldname}");
39 | return "(Paper.{$this->fieldname}" . CountMatcher::sqlexpr_using($this->match) . ")";
40 | }
41 | function test(PaperInfo $row, $xinfo) {
42 | $can_view = "can_view_{$this->kind}";
43 | return $this->user->$can_view($row)
44 | && CountMatcher::compare_using($row->{$this->fieldname}, $this->match);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/lib/gmpshim.php:
--------------------------------------------------------------------------------
1 | = 8 ? 6 : 5);
7 | const GMPSHIM_INT_SIZE = 1 << GMPSHIM_INT_SHIFT;
8 |
9 | class GMPShim {
10 | static function init($v) {
11 | return [(int) $v];
12 | }
13 |
14 | static function clrbit(&$a, $index) {
15 | assert($index >= 0);
16 | $i = $index >> GMPSHIM_INT_SHIFT;
17 | if ($i < count($a)) {
18 | $j = $index & (GMPSHIM_INT_SIZE - 1);
19 | $a[$i] &= ~(1 << $j);
20 | }
21 | }
22 |
23 | static function setbit(&$a, $index, $bit_on = true) {
24 | assert($index >= 0);
25 | $i = $index >> GMPSHIM_INT_SHIFT;
26 | if ($bit_on || $i < count($a)) {
27 | while ($i >= count($a)) {
28 | $a[] = 0;
29 | }
30 | $j = $index & (GMPSHIM_INT_SIZE - 1);
31 | if ($bit_on) {
32 | $a[$i] |= 1 << $j;
33 | } else {
34 | $a[$i] &= ~(1 << $j);
35 | }
36 | }
37 | }
38 |
39 | static function testbit($a, $index) {
40 | assert($index >= 0);
41 | $i = $index >> GMPSHIM_INT_SHIFT;
42 | $j = $index & (GMPSHIM_INT_SIZE - 1);
43 | return $i < count($a) && ($a[$i] & (1 << $j)) !== 0;
44 | }
45 |
46 | static function scan1($a, $start) {
47 | assert($start >= 0);
48 | $i = $start >> GMPSHIM_INT_SHIFT;
49 | $j = $start & (GMPSHIM_INT_SIZE - 1);
50 | while ($i < count($a)) {
51 | $v = $a[$i];
52 | while ($j < GMPSHIM_INT_SIZE) {
53 | if ($v & (1 << $j)) {
54 | return ($i << GMPSHIM_INT_SHIFT) | $j;
55 | }
56 | ++$j;
57 | }
58 | $j = 0;
59 | ++$i;
60 | }
61 | return -1;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/settings/s_messages.php:
--------------------------------------------------------------------------------
1 | messages page
3 | // Copyright (c) 2006-2022 Eddie Kohler; see LICENSE.
4 |
5 | class Messages_SettingParser extends SettingParser {
6 | function default_value(Si $si, SettingValues $sv) {
7 | if ($si->name === "preference_instructions") {
8 | $targ = new FmtArg("topics", !!$sv->oldv("has_topics"));
9 | $t = $sv->conf->fmt()->default_translation("revprefdescription", $targ);
10 | return Ftext::as(5, $t);
11 | }
12 | return null;
13 | }
14 | function apply_req(Si $si, SettingValues $sv) {
15 | if ($si->name === "submission_terms" || $si->name === "review_terms") {
16 | if (($v = $sv->base_parse_req($si)) !== null) {
17 | $sv->save("{$si->name}_exist", $v !== "" ? 1 : 0);
18 | }
19 | }
20 | return false;
21 | }
22 | static function print_submissions(SettingValues $sv) {
23 | $sv->print_message("home_message", "Home page message");
24 | $sv->print_message("submission_terms", "Clickthrough submission terms",
25 | "Users must “accept” these terms to edit a submission. Use HTML and include a headline, such as “<h2>Submission terms</h2>”.
");
26 | $sv->print_message("submission_edit_message", "Submission message",
27 | "This message will appear on submission editing pages.
");
28 | }
29 | static function print_reviews(SettingValues $sv) {
30 | $sv->print_message("review_terms", "Clickthrough reviewing terms",
31 | "Users must “accept” these terms to edit a review. Use HTML and include a headline, such as “<h2>Submission terms</h2>”.
");
32 | $sv->print_message("conflict_description", "Definition of conflict of interest");
33 | $sv->print_message("preference_instructions", "Review preference instructions");
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/search/st_reconflict.php:
--------------------------------------------------------------------------------
1 | add_range((int) $m[1], (int) $m[2]);
12 | } else {
13 | $st->add_range((int) $m[1], (int) $m[1]);
14 | }
15 | $xword = $m[3];
16 | }
17 | if ($xword !== "" || $st->is_empty()) {
18 | $srch->lwarning($sword, "<0>List of paper numbers expected");
19 | return new False_SearchTerm;
20 | }
21 |
22 | $old_overrides = $srch->user->add_overrides(Contact::OVERRIDE_CONFLICT);
23 | $cids = [];
24 | foreach ($srch->user->paper_set([
25 | "paperId" => $st,
26 | "reviewSignatures" => true,
27 | "finalized" => $srch->limit_term()->is_submitted()
28 | ]) as $prow) {
29 | if ($srch->user->can_view_paper($prow)) {
30 | foreach ($prow->all_reviews() as $rrow) {
31 | if ($rrow->reviewToken === 0
32 | && $srch->user->can_view_review_identity($prow, $rrow)) {
33 | $cids[$rrow->contactId] = true;
34 | }
35 | }
36 | }
37 | }
38 | $srch->user->set_overrides($old_overrides);
39 |
40 | if (!empty($cids)) {
41 | return new Conflict_SearchTerm($srch->user, new ContactCountMatcher(">0", array_keys($cids)), false);
42 | } else {
43 | $srch->lwarning($sword, "<0>No visible reviewers");
44 | return new False_SearchTerm;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/formulas/f_pref.php:
--------------------------------------------------------------------------------
1 | kwdef->is_expertise : $ff;
10 | parent::__construct($is_expertise ? "prefexp" : "pref");
11 | $this->is_expertise = $is_expertise;
12 | $this->set_format($is_expertise ? Fexpr::FPREFEXPERTISE : Fexpr::FNUMERIC);
13 | if (is_object($ff) && $ff->modifier) {
14 | $this->cids = $ff->modifier;
15 | }
16 | }
17 | static function parse_modifier(FormulaCall $ff, $arg, Formula $formula) {
18 | if ($ff->modifier !== false || str_starts_with($arg, ".")) {
19 | return false;
20 | }
21 | if (str_starts_with($arg, ":")) {
22 | $arg = substr($arg, 1);
23 | }
24 | $csm = ContactSearch::make_pc($arg, $formula->user);
25 | if (!$csm->has_error()) {
26 | $ff->modifier = $csm->user_ids();
27 | return true;
28 | }
29 | return false;
30 | }
31 | function inferred_index() {
32 | return Fexpr::IDX_PC;
33 | }
34 | function viewable_by(Contact $user) {
35 | return $user->isPC;
36 | }
37 | function compile(FormulaCompiler $state) {
38 | if (!$state->user->is_reviewer()) {
39 | return "null";
40 | }
41 | $state->queryOptions["allReviewerPreference"] = true;
42 | $pref = $state->_add_preferences();
43 | $cid = $state->loop_cid(!$this->cids);
44 | $condition = "isset({$pref}[{$cid}])";
45 | if ($this->cids) {
46 | $condition .= " && in_array({$cid}, [" . join(",", $this->cids) . "], true)";
47 | }
48 | $property = $this->is_expertise ? "expertise" : "preference";
49 | return "({$condition} ? {$pref}[{$cid}]->{$property} : null)";
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/search/st_paperstatus.php:
--------------------------------------------------------------------------------
1 | match = $match;
11 | }
12 | static function parse($word, SearchWord $sword, PaperSearch $srch) {
13 | $fval = PaperSearch::status_field_matcher($srch->conf, $word, $sword->quoted);
14 | if (is_array($fval[1]) && empty($fval[1])) {
15 | $srch->lwarning($sword, "<0>Submission status ‘{$word}’ not found");
16 | $fval[1][] = -10000000;
17 | }
18 | if ($fval[0] === "outcome") {
19 | return new Decision_SearchTerm($srch->user, $fval[1]);
20 | } else {
21 | if ($srch->limit_term()->is_submitted()
22 | && ($fval[0] !== "timeSubmitted" || $fval[1] !== ">0")) {
23 | $srch->lwarning($sword, "<0>Matches nothing because this search is limited to completed submissions");
24 | }
25 | return new PaperStatus_SearchTerm($fval);
26 | }
27 | }
28 | function sqlexpr(SearchQueryInfo $sqi) {
29 | $q = [];
30 | for ($i = 0; $i < count($this->match); $i += 2) {
31 | $sqi->add_column($this->match[$i], "Paper." . $this->match[$i]);
32 | $q[] = "Paper." . $this->match[$i] . CountMatcher::sqlexpr_using($this->match[$i+1]);
33 | }
34 | return self::andjoin_sqlexpr($q);
35 | }
36 | function is_sqlexpr_precise() {
37 | return true;
38 | }
39 | function test(PaperInfo $row, $xinfo) {
40 | for ($i = 0; $i < count($this->match); $i += 2) {
41 | if (!CountMatcher::compare_using($row->{$this->match[$i]}, $this->match[$i+1]))
42 | return false;
43 | }
44 | return true;
45 | }
46 | function about() {
47 | return self::ABOUT_PAPER;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lib/filer.php:
--------------------------------------------------------------------------------
1 | curlh, CURLOPT_CUSTOMREQUEST, "GET");
14 | curl_setopt($clib->curlh, CURLOPT_URL, "{$clib->site}/whoami");
15 | if (!$clib->exec_api(null)
16 | || !($clib->content_json->ok ?? false)) {
17 | return 1;
18 | }
19 | if (!$clib->quiet) {
20 | if ($this->email || $this->roles) {
21 | $t = $clib->content_json->email ?? null;
22 | if (!is_string($t)) {
23 | $t = "__unknown__";
24 | }
25 | if ($this->roles && isset($clib->content_json->roles)) {
26 | $t .= " [" . join(" ", $clib->content_json->roles) . "]";
27 | }
28 | } else {
29 | $t = "Success";
30 | }
31 | $clib->set_output("{$t}\n");
32 | }
33 | return 0;
34 | }
35 |
36 | /** @return Test_CLIBatch */
37 | static function make_arg(Hotcrapi_Batch $clib, Getopt $getopt, $arg) {
38 | $tb = new Test_CLIBatch;
39 | $tb->email = isset($arg["email"]);
40 | $tb->roles = isset($arg["roles"]);
41 | return $tb;
42 | }
43 |
44 | static function register(Hotcrapi_Batch $clib, Getopt $getopt) {
45 | $getopt->subcommand_description(
46 | "test",
47 | "Test API connection
48 | Usage: php batch/hotcrapi.php test"
49 | )->long(
50 | "j,json !test Output JSON",
51 | "email !test Output associated email",
52 | "roles !test Output associated email and roles"
53 | );
54 | $clib->register_command("test", "Test_CLIBatch");
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/devel/manual/css.md:
--------------------------------------------------------------------------------
1 | # HotCRP CSS
2 |
3 | This page documents aspects of HotCRP styles not obvious from the style file
4 | itself.
5 |
6 | ## `z-index`
7 |
8 | Page-level stacking contexts
9 |
10 | * `#p-tracker`: 8
11 | * `#p-page`: 0
12 | * `#p-footer`: 0
13 |
14 | Values meaningful within page stacking contexts, especially `#p-page`
15 |
16 | Generic values
17 |
18 | * `.modal.transparent`: 10
19 | * `#h-actas`, `.dropmenu-container`: 12 (must be > `.modal.transparent`)
20 | * `.modal`: 14
21 | * `.modal-dialog`: 16 (must be > `.modal`)
22 | * `.bubble`: 20
23 |
24 | * `body.page #p-header`: 3 (to occlude `.pslcard-nav`)
25 | * `.pspcard`: 3 (to occlude `.pslcard-nav`)
26 | * `.pslcard-nav`: (0)
27 | * `.pslcard-home`: -1
28 | * `.home-sidebar`: 1
29 | * `button:hover`, `button:focus`, etc.: 1
30 | * `.longtext-fader`: 1
31 | * `.longtext-expander`: 2
32 | * `.overlong-content`: 1
33 | * `.overlong-collapsed > .overlong-content`: 0
34 | * `.overlong-collapsed > .overlong-divider > .overlong-mark`: 2
35 | * `.cmtcard.edit.popout`: 4
36 |
37 | ## `id`
38 |
39 | * Any `id` starting with `[a-z][-_]` is reserved for HotCRP use
40 | * Paper and review fields cannot follow that pattern
41 | * Paper and review fields also must not match JSON keys used for papers
42 | and reviews
43 | * `id^=t-` defines the page type; it is only set on the `` element
44 | * `id^=p-` is for page-level elements
45 | * `#p-tracker` (optional)
46 | * `#p-page`
47 | * `#p-header`
48 | * `#p-body`
49 | * `#p-footer`
50 | * `id^=i-` is for icons
51 | * `id^=f-` is for forms
52 | * `id^=h-` is for header elements
53 | * `#h-actas`
54 | * `#h-site`
55 | * `#h-page`
56 | * `#h-right`
57 | * `#h-deadline`
58 | * `#h-messages`
59 | * `#h-usermenu`
60 | * `#h-usermenubutton`
61 | * `id^=n-` is for navigation elements (quicklinks)
62 | * `#n-next`
63 | * `#n-prev`
64 | * `#n-search`
65 | * `#n-list`
66 | * `id^=k-` is for inputs and for programmatically assigned IDs, e.g., elements
67 | that need IDs for reference by `label`
68 |
--------------------------------------------------------------------------------
/devel/apidoc/search.md:
--------------------------------------------------------------------------------
1 | # Search
2 |
3 | These endpoints perform searches on submissions.
4 |
5 |
6 | # get /search
7 |
8 | > Retrieve search results
9 |
10 | Return IDs of submissions that match a search.
11 |
12 | Pass the search query in the `q` parameter. The list of matching IDs is
13 | returned in the `ids` response property.
14 |
15 | The `t`, `qt`, `reviewer`, `sort`, and `scoresort` parameters can also affect
16 | the search. `t` defines the collection of submissions to search. `t=viewable`
17 | checks all submissions the user can view; the default collection is often
18 | narrower (a typical default is `t=s`, which searches complete submissions).
19 |
20 | The `groups` response property is an array of annotations that apply to the
21 | search, and is returned for `THEN` searches, `LEGEND` searches, and searches
22 | on tags with annotations. Each annotation contains a position `pos`, and may
23 | also have a `legend`, a `search`, an `annoid`, and other properties. `pos` is
24 | an integer index into the `ids` array; it ranges from 0 to the number of items
25 | in that array. Annotations with a given `pos` should appear *before* the paper
26 | at that index in the `ids` array. For instance, this response might be
27 | returned for the search `10-12 THEN 15-18`:
28 |
29 | ```json
30 | {
31 | "ok": true,
32 | "message_list": [],
33 | "ids": [10, 12, 18],
34 | "groups": [
35 | {
36 | "pos": 0,
37 | "legend": "10-12",
38 | "search": "10-12"
39 | },
40 | {
41 | "pos": 2,
42 | "legend": "15-18",
43 | "search": "15-18"
44 | }
45 | ]
46 | }
47 | ```
48 |
49 | * response_schema search_response
50 |
51 |
52 | # get /fieldhtml
53 |
54 | > Retrieve search results as field HTML
55 |
56 |
57 | # get /fieldtext
58 |
59 | > Retrieve list field text
60 |
61 |
62 | # get /searchactions
63 |
64 | > Retrieve available search actions
65 |
66 |
67 | # get /searchaction
68 |
69 | > Perform search action
70 |
71 |
72 | # post /searchaction
73 |
74 | > Perform search action
75 |
--------------------------------------------------------------------------------
/src/search/st_documentcount.php:
--------------------------------------------------------------------------------
1 | compar = CountMatcher::parse_relation($compar);
15 | $this->value = $value;
16 | }
17 | function debug_json() {
18 | return [$this->type, $this->option->search_keyword(), CountMatcher::unparse_relation($this->compar), $this->value];
19 | }
20 | function sqlexpr(SearchQueryInfo $sqi) {
21 | $sqi->add_options_columns();
22 | return CountMatcher::compare(0, $this->compar, $this->value) ? "true" : parent::sqlexpr($sqi);
23 | }
24 | function test(PaperInfo $row, $xinfo) {
25 | if ($this->user->can_view_option($row, $this->option)
26 | && ($ov = $row->option($this->option))) {
27 | $n = count($this->option->value_dids($ov));
28 | } else {
29 | $n = 0;
30 | }
31 | return CountMatcher::compare($n, $this->compar, $this->value);
32 | }
33 | function script_expression(PaperInfo $row, $about) {
34 | if (($about & self::ABOUT_PAPER) !== 0) {
35 | return parent::script_expression($row, $about);
36 | } else if ($this->user->can_view_option($row, $this->option)) {
37 | return [
38 | "type" => "compar",
39 | "child" => [$this->option->present_script_expression(), $this->value],
40 | "compar" => CountMatcher::unparse_relation($this->compar)
41 | ];
42 | } else {
43 | return CountMatcher::compare(0, $this->compar, $this->value);
44 | }
45 | }
46 | function about() {
47 | return self::ABOUT_PAPER;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/formulas/f_topic.php:
--------------------------------------------------------------------------------
1 | */
7 | private $match;
8 | function __construct(FormulaCall $ff, Formula $formula) {
9 | parent::__construct($ff);
10 | if ($ff->modifier === null || $ff->modifier === [-1]) {
11 | $this->match = true;
12 | $this->set_format(Fexpr::FNUMERIC);
13 | } else {
14 | $this->match = $ff->modifier;
15 | if (count($this->match) <= 1 || $this->match[0] === 0) {
16 | $this->set_format(Fexpr::FBOOL);
17 | }
18 | }
19 | }
20 | static function parse_modifier(FormulaCall $ff, $arg, Formula $formula) {
21 | if ($ff->modifier !== null || str_starts_with($arg, ".")) {
22 | return false;
23 | }
24 | if (str_starts_with($arg, ":")) {
25 | $arg = substr($arg, 1);
26 | }
27 | $ff->modifier = $formula->conf->topic_set()->find_all(SearchWord::unquote($arg));
28 | // XXX warn if no match
29 | return true;
30 | }
31 | function paper_options(&$oids) {
32 | $oids[PaperOption::TOPICSID] = true;
33 | }
34 | function compile(FormulaCompiler $state) {
35 | $state->queryOptions["topics"] = true;
36 | $texpr = $state->_prow() . "->topic_list()";
37 | if ($this->match === true) {
38 | return "count({$texpr})";
39 | } else if ($this->match === []) {
40 | return "false";
41 | }
42 | $none = $this->match[0] === 0;
43 | $ts = $none ? array_slice($this->match, 1) : $this->match;
44 | if ($ts === []) {
45 | return "empty({$texpr})";
46 | } else if (count($ts) === 1) {
47 | return ($none ? "!" : "") . "in_array({$ts[0]}, {$texpr}, true)";
48 | } else {
49 | return ($none ? "empty" : "count") . "(array_intersect({$texpr}," . json_encode($ts) . "))";
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/lib/phpqsession.php:
--------------------------------------------------------------------------------
1 | = 20
9 | && strlen($sid) <= 128
10 | && (ctype_alnum($sid) || preg_match('/\A[-,0-9A-Za-z]+\z/', $sid))) {
11 | session_id($sid);
12 | }
13 | session_start();
14 | $this->set_start_sid(session_id());
15 | }
16 |
17 | function new_sid() {
18 | return session_create_id();
19 | }
20 |
21 | function commit() {
22 | if ($this->sopen) {
23 | session_commit();
24 | $this->sopen = false;
25 | }
26 | }
27 |
28 | function all() {
29 | return $_SESSION;
30 | }
31 |
32 | function clear() {
33 | assert($this->sopen);
34 | $_SESSION = [];
35 | }
36 |
37 | function has($key) {
38 | return $this->sopen && isset($_SESSION[$key]);
39 | }
40 |
41 | function get($key) {
42 | return $this->sopen ? $_SESSION[$key] ?? null : null;
43 | }
44 |
45 | function set($key, $value) {
46 | assert($this->sopen);
47 | $_SESSION[$key] = $value;
48 | }
49 |
50 | function unset($key) {
51 | assert($this->sopen);
52 | unset($_SESSION[$key]);
53 | }
54 |
55 | function has2($key1, $key2) {
56 | return $this->sopen && isset($_SESSION[$key1][$key2]);
57 | }
58 |
59 | function get2($key1, $key2) {
60 | return $this->sopen ? $_SESSION[$key1][$key2] ?? null : null;
61 | }
62 |
63 | function set2($key1, $key2, $value) {
64 | assert($this->sopen);
65 | $_SESSION[$key1][$key2] = $value;
66 | }
67 |
68 | function unset2($key1, $key2) {
69 | assert($this->sopen);
70 | if (isset($_SESSION[$key1])) {
71 | unset($_SESSION[$key1][$key2]);
72 | if (empty($_SESSION[$key1])) {
73 | unset($_SESSION[$key1]);
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/help/h_votetags.php:
--------------------------------------------------------------------------------
1 | example_tag(TagInfo::TF_ALLOTMENT) ?? "vote";
8 | echo "Some conferences have PC members vote for papers. In
9 | allotment voting , each PC member is assigned a vote allotment to
10 | distribute among unconflicted papers; a PC member might assign one vote to one
11 | submission and five to another. In approval voting , each PC member
12 | can vote for as many papers as they like. The PC’s aggregated vote totals
13 | might help determine which papers to discuss.
14 |
15 | HotCRP supports voting through ", $hth->help_link("tags", "tags"), ".
16 | The chair can ", $hth->setting_link("define a set of voting tags", "tag_vote_allotment"),
17 | " and allotments" . $hth->tag_settings_having_note(TagInfo::TF_ALLOTMENT) . ".
18 | Votes are represented as twiddle tags, and the vote total is automatically
19 | computed and shown in the public tag.
20 |
21 | For example, an administrator might define an allotment voting tag
22 | “#". $votetag . "” with an allotment of 10 votes.
23 | To assign two votes to a submission, a PC member can either enter that vote
24 | into a text box on the submission page, or directly tag that submission with
25 | “#~". $votetag . "#2”.
26 | As other PC members add their votes with their own “#~vote” tags, the system
27 | updates the main “#vote” tag to reflect the total.
28 | (An error is reported when PC members exceed their allotment.)
29 |
30 | To see papers’ vote counts in a list, search for ",
31 | $hth->search_link("show:#{$votetag}"),
32 | ". To list the papers with votes, sorted by vote count (most votes first),
33 | search for ", $hth->search_link("rorder:#{$votetag}"), " or ",
34 | $hth->search_link("rorder:#{$votetag} show:#{$votetag}"), ".
35 |
36 | Hover to learn how the PC voted:
37 |
38 | " . Ht::img("extagvotehover.png", "[Hovering over a voting tag]", ["width" => 390, "height" => 46]) . "
";
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/search/st_sclass.php:
--------------------------------------------------------------------------------
1 | sr = $sr;
16 | $this->negate = $negate;
17 | }
18 |
19 | static function parse($word, SearchWord $sword, PaperSearch $srch) {
20 | $tagger = new Tagger($srch->user);
21 | $tag = $tagger->check($word, Tagger::ALLOWRESERVED | Tagger::NOPRIVATE | Tagger::NOCHAIR);
22 | if ($tag === false) {
23 | $srch->lwarning($sword, $tagger->error_ftext(true));
24 | return new False_SearchTerm;
25 | }
26 |
27 | if (strcasecmp($tag, "any") === 0) {
28 | return new Sclass_SearchTerm($srch->conf->unnamed_submission_round(), true);
29 | } else if (($sr = $srch->conf->submission_round_by_tag($tag, true))) {
30 | return new Sclass_SearchTerm($sr, false);
31 | } else {
32 | $srch->lwarning($sword, $srch->conf->_("<0>{Submission} class ‘{}’ not found", $tag));
33 | return new False_SearchTerm;
34 | }
35 | }
36 | function sqlexpr(SearchQueryInfo $sqi) {
37 | if (!$this->sr->unnamed) {
38 | return Dbl::format_query($sqi->srch->conf->dblink,
39 | "exists (select * from PaperTag where paperId=Paper.paperId and tag=?)",
40 | $this->sr->tag);
41 | } else {
42 | return "true";
43 | }
44 | }
45 | function test(PaperInfo $row, $xinfo) {
46 | return ($row->submission_round() === $this->sr) !== $this->negate;
47 | }
48 | function debug_json() {
49 | return ["type" => $this->type, "sclass" => $this->negate ? "any" : $this->sr->tag];
50 | }
51 | function about() {
52 | return self::ABOUT_PAPER;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/search/st_decision.php:
--------------------------------------------------------------------------------
1 | */
9 | private $match;
10 |
11 | /** @param string|list $match */
12 | function __construct(Contact $user, $match) {
13 | parent::__construct("decision");
14 | $this->user = $user;
15 | $this->match = $match;
16 | }
17 | static function parse($word, SearchWord $sword, PaperSearch $srch) {
18 | $dec = $srch->conf->decision_set()->matchexpr($word);
19 | if (is_array($dec) && empty($dec)) {
20 | $srch->lwarning($sword, "<0>Decision not found");
21 | $dec[] = -10000000;
22 | }
23 | return new Decision_SearchTerm($srch->user, $dec);
24 | }
25 | /** @return string|list */
26 | function matchexpr() {
27 | return $this->match;
28 | }
29 | function sqlexpr(SearchQueryInfo $sqi) {
30 | $f = ["Paper.outcome" . CountMatcher::sqlexpr_using($this->match)];
31 | if (CountMatcher::compare_using(0, $this->match)
32 | && !$this->user->allow_administer_all()) {
33 | $f[] = "Paper.outcome=0";
34 | }
35 | return "(" . join(" or ", $f) . ")";
36 | }
37 | function test(PaperInfo $row, $xinfo) {
38 | $d = $this->user->can_view_decision($row) ? $row->outcome : 0;
39 | return CountMatcher::compare_using($d, $this->match);
40 | }
41 | function about() {
42 | return self::ABOUT_PAPER;
43 | }
44 | function drag_assigners(Contact $user) {
45 | $ds = $user->conf->decision_set()->filter_using($this->match);
46 | if (count($ds) !== 1 || !$user->can_set_some_decision()) {
47 | return null;
48 | }
49 | return [
50 | ["action" => "decision", "decision" => $ds[0]->name, "ondrag" => "enter"],
51 | ["action" => "decision", "decision" => "none", "ondrag" => "leave"]
52 | ];
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/papercolumns/pc_preferencelist.php:
--------------------------------------------------------------------------------
1 | topics = !!($cj->topics ?? false);
11 | $this->override = PaperColumn::OVERRIDE_IFEMPTY_LINK;
12 | }
13 | function view_option_schema() {
14 | return ["topics", "topic/topics"];
15 | }
16 | function prepare(PaperList $pl, $visible) {
17 | if (!$pl->user->is_manager()) {
18 | return false;
19 | }
20 | $this->topics = ($this->view_option("topics") ?? $this->topics)
21 | && $pl->conf->has_topics();
22 | if ($visible) {
23 | $pl->qopts["allReviewerPreference"] = true;
24 | if ($this->topics) {
25 | $pl->qopts["topics"] = true;
26 | }
27 | $pl->conf->stash_hotcrp_pc($pl->user);
28 | }
29 | return true;
30 | }
31 | function content_empty(PaperList $pl, PaperInfo $row) {
32 | return !$pl->user->can_administer($row);
33 | }
34 | function content(PaperList $pl, PaperInfo $row) {
35 | $ts = [];
36 | if ($this->topics || $row->preferences()) {
37 | foreach ($row->conf->pc_members() as $pcid => $pc) {
38 | $pf = $row->preference($pc);
39 | if ($pf->exists()) {
40 | $ts[] = "{$pcid}P{$pf->preference}" . unparse_expertise($pf->expertise);
41 | } else if ($this->topics && ($tv = $row->topic_interest_score($pc))) {
42 | $ts[] = "{$pcid}T{$tv}";
43 | }
44 | }
45 | }
46 | $pl->row_attr["data-allpref"] = join(" ", $ts);
47 | if (!empty($ts)) {
48 | $t = 'Loading ';
49 | $pl->need_render = true;
50 | return $t;
51 | } else {
52 | return '';
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/listactions/la_getjsonrqc.php:
--------------------------------------------------------------------------------
1 | is_manager();
8 | }
9 | function run(Contact $user, Qrequest $qreq, SearchSelection $ssel) {
10 | $old_overrides = $user->add_overrides(Contact::OVERRIDE_CONFLICT);
11 | $results = ["hotcrp_version" => HOTCRP_VERSION];
12 | if (($git_data = Conf::git_status())) {
13 | $results["hotcrp_commit"] = $git_data[0];
14 | }
15 | $rf = $user->conf->review_form();
16 | $fj = [];
17 | foreach ($rf->bound_viewable_fields(VIEWSCORE_REVIEWERONLY) as $f) {
18 | $fj[$f->uid()] = $f->export_json(ReviewField::UJ_EXPORT);
19 | }
20 | $results["reviewform"] = $fj;
21 | $pj = [];
22 | $pex = new PaperExport($user);
23 | $pex->set_include_permissions(false);
24 | $pex->set_override_ratings(true);
25 | foreach ($ssel->paper_set($user, ["topics" => true, "options" => true]) as $prow) {
26 | if ($user->allow_administer($prow)) {
27 | $pj[] = $j = $pex->paper_json($prow);
28 | $prow->ensure_full_reviews();
29 | foreach ($prow->viewable_reviews_as_display($user) as $rrow) {
30 | if ($rrow->reviewSubmitted) {
31 | $j->reviews[] = $pex->review_json($prow, $rrow);
32 | }
33 | }
34 | } else {
35 | $pj[] = (object) ["pid" => $prow->paperId, "error" => "You don’t have permission to administer this paper."];
36 | }
37 | }
38 | $user->set_overrides($old_overrides);
39 | $results["papers"] = $pj;
40 | $dopt = new Downloader;
41 | $dopt->set_attachment(true);
42 | $dopt->set_filename($user->conf->download_prefix . "rqc.json");
43 | $dopt->set_mimetype(Mimetype::JSON_UTF8_TYPE);
44 | $dopt->set_content(json_encode($results, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n");
45 | return $dopt;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/options/o_title.php:
--------------------------------------------------------------------------------
1 | prow->title !== "") {
11 | $ov->set_value_data([1], [$ov->prow->title]);
12 | }
13 | }
14 | function value_present(PaperValue $ov) {
15 | return $ov->value
16 | && (strlen($ov->data()) > 6
17 | || !preg_match('/\A(?:|N\/?A|TB[AD])\z/i', $ov->data()));
18 | }
19 | function value_export_json(PaperValue $ov, PaperExport $pex) {
20 | return (string) $ov->data();
21 | }
22 | function value_save(PaperValue $ov, PaperStatus $ps) {
23 | if ($ov->equals($ov->prow->base_option($this->id))) {
24 | return true;
25 | }
26 | $ps->change_at($this);
27 | $ov->prow->set_prop("title", $ov->data());
28 | return true;
29 | }
30 | /** @return ?PaperValue */
31 | private function check_value(?PaperValue $ov) {
32 | if ($ov && $ov->data() !== null && strlen($ov->data()) > 511) {
33 | $ov->estop("<0>Field too long");
34 | }
35 | return $ov;
36 | }
37 | function parse_qreq(PaperInfo $prow, Qrequest $qreq) {
38 | return $this->check_value($this->parse_json_string($prow, $qreq->title, PaperOption::PARSE_STRING_CONVERT | PaperOption::PARSE_STRING_SIMPLIFY));
39 | }
40 | function parse_json(PaperInfo $prow, $j) {
41 | return $this->check_value($this->parse_json_string($prow, $j, PaperOption::PARSE_STRING_SIMPLIFY));
42 | }
43 | function print_web_edit(PaperTable $pt, $ov, $reqov) {
44 | $this->print_web_edit_text($pt, $ov, $reqov, ["no_format_description" => true, "rows" => 1]);
45 | }
46 | function render(FieldRender $fr, PaperValue $ov) {
47 | $fr->value = $ov->prow->title ? : "[No title]";
48 | $fr->value_format = $ov->prow->title_format();
49 | }
50 | function present_script_expression() {
51 | return ["type" => "text_present", "formid" => $this->formid];
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/papercolumns/pc_reviewdelegation.php:
--------------------------------------------------------------------------------
1 | user->isPC) {
13 | return false;
14 | }
15 | $pl->qopts["reviewSignatures"] = true;
16 | $this->requester = $pl->reviewer_user();
17 | return true;
18 | }
19 | function content(PaperList $pl, PaperInfo $row) {
20 | $rx = [];
21 | $row->ensure_reviewer_names();
22 | $old_overrides = $pl->user->add_overrides(Contact::OVERRIDE_CONFLICT);
23 | foreach ($row->reviews_as_display() as $rrow) {
24 | if ($rrow->reviewType !== REVIEW_EXTERNAL
25 | || $rrow->requestedBy !== $this->requester->contactId
26 | || !$pl->user->can_view_review_assignment($row, $rrow)) {
27 | continue;
28 | }
29 | if ($pl->user->can_view_review_identity($row, $rrow)) {
30 | $t = $pl->user->reviewer_html_for($rrow);
31 | } else {
32 | $t = "review";
33 | }
34 | $ranal = $pl->make_review_analysis($rrow, $row);
35 | $d = $rrow->status_description();
36 | if ($rrow->reviewOrdinal) {
37 | $d = rtrim("#" . $rrow->unparse_ordinal_id() . " " . $d);
38 | }
39 | $d = $ranal->wrap_link($d, "noq nw");
40 | if ($rrow->reviewStatus < ReviewInfo::RS_DELIVERED) {
41 | if ($rrow->reviewNeedsSubmit >= 0) {
42 | $d = '' . $d . ' ';
43 | }
44 | $pl->mark_has("need_review");
45 | } else if ($rrow->reviewStatus === ReviewInfo::RS_DELIVERED) {
46 | $d = '' . $d . ' ';
47 | }
48 | $rx[] = $t . ', ' . $d;
49 | }
50 | $pl->user->set_overrides($old_overrides);
51 | return join('; ', $rx);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/logentryfilter.php:
--------------------------------------------------------------------------------
1 | */
9 | private $pidset;
10 | /** @var bool */
11 | private $want;
12 | /** @var ?array */
13 | private $includes;
14 |
15 | /** @param array $pidset
16 | * @param bool $want */
17 | function __construct(Contact $user, $pidset, $want, $includes) {
18 | $this->user = $user;
19 | $this->pidset = $pidset;
20 | $this->want = $want;
21 | $this->includes = $includes;
22 | }
23 |
24 | private function test_pidset($row, $pidset, $want, $includes) {
25 | if ($row->paperId) {
26 | return isset($pidset[$row->paperId]) === $want
27 | && (!$includes || isset($includes[$row->paperId]));
28 | }
29 | if (!preg_match('/\A(.*) \(papers ([\d, ]+)\)?\z/', $row->action, $m)) {
30 | return $this->user->privChair;
31 | }
32 | preg_match_all('/\d+/', $m[2], $mm);
33 | $pids = [];
34 | $included = !$includes;
35 | foreach ($mm[0] as $pid) {
36 | if (isset($pidset[$pid]) === $want) {
37 | $pids[] = $pid;
38 | $included = $included || isset($includes[$pid]);
39 | }
40 | }
41 | if (empty($pids) || !$included) {
42 | return false;
43 | } else if (count($pids) === 1) {
44 | $row->action = $m[1];
45 | $row->paperId = (int) $pids[0];
46 | } else {
47 | $row->action = $m[1] . " (papers " . join(", ", $pids) . ")";
48 | }
49 | return true;
50 | }
51 |
52 | /** @param LogEntry $row
53 | * @return bool */
54 | function __invoke($row) {
55 | if ($this->user->hidden_papers !== null
56 | && !$this->test_pidset($row, $this->user->hidden_papers, false, null)) {
57 | return false;
58 | } else if ($row->contactId === $this->user->contactId) {
59 | return true;
60 | } else {
61 | return $this->test_pidset($row, $this->pidset, $this->want, $this->includes);
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/formulas/f_reviewermatch.php:
--------------------------------------------------------------------------------
1 | user = $user;
19 | $this->set_format(Fexpr::FBOOL);
20 | $this->arg = $arg;
21 | $this->istag = $arg[0] === "#" || ($arg[0] !== "\"" && $user->conf->pc_tag_exists($arg));
22 | $flags = ContactSearch::F_USER;
23 | if ($user->can_view_user_tags()) {
24 | $flags |= ContactSearch::F_TAG;
25 | }
26 | if ($arg[0] === "\"") {
27 | $flags |= ContactSearch::F_QUOTED;
28 | $arg = str_replace("\"", "", $arg);
29 | }
30 | $this->csearch = new ContactSearch($flags, $arg, $user);
31 | }
32 | function inferred_index() {
33 | return self::IDX_REVIEW;
34 | }
35 | function viewable_by(Contact $user) {
36 | return $user->can_view_some_review_identity();
37 | }
38 | function compile(FormulaCompiler $state) {
39 | assert($state->user === $this->user);
40 | // NB the following case also catches attempts to view a non-viewable
41 | // user tag (the csearch will return nothing).
42 | if ($this->csearch->is_empty()) {
43 | return "null";
44 | }
45 | $state->queryOptions["reviewSignatures"] = true;
46 | $t = $state->review_identity_loop_cid();
47 | return "({$t} !== null ? array_search({$t}, [" . join(", ", $this->csearch->user_ids()) . "]) !== false : null)";
48 | }
49 | function matches_at_most_once() {
50 | return count($this->csearch->user_ids()) <= 1;
51 | }
52 | #[\ReturnTypeWillChange]
53 | function jsonSerialize() {
54 | $x = parent::jsonSerialize();
55 | $x["match"] = $this->arg;
56 | if ($this->csearch->is_empty()) {
57 | $x["empty"] = true;
58 | }
59 | return $x;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/api/api_assign.php:
--------------------------------------------------------------------------------
1 | assignments)) {
8 | return JsonResult::make_missing_error("assignments");
9 | }
10 | $a = json_decode($qreq->assignments);
11 | if (!is_array($a)) {
12 | return JsonResult::make_parameter_error("assignments");
13 | }
14 |
15 | $aset = (new AssignmentSet($user))->set_override_conflicts(true);
16 | if ($prow) {
17 | $aset->enable_papers($prow);
18 | }
19 | $aset->parse(CsvParser::make_json($a));
20 | $aset->execute();
21 | $jr = $aset->json_result();
22 |
23 | if ($jr["ok"] && $qreq->search) {
24 | Search_API::apply_search($jr, $user, $qreq, $qreq->search);
25 | // include tag information
26 | if (($p = self::assigned_paper_info($user, $aset))) {
27 | $jr["p"] = $p;
28 | }
29 | }
30 | return $jr;
31 | }
32 |
33 | static function assigned_paper_info(Contact $user, AssignmentSet $assigner) {
34 | $pids = [];
35 | foreach ($assigner->assignments() as $ai) {
36 | if ($ai instanceof Tag_Assigner) {
37 | $pids[$ai->pid] = ($pids[$ai->pid] ?? 0) | 1;
38 | } else if ($ai instanceof Decision_Assigner
39 | || $ai instanceof Status_Assigner) {
40 | $pids[$ai->pid] = ($pids[$ai->pid] ?? 0) | 2;
41 | }
42 | }
43 | $p = [];
44 | foreach ($user->paper_set(["paperId" => array_keys($pids)]) as $pr) {
45 | $p[$pr->paperId] = $tmr = new TagMessageReport;
46 | $pr->add_tag_info_json($tmr, $user);
47 | if (($pids[$pr->paperId] & 2) !== 0) {
48 | list($class, $name) = $pr->status_class_and_name($user);
49 | if ($class !== "ps-submitted") {
50 | $tmr->status_html = "" . htmlspecialchars($name) . " ";
51 | } else {
52 | $tmr->status_html = "";
53 | }
54 | }
55 | }
56 | return $p;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/search/st_phase.php:
--------------------------------------------------------------------------------
1 | user = !$user || $user->is_root_user() ? null : $user;
15 | $this->phase = $phase;
16 | }
17 | static function parse($word, SearchWord $sword, PaperSearch $srch) {
18 | if (strcasecmp($word, "final") === 0) {
19 | return new Phase_SearchTerm($srch->user, PaperInfo::PHASE_FINAL);
20 | } else if (strcasecmp($word, "review") === 0) {
21 | return new Phase_SearchTerm($srch->user, PaperInfo::PHASE_REVIEW);
22 | } else {
23 | $srch->lwarning($sword, "<0>Only “phase:review” and “phase:final” are allowed");
24 | return new False_SearchTerm;
25 | }
26 | }
27 | function sqlexpr(SearchQueryInfo $sqi) {
28 | return $this->phase === PaperInfo::PHASE_FINAL ? "(Paper.timeWithdrawn<=0 and Paper.outcome>0)" : "true";
29 | }
30 | function test(PaperInfo $row, $xinfo) {
31 | return $row->visible_phase($this->user) === $this->phase;
32 | }
33 | function about() {
34 | return self::ABOUT_PAPER;
35 | }
36 |
37 | /** @return ?int */
38 | static function term_phase(SearchTerm $st) {
39 | return $st->visit(function ($t, ...$vals) {
40 | if (empty($vals)) {
41 | return $t instanceof Phase_SearchTerm ? $t->phase : null;
42 | } else if ($t instanceof And_SearchTerm) {
43 | foreach ($vals as $v) {
44 | if ($v !== null)
45 | return $v;
46 | }
47 | return null;
48 | } else if ($t instanceof Not_SearchTerm) {
49 | return null;
50 | } else {
51 | $x = $vals[0];
52 | foreach ($vals as $v) {
53 | if ($v !== $x)
54 | return null;
55 | }
56 | return $x;
57 | }
58 | });
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/papercolumns/pc_shepherd.php:
--------------------------------------------------------------------------------
1 | override = PaperColumn::OVERRIDE_IFEMPTY;
13 | }
14 | function view_option_schema() {
15 | return self::user_view_option_schema();
16 | }
17 | function prepare(PaperList $pl, $visible) {
18 | if (!$pl->user->can_view_shepherd(null)
19 | || (!$pl->conf->has_any_lead_or_shepherd() && !$visible)) {
20 | return false;
21 | }
22 | $pl->conf->pc_set(); // prepare cache
23 | $this->nameflags = $this->user_view_option_name_flags($pl->conf);
24 | return true;
25 | }
26 | static private function cid(PaperList $pl, PaperInfo $row) {
27 | if ($row->shepherdContactId > 0 && $pl->user->can_view_shepherd($row)) {
28 | return $row->shepherdContactId;
29 | } else {
30 | return 0;
31 | }
32 | }
33 | function reset(PaperList $pl) {
34 | if (!$this->was_reset && $pl->conf->setting("extrev_shepherd")) {
35 | foreach ($pl->rowset() as $row) {
36 | if ($row->shepherdContactId > 0)
37 | $pl->conf->prefetch_user_by_id($row->shepherdContactId);
38 | }
39 | }
40 | }
41 | function sort_name() {
42 | return $this->sort_name_with_options("format");
43 | }
44 | function compare(PaperInfo $a, PaperInfo $b, PaperList $pl) {
45 | $ianno = $this->nameflags & NAME_L ? Contact::SORTSPEC_LAST : Contact::SORTSPEC_FIRST;
46 | return $pl->user_compare(self::cid($pl, $a), self::cid($pl, $b), $ianno);
47 | }
48 | function content_empty(PaperList $pl, PaperInfo $row) {
49 | return !self::cid($pl, $row);
50 | }
51 | function content(PaperList $pl, PaperInfo $row) {
52 | return $pl->user_content($row->shepherdContactId, $row, $this->nameflags);
53 | }
54 | function text(PaperList $pl, PaperInfo $row) {
55 | return $pl->user_text($row->shepherdContactId, $this->nameflags);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/api/api_paperpc.php:
--------------------------------------------------------------------------------
1 | method() === "POST" && isset($qreq->$type)) {
8 | $aset = new AssignmentSet($user);
9 | $aset->enable_papers($prow);
10 | $aset->parse("paper,action,user\n{$prow->paperId},{$type}," . CsvGenerator::quote($qreq->$type));
11 | if (!$aset->execute()) {
12 | return $aset->json_result();
13 | }
14 | $cid = $user->conf->fetch_ivalue("select {$type}ContactId from Paper where paperId=?", $prow->paperId);
15 | } else {
16 | $k = "can_view_{$type}";
17 | if (!$user->$k($prow)) {
18 | return JsonResult::make_permission_error();
19 | }
20 | $k = "{$type}ContactId";
21 | $cid = $prow->$k;
22 | }
23 | $pcu = $cid ? $user->conf->user_by_id($cid, USER_SLICE) : null;
24 | $j = [
25 | "ok" => true,
26 | $type => $pcu ? $pcu->email : "none",
27 | "{$type}_html" => $pcu ? $user->name_html_for($pcu) : "None"
28 | ];
29 | if ($user->can_view_user_tags()) {
30 | $j["color_classes"] = $pcu ? $pcu->viewable_color_classes($user) : "";
31 | }
32 | return $j;
33 | }
34 |
35 | static function lead_api(Contact $user, Qrequest $qreq, PaperInfo $prow) {
36 | return self::run($user, $qreq, $prow, "lead");
37 | }
38 |
39 | static function shepherd_api(Contact $user, Qrequest $qreq, PaperInfo $prow) {
40 | return self::run($user, $qreq, $prow, "shepherd");
41 | }
42 |
43 | static function manager_api(Contact $user, Qrequest $qreq, PaperInfo $prow) {
44 | return self::run($user, $qreq, $prow, "manager");
45 | }
46 |
47 | static function pc_api(Contact $user, Qrequest $qreq) {
48 | if (!$user->can_view_pc()) {
49 | return JsonResult::make_permission_error();
50 | }
51 | $jr = new JsonResult($user->conf->hotcrp_pc_json($user, $qreq->ui ? Conf::PCJM_UI : Conf::PCJM_DEFAULT));
52 | if ($qreq->ui) {
53 | $jr->set_pretty_print(false);
54 | }
55 | return $jr;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/etc/autoassigners.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "prefconflict",
4 | "function": "+PrefConflict_Autoassigner"
5 | },
6 | {
7 | "name": "clear",
8 | "function": "+Clear_Autoassigner",
9 | "parameters": ["type"]
10 | },
11 | {
12 | "name": "lead",
13 | "function": "+PaperPC_Autoassigner",
14 | "parameters": ["?score", "?allow_incomplete {bool}", "?method", "?balance", "?max_load", "?max_load_tag"]
15 | },
16 | {
17 | "name": "shepherd",
18 | "function": "+PaperPC_Autoassigner",
19 | "parameters": ["?score", "?allow_incomplete {bool}", "?method", "?balance", "?max_load", "?max_load_tag"]
20 | },
21 | {
22 | "name": "__review_parameters",
23 | "parameters": [
24 | "?rtype Review type",
25 | "?round Review round",
26 | "?method",
27 | "?balance",
28 | "?load",
29 | "?max_load",
30 | "?max_load_tag",
31 | "?gadget",
32 | "?assignment_cost",
33 | "?preference_cost",
34 | "?expertise_x_cost",
35 | "?expertise_y_cost"
36 | ]
37 | },
38 | {
39 | "name": "review",
40 | "function": "+Review_Autoassigner",
41 | "parameters": [
42 | "count {n} Number of reviews to assign per paper",
43 | "$__review_parameters"
44 | ]
45 | },
46 | {
47 | "name": "review_ensure",
48 | "function": "+Review_Autoassigner",
49 | "parameters": [
50 | "count {n} Number of reviews to ensure per paper",
51 | "$__review_parameters"
52 | ]
53 | },
54 | {
55 | "name": "review_adjust",
56 | "function": "+Review_Autoassigner",
57 | "parameters": [
58 | "count {n} Number of reviews to ensure per paper",
59 | "$__review_parameters"
60 | ]
61 | },
62 | {
63 | "name": "review_per_pc",
64 | "function": "+Review_Autoassigner",
65 | "parameters": [
66 | "count {n} Number of reviews to assign per PC member",
67 | "$__review_parameters"
68 | ]
69 | },
70 | {
71 | "name": "discussion_order",
72 | "function": "+DiscussionOrder_Autoassigner",
73 | "parameters": ["tag {tag} Destination tag"]
74 | }
75 | ]
76 |
--------------------------------------------------------------------------------
/src/options/o_topics.php:
--------------------------------------------------------------------------------
1 | refresh_topic_set();
9 | }
10 |
11 | function refresh_topic_set() {
12 | $ts = $this->topic_set();
13 | $empty = $ts->count() === 0 && !$ts->auto_add();
14 | $this->override_exists_condition($empty ? false : null);
15 | }
16 |
17 | function topic_set() {
18 | return $this->conf->topic_set();
19 | }
20 |
21 | function interests($user) {
22 | return $user ? $user->topic_interest_map() : [];
23 | }
24 |
25 | function value_force(PaperValue $ov) {
26 | if ($this->id === PaperOption::TOPICSID) {
27 | $vs = $ov->prow->topic_list();
28 | $ov->set_value_data($vs, array_fill(0, count($vs), null));
29 | }
30 | }
31 |
32 | private function _store_new_values(PaperValue $ov, PaperStatus $ps) {
33 | $this->topic_set()->commit_auto_add();
34 | $vs = $ov->value_list();
35 | $newvs = $ov->anno("new_values");
36 | '@phan-var list $newvs';
37 | foreach ($newvs as $tk) {
38 | if (($tid = $this->topic_set()->find_exact($tk)) !== null) {
39 | $vs[] = $tid;
40 | }
41 | }
42 | $this->topic_set()->sort($vs); // to reduce unnecessary diffs
43 | $ov->set_value_data($vs, array_fill(0, count($vs), null));
44 | $ov->set_anno("new_values", null);
45 | }
46 |
47 | function value_save(PaperValue $ov, PaperStatus $ps) {
48 | if (!$ov->anno("new_values")
49 | && $ov->equals($ov->prow->base_option($this->id))) {
50 | return true;
51 | }
52 | if ($ov->anno("new_values")) {
53 | if (!$ps->save_status_prepared()) {
54 | $ps->request_resave($this);
55 | $ps->change_at($this);
56 | } else {
57 | $this->_store_new_values($ov, $ps);
58 | }
59 | }
60 | $ps->change_at($this);
61 | if ($this->id === PaperOption::TOPICSID) {
62 | $ov->prow->set_prop("topicIds", join(",", $ov->value_list()));
63 | }
64 | return true;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/search/st_color.php:
--------------------------------------------------------------------------------
1 | user = $user;
15 | $this->word = $word;
16 | }
17 | function sqlexpr(SearchQueryInfo $sqi) {
18 | return 'exists (select * from PaperTag where paperId=Paper.paperId)';
19 | }
20 | function test(PaperInfo $row, $xinfo) {
21 | $tags = $row->viewable_tags($this->user);
22 | $styles = $row->conf->tags()->styles($tags, 0, true);
23 | return !empty($styles)
24 | && ($this->word === "any" || in_array($this->word, $styles, true));
25 | }
26 | function debug_json() {
27 | return ["type" => $this->type, "style" => $this->word];
28 | }
29 | function about() {
30 | return self::ABOUT_PAPER;
31 | }
32 |
33 | static function parse_style($word, SearchWord $sword, PaperSearch $srch) {
34 | $word = strtolower($word);
35 | if ($word === "any" || $word === "none") {
36 | return (new Color_SearchTerm($srch->user, "any"))->negate_if($word === "none");
37 | } else if ($word === "color") {
38 | return new Color_SearchTerm($srch->user, "tagbg");
39 | } else if (($ks = $srch->conf->tags()->known_style($word))) {
40 | return new Color_SearchTerm($srch->user, "tag-" . $ks->style);
41 | } else {
42 | $srch->lwarning($sword, "<0>Unknown style ‘{$word}’");
43 | return new False_SearchTerm;
44 | }
45 | }
46 | static function parse_color($word, SearchWord $sword, PaperSearch $srch) {
47 | $word = strtolower($word);
48 | if ($word === "any" || $word === "color" || $word === "none") {
49 | return (new Color_SearchTerm($srch->user, "tagbg"))->negate_if($word === "none");
50 | } else if (($ks = $srch->conf->tags()->known_style($word))) {
51 | return new Color_SearchTerm($srch->user, $ks->style);
52 | } else {
53 | $srch->lwarning($sword, "<0>Unknown color ‘{$word}’");
54 | return new False_SearchTerm;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches: [ master, github ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | matrix:
15 | php-versions: ['7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4']
16 |
17 | steps:
18 | - name: Checkout
19 | uses: actions/checkout@v4
20 |
21 | - name: Start mysql
22 | run: |
23 | (echo '[mysqld]'; echo 'default-authentication-plugin=mysql_native_password') | sudo sh -c "cat > /etc/mysql/conf.d/nativepassword.cnf"
24 | sudo systemctl start mysql
25 | mysql -V
26 |
27 | - name: Prepare the application
28 | run: |
29 | sudo lib/createdb.sh -u root -proot -c test/options.php --batch
30 | sudo lib/createdb.sh -u root -proot -c test/cdb-options.php --no-dbuser --batch
31 |
32 | - name: Set up PHP
33 | uses: shivammathur/setup-php@v2
34 | with:
35 | php-version: ${{ matrix.php-versions }}
36 | extensions: mbstring, intl, mysqlnd
37 |
38 | - name: Install poppler
39 | run: |
40 | sudo apt-get --fix-broken install
41 | sudo apt-get install poppler-utils || ( sudo apt-get update && sudo apt-get install poppler-utils )
42 |
43 | - name: Run tests
44 | run: sh test/check.sh --all
45 |
46 | build-22:
47 | runs-on: ubuntu-22.04
48 |
49 | strategy:
50 | matrix:
51 | php-versions: ['7.3', '8.0']
52 |
53 | steps:
54 | - name: Checkout
55 | uses: actions/checkout@v4
56 |
57 | - name: Start mysql
58 | run: |
59 | (echo '[mysqld]'; echo 'default-authentication-plugin=mysql_native_password') | sudo sh -c "cat > /etc/mysql/conf.d/nativepassword.cnf"
60 | sudo /etc/init.d/mysql start
61 | mysql -V
62 |
63 | - name: Prepare the application
64 | run: |
65 | sudo lib/createdb.sh -u root -proot -c test/options.php --batch
66 | sudo lib/createdb.sh -u root -proot -c test/cdb-options.php --no-dbuser --batch
67 |
68 | - name: Set up PHP
69 | uses: shivammathur/setup-php@v2
70 | with:
71 | php-version: ${{ matrix.php-versions }}
72 | extensions: mbstring, intl, mysqlnd
73 |
74 | - name: Install poppler
75 | run: |
76 | sudo apt-get update
77 | sudo apt-get --fix-missing install poppler-utils
78 |
79 | - name: Run tests
80 | run: sh test/check.sh --all
81 |
--------------------------------------------------------------------------------