diff --git a/phpbb-test-board/README.md b/phpbb-test-board/README.md index dbea221..06298f5 100644 --- a/phpbb-test-board/README.md +++ b/phpbb-test-board/README.md @@ -75,7 +75,7 @@ Exit status: 0 when every file passes on every version, 1 when a file fails or D phpbb-test-board/enable-on-php.sh [-p 8.1] [--disable] BOARD_ROOT path/to/extension... ``` -Enables each extension on a SQLite board built with [seed-forum](https://github.com/phpbbmodders/seed-forum) (`database: sqlite`, no extensions), running phpBB's `extension:enable` in a throwaway `php:VERSION-cli` container. That command calls `is_enableable()` and then runs the migrations, so it shows whether an extension installs on that PHP, or the refusal message when it should not (for example one version below its minimum). The board is mounted read-write at its own path, because `config.php` names the database by its absolute path; each extension is mounted read-only under `ext/` from its `composer.json` name, so nothing is copied into the board. Extensions that fail are retried once after the others, for ones that need another extension. `--disable` disables and purges them again afterwards. +Enables each extension on a SQLite board built with [seed-forum](https://github.com/phpbbmodders/seed-forum) (`database: sqlite`, no extensions), running phpBB's `extension:enable` in a throwaway `php:VERSION-cli` container. That command calls `is_enableable()` and then runs the migrations, so it shows whether an extension installs on that PHP, or the refusal message when it should not (for example one version below its minimum). The board is mounted read-write at its own path, because `config.php` names the database by its absolute path; each extension is mounted read-only under `ext/` from its `composer.json` name, so nothing is copied into the board. Extensions that fail are retried once after the others, for ones that need another extension. `--disable` disables and purges them again afterwards. The board's `cache/production` is emptied first, because a container compiled by a newer PHP (such as the host's) can't be read by an older one. Exit status: 0 when every extension enabled, 1 when one failed or Docker fails, 2 for bad arguments. Needs Docker and `jq`. diff --git a/phpbb-test-board/enable-on-php.sh b/phpbb-test-board/enable-on-php.sh index e3d0eaf..f116fc1 100755 --- a/phpbb-test-board/enable-on-php.sh +++ b/phpbb-test-board/enable-on-php.sh @@ -166,6 +166,11 @@ for name in "${names[@]}"; do done done +# The compiled container in cache/production is PHP for the PHP version that +# built it (for example the host's 8.4); an older PHP can't even parse it, so +# start from an empty cache. phpBB rebuilds it on the first request. +find "$board_root/cache/production" -mindepth 1 -delete 2>/dev/null || true + docker run --rm --network none --user "$(id -u):$(id -g)" \ -e DISABLE_AFTER="$disable_after" \ -e BOARD_ROOT="$board_root" -v "$board_root:$board_root" "${mounts[@]}" \ diff --git a/phpbb-test-board/feature_checks.py b/phpbb-test-board/feature_checks.py index cfa8755..18a348e 100755 --- a/phpbb-test-board/feature_checks.py +++ b/phpbb-test-board/feature_checks.py @@ -107,12 +107,21 @@ def check_bannerrotator(b: Board) -> None: def check_groupwarn(b: Board) -> None: + """phpbbmodders/groupwarn: warnings are refused, and the warn button and profile link hidden, until all + of a user's groups are ticked; founders can always warn; pending group memberships don't count.""" mod_pw = secrets.token_urlsafe(12) mod_id = b.add_user("gwmod", mod_pw) target_id = b.add_user("gwtarget", secrets.token_urlsafe(12)) b.php(f"group_user_add(4, [{mod_id}]);") # Global moderators + # Give the target a post, so the warn button on posts can be checked. + post_id, topic_id = b.sql("SELECT post_id, topic_id FROM phpbb_posts " + "WHERE post_id = (SELECT MIN(post_id) FROM phpbb_posts)")[0] + b.sql("UPDATE phpbb_posts SET poster_id = ? WHERE post_id = ?", (target_id, post_id)) + b.purge_cache() s = requests.Session() b.login(s, "gwmod", mod_pw) + admin = requests.Session() + b.login(admin) url = f"{b.base}/mcp.php?i=warn&mode=warn_user&u={target_id}" def submit_warning() -> str: @@ -121,18 +130,45 @@ def submit_warning() -> str: {**fields, "warning": "probe warning", "action[add_warning]": "Submit"}) def warnings() -> int: - return b.sql("SELECT user_warnings FROM phpbb_users WHERE user_id = ?", (target_id,))[0][0] + # int(): MySQL, MariaDB and PostgreSQL boards return numbers as strings here. + return int(b.sql("SELECT user_warnings FROM phpbb_users WHERE user_id = ?", (target_id,))[0][0]) + + def warn_button(session: requests.Session) -> bool: + return f"mode=warn_post&p={post_id}" in session.get(f"{b.base}/viewtopic.php?t={topic_id}").text + + def profile_link(session: requests.Session) -> bool: + page = session.get(f"{b.base}/memberlist.php?mode=viewprofile&u={target_id}").text + return f"mode=warn_user&u={target_id}" in page + + def set_warn(group_ids: list, value: int) -> None: + b.sql(f"UPDATE phpbb_groups SET group_warn = ? WHERE group_id IN ({','.join('?' * len(group_ids))})", + (value, *group_ids)) + b.purge_cache() + ok(not warn_button(s) and not profile_link(s), + "warn button on posts and warn link on the profile hidden while the user's groups aren't ticked") body = submit_warning() ok("You cannot warn this user" in body and warnings() == 0, "moderator can't warn a user whose groups aren't ticked (default)") + ok(warn_button(admin), "the founder still gets the warn button") + groups = [r[0] for r in b.sql("SELECT group_id FROM phpbb_user_group WHERE user_id = ?", (target_id,))] - b.sql(f"UPDATE phpbb_groups SET group_warn = 1 WHERE group_id IN ({','.join('?' * len(groups))})", tuple(groups)) - b.purge_cache() + set_warn(groups, 1) + ok(warn_button(s) and profile_link(s), "warn button and profile link shown once all the user's groups are ticked") body = submit_warning() ok("You cannot warn this user" not in body and warnings() == 1, "moderator can warn once all the user's groups are ticked") + # A pending membership of an unticked group doesn't count; a full one does. + b.php("group_create($gid, GROUP_OPEN, 'GW pending', '', []);") + gid = b.sql("SELECT group_id FROM phpbb_groups WHERE group_name = 'GW pending'")[0][0] + b.php(f"group_user_add({gid}, [{target_id}], false, false, false, 0, 1);") + b.purge_cache() + ok(warn_button(s), "a pending membership of an unticked group doesn't block warnings") + b.sql("UPDATE phpbb_user_group SET user_pending = 0 WHERE group_id = ? AND user_id = ?", (gid, target_id)) + b.purge_cache() + ok(not warn_button(s), "a full membership of an unticked group blocks warnings") + def check_wiki(b: Board) -> None: # Articles and a post stored through phpBB's own text parser, so the diff --git a/phpbb-test-board/screenshots.py b/phpbb-test-board/screenshots.py index af7da17..d9066ba 100755 --- a/phpbb-test-board/screenshots.py +++ b/phpbb-test-board/screenshots.py @@ -781,6 +781,20 @@ def shots_banhammer(board: Board, shoot: Shooter, args: argparse.Namespace) -> N shoot.shot("banhammer-banlist", "app.php/banlist?sid={sid}", admin=True) +def shots_groupwarn(board: Board, shoot: Shooter, args: argparse.Namespace) -> None: + """phpbbmodders/groupwarn: the Group warn setting on the Registered users group's ACP page.""" + group_id = board.sql("SELECT group_id FROM phpbb_groups WHERE group_name = 'REGISTERED'")[0][0] + board.sql("UPDATE phpbb_groups SET group_warn = 1 WHERE group_id = ?", (group_id,)) + board.purge_cache() + + def highlight(p) -> None: + p.add_style_tag(content="#group_warn { outline: 3px solid #d31141; outline-offset: 2px; }") + p.locator("#group_warn").scroll_into_view_if_needed() + shoot.shot("groupwarn-acp-group-setting", + f"adm/index.php?i=acp_groups&mode=manage&action=edit&g={group_id}&sid={{sid}}", admin=True, + selector="fieldset:has(#group_warn)", prepare=highlight) + + def shots_sfscompanion(board: Board, shoot: Shooter, args: argparse.Namespace) -> None: """phpbbmodders/sfscompanion: its ACP logs and settings, and the profile link. @@ -1437,6 +1451,7 @@ def run_prune(p) -> None: "phpbbmodders/sfscompanion": shots_sfscompanion, "phpbbmodders/stopforumspam": shots_stopforumspam, "phpbbmodders/banhammer": shots_banhammer, + "phpbbmodders/groupwarn": shots_groupwarn, "phpbbmodders/topicclaim": shots_topicclaim, "phpbbmodders/wiki": shots_wiki, "ProMinoDeux": shots_prominodeux,