Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion phpbb-test-board/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down
5 changes: 5 additions & 0 deletions phpbb-test-board/enable-on-php.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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[@]}" \
Expand Down
42 changes: 39 additions & 3 deletions phpbb-test-board/feature_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
15 changes: 15 additions & 0 deletions phpbb-test-board/screenshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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,
Expand Down
Loading