diff --git a/phpbb-test-board/README.md b/phpbb-test-board/README.md index b828864..885b8be 100644 --- a/phpbb-test-board/README.md +++ b/phpbb-test-board/README.md @@ -199,6 +199,16 @@ phpbb-test-board/rename_upgrade.py path/to/stopforumspam@0af40a1 path/to/stopfor --setting sfs_api_key=UPGRADE-TEST ``` +### `acp_extension.py`: enable or disable through the ACP + +```bash +phpbb-test-board/acp_extension.py enable|disable vendor/name [--url http://localhost:8161] +``` + +Does what an administrator does on **Customise > Manage extensions**, on a board that is already being served (a seed-forum board's nginx site, for example): logs in, re-authenticates for the ACP, confirms Enable or Disable, then follows each "in progress" refresh phpBB sends while it runs the extension's steps and migrations in time-limited chunks, until phpBB reports success or an error. It prints one line per request and a summary with the number of requests and the time taken. + +Use it for an extension that cannot be enabled from phpBB's command line (kinerity/knowledgebase reads the current user's name in a migration), and to time the web path of a long migration. It logs in as seed-forum's public fixture admin unless you pass `--user` and put the password in `PHPBB_ADMIN_PASSWORD`. The session id goes in every URL, because Python's cookie jar does not send cookies set for a bare `localhost` back. + ### `template_a11y.py`: labels on icon-only links and buttons ```bash diff --git a/phpbb-test-board/acp_extension.py b/phpbb-test-board/acp_extension.py new file mode 100755 index 0000000..8750088 --- /dev/null +++ b/phpbb-test-board/acp_extension.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +""" +Enable or disable a phpBB extension through the Administration Control Panel. + +Does what an administrator does on Customise > Manage extensions: logs in, +re-authenticates for the ACP, opens the extension's Enable (or Disable) +confirmation, submits it, then follows each "in progress" page refresh phpBB +sends while it runs the extension's steps and migrations in time-limited +chunks, until phpBB reports success or an error. Use it on a served local +test board (seed-forum's nginx site, for example), for extensions whose +migrations need a logged-in user, and to time the web path of a long +migration. + +Inputs: the action (enable or disable), the extension name (vendor/name), + --url (the board's address), and the admin login: seed-forum's + public fixture admin by default, or --user plus a password in the + PHPBB_ADMIN_PASSWORD environment variable. +Output: one line per request, then a summary with the number of requests, + the time taken and phpBB's final message. +Exit status: 0 when phpBB reports success, 1 when it reports an error or +stops without one, 2 for bad arguments. +""" +import argparse +import html +import os +import re +import sys +import time +from pathlib import Path +from urllib.parse import urljoin, urlencode + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from _venv import ensure_venv # noqa: E402 + +ensure_venv() + +import requests # noqa: E402 + +# seed-forum's public fixture admin; never a real board's +DEFAULT_USER = "admin" +DEFAULT_PASSWORD = "KbTest1234!" +# phpBB rejects a form submitted less than a second after it was made +FORM_WAIT = 1.1 +REFRESH = re.compile(r' str: + """The form with id form_id, or the first form whose HTML contains `containing`.""" + for match in re.finditer(r"
", page, re.S): + form = match.group(0) + opening = form[:form.index(">") + 1] + if (form_id and f'id="{form_id}"' in opening) or (not form_id and containing in form): + return form + return "" + + +def form_fields(page: str, form_id: str = "", containing: str = "") -> dict: + """The named inputs of a form (see form_html()) as a dict.""" + body = form_html(page, form_id, containing) + fields = {} + for tag in re.findall(r"]*>", body): + name = re.search(r'name="([^"]+)"', tag) + if not name: + continue + value = re.search(r'value="([^"]*)"', tag) + fields[html.unescape(name.group(1))] = html.unescape(value.group(1)) if value else "" + return fields + + +def message(page: str) -> str: + """phpBB's message box text (success or error), or the page title.""" + for pattern in (r'