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
103 lines
3.2 KiB
Python
103 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Compiles every languages/*.po into a binary .mo (no gettext tools required).
|
|
|
|
Usage: python3 bin/compile-mo.py
|
|
"""
|
|
import ast
|
|
import glob
|
|
import os
|
|
import struct
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
|
|
def parse_po(path):
|
|
messages = {}
|
|
ctx = msgid = msgstr = None
|
|
plural = None
|
|
plurals = {}
|
|
section = None
|
|
|
|
def flush():
|
|
if msgid is None:
|
|
return
|
|
key = msgid if ctx is None else ctx + "\x04" + msgid
|
|
if plural is not None:
|
|
key = key + "\x00" + plural
|
|
value = "\x00".join(plurals[i] for i in sorted(plurals))
|
|
else:
|
|
value = msgstr or ""
|
|
if msgid == "" or value:
|
|
messages[key] = value
|
|
|
|
for raw in open(path, encoding="utf-8"):
|
|
line = raw.strip()
|
|
if not line or line.startswith("#"):
|
|
continue
|
|
if line.startswith("msgctxt "):
|
|
flush()
|
|
ctx, msgid, msgstr, plural, plurals = ast.literal_eval(line[8:]), None, None, None, {}
|
|
section = "ctx"
|
|
elif line.startswith("msgid_plural "):
|
|
plural = ast.literal_eval(line[13:])
|
|
section = "plural"
|
|
elif line.startswith("msgid "):
|
|
if section != "ctx":
|
|
flush()
|
|
ctx, msgstr, plural, plurals = None, None, None, {}
|
|
msgid = ast.literal_eval(line[6:])
|
|
section = "id"
|
|
elif line.startswith("msgstr["):
|
|
idx = int(line[7:line.index("]")])
|
|
plurals[idx] = ast.literal_eval(line[line.index("]") + 1:].strip())
|
|
section = ("pl", idx)
|
|
elif line.startswith("msgstr "):
|
|
msgstr = ast.literal_eval(line[7:])
|
|
section = "str"
|
|
elif line.startswith('"'):
|
|
chunk = ast.literal_eval(line)
|
|
if section == "ctx":
|
|
ctx += chunk
|
|
elif section == "id":
|
|
msgid += chunk
|
|
elif section == "plural":
|
|
plural += chunk
|
|
elif section == "str":
|
|
msgstr += chunk
|
|
elif isinstance(section, tuple):
|
|
plurals[section[1]] += chunk
|
|
flush()
|
|
return messages
|
|
|
|
|
|
def write_mo(messages, path):
|
|
keys = sorted(messages)
|
|
ids = b""
|
|
strs = b""
|
|
offsets = []
|
|
for k in keys:
|
|
kb = k.encode("utf-8")
|
|
vb = messages[k].encode("utf-8")
|
|
offsets.append((len(ids), len(kb), len(strs), len(vb)))
|
|
ids += kb + b"\x00"
|
|
strs += vb + b"\x00"
|
|
n = len(keys)
|
|
keystart = 7 * 4 + 16 * n
|
|
valuestart = keystart + len(ids)
|
|
koffsets = []
|
|
voffsets = []
|
|
for o1, l1, o2, l2 in offsets:
|
|
koffsets += [l1, o1 + keystart]
|
|
voffsets += [l2, o2 + valuestart]
|
|
output = struct.pack("Iiiiiii", 0x950412DE, 0, n, 7 * 4, 7 * 4 + n * 8, 0, 0)
|
|
output += struct.pack("%di" % len(koffsets), *koffsets)
|
|
output += struct.pack("%di" % len(voffsets), *voffsets)
|
|
output += ids + strs
|
|
open(path, "wb").write(output)
|
|
|
|
|
|
for po in sorted(glob.glob(os.path.join(ROOT, "languages", "*.po"))):
|
|
mo = po[:-3] + ".mo"
|
|
messages = parse_po(po)
|
|
write_mo(messages, mo)
|
|
print("Compiled %s (%d entries)" % (os.path.relpath(mo, ROOT), len(messages) - 1))
|