wp-m365-login/bin/make-pot.py
friloo 3e3e87b399
Add M365 Login plugin: Microsoft Entra ID sign-in for existing users
Adds a WordPress plugin that places a customisable "Sign in with
Microsoft" button on wp-login.php and signs existing users in via the
OpenID Connect authorization code flow with PKCE. Users are matched by
e-mail address only; no accounts are created.

Security: single-use state/nonce bound to an HttpOnly cookie, ID token
signature verification against Microsoft's JWKS (RS256 only) with
issuer/audience/tenant/expiry/nonce checks, optional tenant pinning,
account binding to the Microsoft object ID, e-mail domain allow-list,
client secret encrypted at rest (AES-256-GCM).

Admin: settings screen with connection, button and security tabs, live
button preview, colour presets, media-library icon picker, redirect URI
copy button and tenant connectivity test.

Packaging for WordPress.org: readme.txt with External services section,
GPL-2.0 license, uninstall.php, POT + German translations, .distignore,
build script, PHPCS config and CI running Plugin Check.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJxAHYdMfKPoN4koRc4Ci2
2026-09-22 14:21:10 +00:00

94 lines
3.8 KiB
Python

#!/usr/bin/env python3
"""Minimal POT generator for this plugin (fallback when WP-CLI is unavailable).
Usage: python3 bin/make-pot.py
Prefer `wp i18n make-pot . languages/m365-login.pot` when WP-CLI is installed.
"""
import os
import re
import sys
from collections import OrderedDict
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DOMAIN = "m365-login"
FUNCS = r"(?:__|_e|esc_html__|esc_html_e|esc_attr__|esc_attr_e|_x|_ex|esc_html_x|esc_attr_x|_n|_nx)"
STR = r"(?:'((?:[^'\\]|\\.)*)'|\"((?:[^\"\\]|\\.)*)\")"
PATTERN = re.compile(FUNCS + r"\s*\(\s*" + STR + r"(?:\s*,\s*" + STR + r")?(?:\s*,\s*" + STR + r")?\s*[,)]", re.S)
COMMENT = re.compile(r"/\*\s*translators:\s*(.*?)\*/", re.S)
def unescape(s):
return s.replace("\\'", "'").replace('\\"', '"').replace("\\\\", "\\")
def po_escape(s):
return s.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")
entries = OrderedDict()
for dirpath, dirnames, filenames in os.walk(ROOT):
dirnames[:] = [d for d in dirnames if d not in (".git", "vendor", "node_modules", "build", "bin", "docs", ".github", ".wordpress-org")]
for fn in sorted(filenames):
if not fn.endswith(".php"):
continue
path = os.path.join(dirpath, fn)
rel = os.path.relpath(path, ROOT)
src = open(path, encoding="utf-8").read()
for m in PATTERN.finditer(src):
func = m.group(0).split("(")[0].strip()
msgid = unescape(m.group(1) if m.group(1) is not None else m.group(2))
second = m.group(3) if m.group(3) is not None else m.group(4)
third = m.group(5) if m.group(5) is not None else m.group(6)
context = None
plural = None
if func in ("_x", "_ex", "esc_html_x", "esc_attr_x"):
context = unescape(second) if second is not None else None
elif func in ("_n",):
plural = unescape(second) if second is not None else None
elif func == "_nx":
plural = unescape(second) if second is not None else None
context = unescape(third) if third is not None else None
line = src.count("\n", 0, m.start()) + 1
before = src[max(0, m.start() - 400):m.start()]
cm = COMMENT.findall(before)
comment = " ".join(cm[-1].split()) if cm else None
key = (context, msgid, plural)
e = entries.setdefault(key, {"refs": [], "comment": None})
e["refs"].append("%s:%d" % (rel, line))
if comment:
e["comment"] = comment
header = '''# Copyright (C) 2026 friloo
# This file is distributed under the GPL-2.0-or-later.
msgid ""
msgstr ""
"Project-Id-Version: M365 Login 1.0.0\\n"
"Report-Msgid-Bugs-To: https://github.com/friloo/wp-m365-login/issues\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
"POT-Creation-Date: 2026-09-22T00:00:00+00:00\\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"
"Language-Team: LANGUAGE <LL@li.org>\\n"
"X-Generator: bin/make-pot.py\\n"
"X-Domain: m365-login\\n"
'''
out = [header]
for (context, msgid, plural), e in entries.items():
if e["comment"]:
out.append("#. translators: %s\n" % e["comment"])
out.append("#: %s\n" % " ".join(e["refs"]))
if context:
out.append('msgctxt "%s"\n' % po_escape(context))
out.append('msgid "%s"\n' % po_escape(msgid))
if plural:
out.append('msgid_plural "%s"\n' % po_escape(plural))
out.append('msgstr[0] ""\nmsgstr[1] ""\n\n')
else:
out.append('msgstr ""\n\n')
dest = os.path.join(ROOT, "languages", DOMAIN + ".pot")
open(dest, "w", encoding="utf-8").write("".join(out))
print("Wrote %s (%d strings)" % (os.path.relpath(dest, ROOT), len(entries)))