Skip to content

Commit

Permalink
Initial documentation of page components.
Browse files Browse the repository at this point in the history
  • Loading branch information
kohler committed Oct 16, 2023
1 parent a13c08c commit 06d0253
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 28 deletions.
72 changes: 51 additions & 21 deletions devel/manual/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,27 @@ The components for each feature are defined by a JSON configuration file in
`etc/`, and then modified by JSON objects and files listed in an `$Opt`
setting.

| Feature | Default configuration file | `$Opt` setting | Match? | User `allow_if`? |
| ----------------------------------------- | ----------------------------- | ------------------------- | ------ | ----------- |
| Request handling and page rendering | `etc/pages.json` | `$Opt["pages"]` | | |
| API endpoints | `etc/apifunctions.json` | `$Opt["apiFunctions"]` | | |
| Formula functions | `etc/formulafunctions.json` | `$Opt["formulaFunctions"]` | | |
| Submission field types | `etc/optiontypes.json` | `$Opt["optionTypes"]` | | |
| Sample submission fields | `etc/submissionfieldlibrary.json` | `$Opt["submissionFieldLibraries"]` | | |
| Assignment types for bulk assignment | `etc/assignmentparsers.json` | `$Opt["assignmentParsers"]` | | |
| Autoassigners | `etc/autoassigners.json` | `$Opt["autoassigners"]` | | |
| Help topics | `etc/helptopics.json` | `$Opt["helpTopics"]` | | |
| Search actions | `etc/listactions.json` | `$Opt["listActions"]` | | |
| Mail keywords | `etc/mailkeywords.json` | `$Opt["mailKeywords"]` | | |
| Mail templates | `etc/mailtemplates.json` | `$Opt["mailTemplates"]` | | |
| Search columns | `etc/papercolumns.json` | `$Opt["paperColumns"]` | | |
| Profile page topics | `etc/profilegroups.json` | `$Opt["profileGroups"]` | | |
| Sample review fields | `etc/reviewfieldlibrary.json` | `$Opt["reviewFieldLibraries"]` | | |
| Review field types | `etc/reviewfieldtypes.json` | `$Opt["reviewFieldTypes"]` | | |
| Search keywords | `etc/searchkeywords.json` | `$Opt["searchKeywords"]` | | |
| Setting topics and rendering | `etc/settinggroups.json` | `$Opt["settingGroups"]` | | |
| Settings | `etc/settinginfo.json` | `$Opt["settingInfo"]` | | |
| OAuth/OpenID authentication types | None | `$Opt["oAuthTypes"]` | | |
| Feature | Default configuration file | `$Opt` setting | Match? | User `allow_if`? | Nested? |
|:---------------------------------------|:------------------------------|:--------------------------|---|---|---|
| [Request handling and page rendering](./pages.md) | `etc/pages.json` | `$Opt["pages"]` | || |
| API endpoints | `etc/apifunctions.json` | `$Opt["apiFunctions"]` | || |
| Formula functions | `etc/formulafunctions.json` | `$Opt["formulaFunctions"]` | || |
| Submission field types | `etc/optiontypes.json` | `$Opt["optionTypes"]` | | | |
| Sample submission fields | `etc/submissionfieldlibrary.json` | `$Opt["submissionFieldLibraries"]` | | | |
| Assignment types for bulk assignment | `etc/assignmentparsers.json` | `$Opt["assignmentParsers"]` | || |
| Autoassigners | `etc/autoassigners.json` | `$Opt["autoassigners"]` | || |
| Help topics | `etc/helptopics.json` | `$Opt["helpTopics"]` | || |
| Search actions | `etc/listactions.json` | `$Opt["listActions"]` | || |
| Mail keywords | `etc/mailkeywords.json` | `$Opt["mailKeywords"]` | || |
| Mail templates | `etc/mailtemplates.json` | `$Opt["mailTemplates"]` | || |
| Search columns | `etc/papercolumns.json` | `$Opt["paperColumns"]` | || |
| Profile page topics | `etc/profilegroups.json` | `$Opt["profileGroups"]` | || |
| Sample review fields | `etc/reviewfieldlibrary.json` | `$Opt["reviewFieldLibraries"]` | | | |
| Review field types | `etc/reviewfieldtypes.json` | `$Opt["reviewFieldTypes"]` | | | |
| Search keywords | `etc/searchkeywords.json` | `$Opt["searchKeywords"]` | || |
| Setting topics and rendering | `etc/settinggroups.json` | `$Opt["settingGroups"]` | || |
| Settings | `etc/settinginfo.json` | `$Opt["settingInfo"]` | || |
| OAuth/OpenID authentication types | None | `$Opt["oAuthTypes"]` | | | |

## Component construction

Expand Down Expand Up @@ -107,3 +107,33 @@ returned. The merge process works as follows.

4. The highest-priority remaining component, if any, is the result of the
merge process.

## Listing components

In some cases, such as rendering pages, HotCRP needs to list all components,
or all components that match a given prefix. This list is usually produced by
ordering the relevant components according to their `order` properties.
`order` defaults to 0; ties are broken by the `name` property and and/or by
source order.

## Aliases

When a component fragment has an `alias` property, HotCRP replaces that
fragment by the result of looking up the component named `alias`. This can be
useful to provide synonyms; for example, the search keyword component fragment
`{"name": "administrator", "alias": "admin"}` allows users to search for
`administrator:` as an alias for `admin:`. Aliasing may be preferred to
copying a component definition because changes in the source component are
automatically incorporated in the alias.

An alias may point to another alias, but HotCRP will only follow alias chains
to a limited depth before giving up.

## Nested components

Certain component types, such as pages and help topics, support nesting. A
name may contain one or more slashes. The component is considered a member of
the **group** defined by everything up to the last slash; for instance,
components `a/b`, `a/c`, and `a/b/c` are members of groups `a`, `a`, and
`a/b`, respectively. If present, the component’s `group` property overrides
the group value derived from the component name.
97 changes: 97 additions & 0 deletions devel/manual/pages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# HotCRP page construction

HotCRP uses [components][] to handle requests and render pages. You can add
new pages to HotCRP or selectively override existing pages by extending
`$Opt["pages"]`.

## Page flow

When a request arrives, HotCRP parses the requested URL into the *site path*,
which the base URL for this conference; the *page*, which is the name of the
page to render; and the *path*, which is anything following the page. For
example:

https://yoursite.edu/hotcrpprefix/paper/3/edit
-------------- *******
SITE PATH ===== PATH
PAGE

The site path may end with a user identifier, such as `/u/0/`, that’s present
when a single browser logs in to multiple accounts.

HotCRP looks up the page component named `PAGE`. If the page component does
not exist, or if its name starts with two underscores, HotCRP reports a
page-not-found error. If the user is disabled, then HotCRP checks the page
component’s `allow_disabled` property and returns an error to the user unless
`allow_disabled` is true.

Otherwise, the page component will have a `group` member, which defaults to
`PAGE` but need not equal it (for instance, if one page is an alias for
another). The value of this `group` member is called the **page group**.

## Processing the request

HotCRP next obtains a list of the page group’s components, including (1) the
page component itself, and then (2) any components that are direct members of
that group, ordered by their `order` properties.

In the first stage, HotCRP scans the component list and calls any allowed
`request_function`s.

A `request_function` defines a PHP callback that processes the request before
page rendering starts. Parameters to `request_function` are (1) the viewing
user (a `Contact` object), (2) the request being processed (a `Qrequest`
object), (3) the set of page components (a `ComponentSet` object), and (4) the
page component itself (a generic object). `request_function` may have the
format `CLASSNAME::FUNCTIONNAME` or `*CLASSNAME::FUNCTIONNAME`. In the first
form, HotCRP calls the named function statically. In the second form (with
`*`), HotCRP constructs an object of type `CLASSNAME` (constructor parameters
`Contact, Qrequest, ComponentSet`) and then calls the relevant `FUNCTIONNAME`
on that object. All calls with the same `*CLASSNAME` will use the same object.

A component’s `request_function` may be blocked by the `allow_request_if` as
well as the `allow_if` property. For instance, `"allow_request_if":
"req.clearbug"` allows the `request_function` only if the request has a
`clearbug` parameter.

If a `request_function` explicitly returns `false` (as opposed to `null`, or
by simply falling off the end), then HotCRP quits calling `request_function`s
and terminates request processing. A `request_function` may also throw a value
of type `Redirection` to force HotCRP to redirect the user’s browser to
another URI.

## Rendering the result

Assuming the `request_function`s do not throw a redirection, HotCRP next
re-scans the component list and prints the corresponding components.

To print a component, HotCRP checks for:

1. A `print_function` property. If present, this calls the corresponding PHP
callback, using the same syntax and arguments as `request_function`, above.

2. Otherwise, an `html_content` property. If present, this is copied to the output.

3. In either case, if the component has a `print_members` property, HotCRP
next prints the members of that group.

A `print_function` may cancel further rendering by returning explicit `false`,
or by throwing a `Redirection` or `PageCompletion` exception.

Many HotCRP pages do not have separate `request_function`s, instead handling
all request parsing as part of the first `print_function` for a page.

## Shorthand

Page components may be defined using an array shorthand. The notation `[NAME,
ORDER, PRINT_FUNCTION]` or `[NAME, ORDER, PRINT_FUNCTION, PRIORITY]` is the
same as an with the corresponding properties:

```
{
"name": NAME, "order": ORDER, "print_function": PRINT_FUNCTION [, "priority": PRIORITY]
}
```


[components]: ./components.md
23 changes: 16 additions & 7 deletions index.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,29 @@

/** @param Contact $user
* @param Qrequest $qreq
* @param string $group
* @param object $pagej
* @param ComponentSet $pc */
function handle_request_components($user, $qreq, $group, $pc) {
function handle_request_components($user, $qreq, $pagej, $pc) {
$pc->add_xt_checker([$qreq, "xt_allow"]);
$reqgj = [];
$nfound = 0;
$not_allowed = false;
foreach ($pc->members($group, "request_function") as $gj) {
if (isset($pagej->request_function)) {
++$nfound;
if ($pc->allowed($gj->allow_request_if ?? null, $gj)) {
$reqgj[] = $gj;
}
}
foreach ($pc->members($pagej->group, "request_function") as $gj) {
++$nfound;
if ($pc->allowed($gj->allow_request_if ?? null, $gj)) {
$reqgj[] = $gj;
} else {
$not_allowed = true;
}
}
if ($not_allowed && $qreq->is_post() && !$qreq->valid_token()) {
if ($nfound > 0
&& $nfound < count($reqgj)
&& $qreq->is_post()
&& !$qreq->valid_token()) {
$user->conf->error_msg($user->conf->_i("badpost"));
}
foreach ($reqgj as $gj) {
Expand All @@ -46,7 +55,7 @@ function handle_request($nav) {
Multiconference::fail($qreq, 403, ["link" => true], $user->conf->_i("account_disabled"));
} else {
$pc->set_root($pagej->group);
handle_request_components($user, $qreq, $pagej->group, $pc);
handle_request_components($user, $qreq, $pagej, $pc);
$pc->print_group($pagej->group, true);
}
} catch (Redirection $redir) {
Expand Down

0 comments on commit 06d0253

Please sign in to comment.