Some checks are pending
CI / PHP lint (7.4) (pull_request) Waiting to run
CI / PHP lint (8.0) (pull_request) Waiting to run
CI / PHP lint (8.1) (pull_request) Waiting to run
CI / PHP lint (8.2) (pull_request) Waiting to run
CI / PHP lint (8.3) (pull_request) Waiting to run
CI / PHP lint (8.4) (pull_request) Waiting to run
CI / WordPress Coding Standards (pull_request) Waiting to run
CI / WordPress.org Plugin Check (pull_request) Waiting to run
New "User sync" tab that imports Microsoft 365 / Entra ID users as WordPress accounts and keeps them up to date: - Scope: whole tenant or the (nested) members of selected groups, guests optional, e-mail domain allow-list respected. Existing accounts are linked by e-mail address. - Roles: selectable default role plus a group -> role mapping (in addition to or instead of the default role, first match wins). Roles of pre-existing accounts are only managed on request. - Profile: selectable Graph attributes (names, job title, department, phones, address, language, ...) and the profile photo as avatar. - Deprovisioning: accounts disabled or deleted in Microsoft 365 (or removed from the sync groups) are deactivated or deleted; accounts deactivated by the sync are reactivated automatically. Deactivated accounts lose every sign-in path and all sessions. - Safeguards: dry run, safety stop above 20 % (min. 5) deprovisioning, abort on any Graph error, "deleted" only on a 404 for the object ID, protected pre-existing administrators and own account, content reassignment required for deletion, run lock. - Runs manually, via WP-Cron or `wp m365-login sync [--dry-run]`. - Users screen column with deactivate/reactivate row actions and a read-only Microsoft 365 section on the profile screen. The Graph client gains paging, retry on throttling and user, group member and photo endpoints. The group picker is now reusable. Version 1.1.0, German translations (du/Sie), docs and audit addendum. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
94 lines
3.8 KiB
Python
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.1.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-23T00: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)))
|