Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/nouveauxterritoires/ip-restriction-for-restrict-content/archive/refs/heads/main.zipRestriction par adresse IP pour Restrict Content et Restrict Content Pro.
Extension indépendante, ni développée ni approuvée par les éditeurs de Restrict Content ou de Restrict Content Pro.
État réel du projet
Les dix lots sont faits, et la version est 1.0.0. Une adresse peut être
autorisée globalement, par niveau d'adhésion ou par contenu, les
contenus restreints par catégorie sont couverts, une adresse de la liste de
blocage ne peut pas s'inscrire, et make build produit l'archive distribuable.
Ce qui est livré et vérifié :
| Livré | Ce qui l'atteste |
|---|---|
| Environnement de développement reproductible en une commande | make up provisionne WordPress, la base, la base de test, la capture d'e-mails et la dépendance |
| Amorçage conditionnel du plugin | src/Plugin.php, suite integration |
| Contrôle des prérequis par capacité, jamais par version ni par nom de plugin | src/Plugin.php, src/Domain/, suites unit et integration |
| Sonde étendue aux quatorze capacités du cahier des charges §5.1, jamais mémorisée | src/Integration/Capability_Probe.php, tests/integration/ProbePersistenceTest.php |
| Avis d'administration nommant la cause constatée — dépendance absente, ou active sur sa base de code historique — et l'action de retour | src/Admin/Dependency_Labels.php, suites unit et integration |
| Écran d'état des capacités, en lecture seule, disponible même quand la dépendance ne l'est pas | src/Admin/Capability_Status_Page.php, tests/integration/CapabilityStatusPageTest.php |
| Preuve par l'exécution que chaque accroche du §5.1 se déclenche | tests/contract/, 36 tests contre Restrict Content 4.0.5 |
| Preuve par l'exécution que la bascule de branche amont est réversible, sous capacité et sous nonce | tests/contract/BranchSwitchContractTest.php |
| Moteur de correspondance : grammaire close, quatre formes normalisées en intervalles, IPv4 et IPv6, décision sur un ensemble | src/Rule/, src/Address/Ip_Number.php, 93 tests de la suite matching |
| Couverture de 100 % des lignes et des branches sur le moteur | bin/check-coverage.sh, bin/lib/check-branches.php |
| Aucun appel à WordPress dans la couche de décision, vérifié mécaniquement | tests/unit/Tooling/PurityTest.php |
Résolution de l'adresse du visiteur : REMOTE_ADDR par défaut, en-tête lu seulement derrière un proxy déclaré, chaîne remontée de droite à gauche |
src/Address/Client_Address_Resolver.php, src/Http/Server_Environment.php |
| Expurgation avant toute écriture : jamais d'adresse complète dans un message du plugin | src/Support/Redactor.php |
| Écran de réglages sous le menu de la dépendance : les deux listes, le message de blocage, les proxys, l'en-tête à lire, la suppression à la désinstallation | src/Admin/Settings_Page.php, suite access |
| Validation à l'écriture avec la même sévérité qu'à la lecture, et avis persistant nommant chaque entrée ignorée, sa ligne et son motif | src/Settings/Settings_Validator.php, src/Admin/Rejection_Notice.php |
| Écran de diagnostic : l'adresse retenue pour la requête en cours et sa provenance | src/Admin/Settings_Page.php, suite access |
| Autorisation globale : une adresse de la liste accède à tout contenu restreint, connectée ou non, sans obtenir aucune capacité | src/Integration/Content_Access.php, src/Policy/, suite access |
| Une réponse élargie par une autorisation n'entre pas dans un cache partagé | src/Integration/Cache_Guard.php |
| Points d'extension du §8 sur le chemin d'accès, dont un filtre qui ne peut que retirer un octroi | src/Support/Extension_Points.php, suite access |
| Blocage d'inscription : une adresse de la liste ne peut pas soumettre le formulaire, avec un message avare et échappé | src/Integration/Registration_Guard.php, suite access |
| Les deux listes ne partagent ni stockage, ni chemin de décision, ni instance (A-01) | src/Policy/, tests sur les signatures |
| Portée par niveau d'adhésion : champ sur les écrans de niveau, stockage en métadonnée, et non-transitivité — une autorisation sur un niveau n'ouvre aucun autre niveau | src/Admin/Level_Field.php, src/Policy/Access_Policy.php, suite access |
| Portée par contenu : champ dans la boîte de restriction de la dépendance, stockage en métadonnée protégée, nonce et capacité vérifiée sur le contenu enregistré — un identifiant substitué dans la requête ne pose aucune autorisation | src/Admin/Post_Metabox.php, src/Policy/Access_Policy.php, suite access |
| Contenus restreints par catégorie : couverts par le même branchement que le reste, sans toucher aux données de la catégorie | src/Integration/Content_Access.php, suite access |
| Archives de catégorie restreintes : couvertes en option désactivée par défaut, parce que la dépendance n'offre là aucun point de décision et que la seule voie altère une donnée partagée | src/Integration/Term_Access.php, suite access, §5.1.1 |
| Désinstallation complète sur demande — réglages, listes de niveau et listes de contenu —, et jamais rien d'autre | uninstall.php, suite integration |
| Archive distribuable sans test, sans documentation ni outillage, et exigences de livraison tenues par des tests | bin/build.sh, readme.txt, suite unit |
| Les cinq suites de tests sont peuplées — 412 tests | phpunit.xml.dist, tests/ |
| Scanners de sécurité et d'hygiène, bloquants | bin/scan-secrets.sh, bin/scan-placeholders.sh |
| Intégration continue : lint, analyse statique, scanners, suites peuplées, seuil de couverture | .github/workflows/ci.yml |
Rien d'autre. Ce qui n'est délibérément pas dans ce plugin — import de listes, journal d'audit, détection de VPN — n'y entrera pas : chacun de ces sujets est un plugin, pas une fonctionnalité de celui-ci.
L'utilisation du plugin est documentée pour l'administrateur dans
docs/utilisation.md, qui énonce aussi les comportements
que la documentation amont laisse indéterminés.
Les trois portées ne se cumulent pas comme on le croit
| Autorisation posée sur | Ouvre | N'ouvre pas |
|---|---|---|
| La liste globale | Tout contenu restreint du site, tous niveaux confondus | — |
| Un niveau d'adhésion | Les contenus restreints à ce niveau | Les contenus d'un autre niveau, ni ceux restreints autrement (rôle, niveau d'accès) |
| Un contenu | Ce seul contenu | Aucun autre contenu, même du même niveau ou du même auteur |
Une catégorie ne porte pas de liste. Un contenu restreint par sa catégorie est ouvert par la liste globale, ou par celle d'un niveau que cette catégorie exige : la restriction de catégorie est évaluée par la dépendance à l'intérieur de sa décision, et c'est cette décision que le plugin élargit.
La page d'archive d'une catégorie restreinte est un cas à part, et la seule fonctionnalité optionnelle du plugin. La dépendance n'y offre aucun point de décision : la seule voie est de neutraliser la restriction de la catégorie demandée pendant la requête, et tout autre code qui relit cette restriction pendant cette requête la verra donc vide. L'option est désactivée par défaut, et l'effet est borné à la catégorie réellement demandée — jamais une autre, jamais un écran d'administration.
Une autorisation de niveau n'est pas une autorisation globale restreinte : c'est une autorisation qui ne s'applique qu'aux contenus de ce niveau. Un contenu réservé à « Or » ne regarde jamais la liste d'« Argent », et un article ne regarde jamais la liste d'un autre article.
Les deux listes ne se ressemblent que de loin
| Liste d'autorisation | Liste de blocage | |
|---|---|---|
| Effet | Accorde la lecture d'un contenu restreint | Refuse une inscription |
| Effet sur l'autre fonction | Aucun : une adresse autorisée n'est pas bloquée à l'inscription | Aucun : une adresse bloquée n'obtient ni plus ni moins d'accès au contenu |
| Portées | Globale, par niveau d'adhésion, par contenu | Globale uniquement |
| Erreur par excès | Fuite d'accès silencieuse | Inscriptions légitimes refusées, donc signalées |
Les deux n'ont aucun stockage, aucun cache ni aucun chemin de décision en commun : une confusion de champ ne peut pas transformer un blocage en autorisation. Ce que le plugin fera est spécifié dans le cahier des charges ; le découpage en lots est au §13.
La suite contract est exécutée contre les deux variantes en intégration
continue, Restrict Content Pro compris : OK (36 tests, 77 assertions). En
local, la variante commerciale demande son archive, qui n'est jamais versionnée
(make test-contract-pro, voir les prérequis ci-dessous).
Formats acceptés par le moteur
| Forme | Exemple | IPv4 | IPv6 |
|---|---|---|---|
| Littéral | 226.216.136.78, 2001:db8::1 |
oui | oui |
| Joker sur les octets de fin | 200.122.216.*, 186.*.*.*, 149.58.* |
oui | non (ADR-0005) |
| Plage, bornes incluses | 159.7.89.231-159.7.89.240 |
oui | oui |
| CIDR | 10.0.0.0/24, 2001:db8::/32 |
oui | oui |
| Plage à bornes jokers | 149.58.*-149.60.* |
oui | non |
Quelle adresse est évaluée
Par défaut, l'adresse de la connexion TCP (REMOTE_ADDR), et rien d'autre :
aucun en-tête HTTP n'est lu, parce que tout en-tête est écrit par le client. Un
visiteur qui envoie X-Forwarded-For: <adresse autorisée> n'obtient rien.
Derrière un proxy ou un CDN, il faut déclarer deux choses : la liste des
proxys de confiance (même grammaire que les listes d'adresses) et le nom de
l'en-tête à lire, parmi X-Forwarded-For, CF-Connecting-IP, True-Client-IP
et X-Real-IP. L'en-tête n'est alors lu que si la connexion vient d'un proxy
déclaré, et la chaîne est remontée de droite à gauche en sautant les proxys
connus : la valeur qu'un client aurait préfixée n'est jamais retenue.
Si l'adresse ne peut pas être déterminée — en-tête absent ou illisible, chaîne entièrement composée de proxys —, elle est indéterminable : aucune règle ne correspond, et aucun accès n'est accordé. Le plugin ne se replie jamais sur l'adresse du proxy, qui est celle du CDN.
Une entrée par ligne ; les lignes vides, les espaces de bordure et les
commentaires introduits par # sont ignorés. Une entrée que l'analyseur ne
comprend pas est rejetée avec un motif nommé, jamais interprétée par
approximation : familles mêlées, plage inversée, préfixe hors bornes, joker
devant un littéral, règle couvrant toutes les adresses, joker dans un CIDR, nom
d'hôte, joker IPv6, octet à zéro de tête.
Démarrage en trois commandes
cp .env.example .env
make up
make test-unit
make up démarre la pile Docker et provisionne le site sur
http://localhost:8082 (capture d'e-mails sur http://localhost:8027). Les
ports sont paramétrables dans .env.
make help liste toutes les cibles et ce qu'elles font réellement.
Commandes utiles
| Commande | Effet |
|---|---|
make test |
Toutes les suites, séquentiellement |
make test-unit |
Logique pure et outillage, WordPress mocké |
make test-integration |
WordPress réel |
make test-contract |
Accroches contre Restrict Content |
make test-contract-pro |
Accroches contre Restrict Content Pro |
make lint / make fix |
PHPCS / PHPCBF |
make stan |
PHPStan niveau 5 |
make scan |
Scanners de secrets et de marqueurs |
make ci |
L'enchaînement complet |
Les suites de tests ne peuvent pas être exécutées dans une même invocation :
la suite unit remplace les fonctions de WordPress, les autres les chargent.
tests/bootstrap.php refuse une invocation sans --testsuite et le dit.
Prérequis
| Pour développer | Pour exécuter le plugin |
|---|---|
| Docker et Docker Compose v2 | PHP 8.1 ou plus récent |
| WordPress 6.4 ou plus récent | |
| Restrict Content ou Restrict Content Pro, actif et chargeant sa base de code moderne |
La dernière condition n'est pas rhétorique. Restrict Content embarque deux bases de code mutuellement exclusives, et la branche historique n'expose aucune des fonctions dont ce plugin dépend. Pire : le choix entre les deux est réversible à tout moment depuis l'administration de la dépendance. Un administrateur peut donc désactiver ce plugin en cours de vie du site, sans le désinstaller — et recevra alors un avis d'administration qui le lui dit et nomme le retour en arrière.
Le constat, avec ses preuves et ses numéros de ligne, est en
docs/journal-empirique.md (JE-003).
La variante commerciale n'est jamais versionnée dans ce dépôt : déposez son
archive dans vendor-plugins/, ou désignez un répertoire décompressé par
RCP_PRO_SOURCE.
Documents
| Document | Contenu |
|---|---|
docs/utilisation.md |
La documentation utilisateur : les trois portées, la grammaire des entrées, les proxys, l'option des archives de catégorie, la désinstallation |
docs/cahier-des-charges.md |
Le document central : exigences, règles de décision, sécurité, tests, lots, points ouverts |
docs/architecture.md |
Les trois couches, l'arborescence src/ prévue, les objets de valeur, les points d'extension |
docs/tests.md |
Rôle de chaque suite, ce que chacune doit établir, fixtures, seuils |
docs/securite.md |
Les dix-huit exigences SEC-xx, leur état réel, le test qui les couvre, le modèle de menace |
docs/journal-empirique.md |
Les constats sur la dépendance et l'outillage, avec leur preuve |
CHANGELOG.md |
Format Keep a Changelog |
Décisions d'architecture
| ADR | Décision |
|---|---|
| 0001 | Deux formes du préfixe |
| 0002 | PHP 8.1, WordPress 6.4 |
| 0003 | Aucune table dédiée |
| 0004 | Réglage de niveau en métadonnée de niveau |
| 0005 | Étendue du support IPv6 |
| 0006 | REMOTE_ADDR par défaut, en-tête sur déclaration explicite |
| 0007 | Les deux listes ne partagent ni stockage ni chemin de décision |
| 0008 | Slug conservé, mention de non-affiliation maintenue |
| 0009 | Réglages propres à chaque site d'un réseau |
Identité
| Élément | Valeur |
|---|---|
| Slug | ip-restriction-for-restrict-content |
| Namespace | Ip_Restriction_For_Restrict_Content\ |
| Préfixe des fonctions et des hooks | ip_restriction_for_restrict_content_ |
| Préfixe court | RCIPR_ / rcipr_ |
| Text domain | ip-restriction-for-restrict-content |
| Port de développement | 8082 |
Le slug commence par ip-restriction, une fonction générique, et mentionne la
marque après « for » : c'est la forme que recommande la directive 17 du
répertoire WordPress.org, qui n'interdit que le début du slug. Le nommage
est donc arrêté — voir ADR-0008.
La mention de non-affiliation en tête de ce document reste nécessaire : elle répond à une exigence distincte de la conformité du slug, et l'une ne dispense pas de l'autre (cahier des charges §14.3).
Licence
GPL-2.0-or-later. Voir LICENSE.