dolibarr/htdocs/modulebuilder/class/NamingContractValidator.class.php

191 lines
6.4 KiB
PHP
Raw Permalink Normal View History

NEW : Replace MyObject MyModule occurences (#38370) * [COREMB] Centralize myobject/mymodule substitutions via NamingContract value object Introduce NamingContract (immutable value object) and NamingContractValidator to replace 6 scattered ad-hoc $arrayreplacement blocks across index.php and modulebuilder.lib.php. Fixes a critical bug where MYOBJECT token was commented out in initobject, leaving generated class files with unsubstituted placeholders. - NamingContract: canonical ordered substitution map (12 tokens with object, 7 module-only), applyTo() via str_replace (not make_substitutions, to avoid processing Dolibarr __(key)__ patterns in raw templates), applyToFilename() for lowercase-only filename substitution - StrictNamingContractValidator: post-generation scan for residual myobject/ mymodule tokens, skipping /* BEGIN/END MODULEBUILDER */ structural markers - modulebuilderValidateGeneratedFile() helper: non-blocking warnings with XSS-safe output via dol_escape_htmltag() - Collision guard: InvalidArgumentException if module === object name (case-insensitive), wrapped in try-catch at all instantiation sites - 24 PHPUnit tests, 52 assertions * fix(initobject): apply object substitution to all module PHP files After initmodule creates the module structure, files like index.php, lib/module.lib.php, admin/setup.php and class/api_module.class.php still contain myobject/mymodule placeholders. initobject now scans ALL PHP files in the module directory and applies the full NamingContract substitution, and deletes the orphan stats/myobject_index.php placeholder. * fix(initmodule): prevent array_merge from renumbering the '500000' key PHP casts numeric string keys like '500000' to int(500000) internally. array_merge() then renumbers integer keys starting from 0, turning '500000' into key 0. make_substitutions() then calls str_replace(0, ...) which casts 0 to the string '0', replacing EVERY zero digit in every generated file with the idmodule value. Fix: replace the bare '500000' key with '$this->numero = 500000' so that array_merge sees a non-numeric string key and preserves it as-is. * Fix initapi: apply header substitution after addObjectsToApiFile addObjectsToApiFile substitutes properties/constructor/includes via regex but leaves PHPDoc header and class declaration with mymodule/myobject tokens. Fix: apply $arrayreplacement (minus 'MYOBJECT' uppercase key) after addObjectsToApiFile to resolve the remaining residuals. 'MYOBJECT' is excluded to preserve the /* BEGIN MODULEBUILDER API MYOBJECT */ placeholder that addObjectsToApiFile relies on for future object additions. * Move NamingContractTest to core test directory NamingContract and NamingContractValidator are core modulebuilder classes; their tests belong alongside other Dolibarr core tests in test/phpunit/, not in htdocs/modulebuilder/test/phpunit/ (the custom-module pattern). Updated require_once paths accordingly. * fix useless file * fix(phpcs): add missing @param descriptions and function docblocks - NamingContract: merge adjacent string literals in exception message, add description to @param $content in applyTo() - NamingContractValidator: add descriptions to all bare @param tags in interface and StrictNamingContractValidator methods - NamingContractTest: add /** @return void */ docblocks to all test methods, merge unnecessary string concat on template variable * fix(phan/phpstan): remove readonly, add baselines for test file - NamingContract: replace readonly properties with plain public string (readonly requires PHP 8.1, phan minimum target is PHP 7.2) - dev/tools/phan/baseline.txt: add NamingContractTest.php suppression for PhanUndeclaredMethod (PHPUnit stubs not available in phan scope) - NamingContractTest.php: add @phpstan-ignore class.notFound on class declaration and method.notFound on each assertion call (PHPUnit not in phpstan bootstrap, consistent with CommonClassTest.class.php) * fix(phan/phpstan): suppress typed property warnings, NoopNew, class.notFound - baseline.txt: add PhanCompatibleTypedProperty for NamingContract.class.php (typed properties require PHP 7.4, phan min target is 7.2 — warning only but phan exits 1 on any issue including warnings) - baseline.txt: add PhanNoopNew for NamingContractTest.php (constructor called for exception side-effect in guard tests is intentional) - NamingContractTest.php: move @phpstan-ignore class.notFound to inline comment on class declaration (docblock placement not picked up by phpstan) * fix(phan/phpstan): add missing phan suppress on index.php l.574, fix phpstan class docblock - index.php l.574: add @phan-suppress-next-line PhanPluginSuspiciousParamPosition (same pattern as l.577 and l.587 for identical dolReplaceInFile calls where local variable name $destfile matches parameter #3 of the function) - NamingContractTest.php: use standalone /** @phpstan-ignore class.notFound */ docblock before class declaration (inline // comment not picked up by phpstan on class declarations; separate docblock matches CommonClassTest.class.php pattern) * Update baseline.txt * fix(phan): add phan-file-suppress on NamingContractTest for PHPUnit undeclared methods * fix(phan): suppress PhanNoopNew on expectException patterns in NamingContractTest --------- Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-05-25 12:52:38 +00:00
<?php
/* Copyright (C) 2026 ATM Consulting <support@atm-consulting.fr>
* Copyright (C) 2026 MDW <mdeweerd@users.noreply.github.com>
NEW : Replace MyObject MyModule occurences (#38370) * [COREMB] Centralize myobject/mymodule substitutions via NamingContract value object Introduce NamingContract (immutable value object) and NamingContractValidator to replace 6 scattered ad-hoc $arrayreplacement blocks across index.php and modulebuilder.lib.php. Fixes a critical bug where MYOBJECT token was commented out in initobject, leaving generated class files with unsubstituted placeholders. - NamingContract: canonical ordered substitution map (12 tokens with object, 7 module-only), applyTo() via str_replace (not make_substitutions, to avoid processing Dolibarr __(key)__ patterns in raw templates), applyToFilename() for lowercase-only filename substitution - StrictNamingContractValidator: post-generation scan for residual myobject/ mymodule tokens, skipping /* BEGIN/END MODULEBUILDER */ structural markers - modulebuilderValidateGeneratedFile() helper: non-blocking warnings with XSS-safe output via dol_escape_htmltag() - Collision guard: InvalidArgumentException if module === object name (case-insensitive), wrapped in try-catch at all instantiation sites - 24 PHPUnit tests, 52 assertions * fix(initobject): apply object substitution to all module PHP files After initmodule creates the module structure, files like index.php, lib/module.lib.php, admin/setup.php and class/api_module.class.php still contain myobject/mymodule placeholders. initobject now scans ALL PHP files in the module directory and applies the full NamingContract substitution, and deletes the orphan stats/myobject_index.php placeholder. * fix(initmodule): prevent array_merge from renumbering the '500000' key PHP casts numeric string keys like '500000' to int(500000) internally. array_merge() then renumbers integer keys starting from 0, turning '500000' into key 0. make_substitutions() then calls str_replace(0, ...) which casts 0 to the string '0', replacing EVERY zero digit in every generated file with the idmodule value. Fix: replace the bare '500000' key with '$this->numero = 500000' so that array_merge sees a non-numeric string key and preserves it as-is. * Fix initapi: apply header substitution after addObjectsToApiFile addObjectsToApiFile substitutes properties/constructor/includes via regex but leaves PHPDoc header and class declaration with mymodule/myobject tokens. Fix: apply $arrayreplacement (minus 'MYOBJECT' uppercase key) after addObjectsToApiFile to resolve the remaining residuals. 'MYOBJECT' is excluded to preserve the /* BEGIN MODULEBUILDER API MYOBJECT */ placeholder that addObjectsToApiFile relies on for future object additions. * Move NamingContractTest to core test directory NamingContract and NamingContractValidator are core modulebuilder classes; their tests belong alongside other Dolibarr core tests in test/phpunit/, not in htdocs/modulebuilder/test/phpunit/ (the custom-module pattern). Updated require_once paths accordingly. * fix useless file * fix(phpcs): add missing @param descriptions and function docblocks - NamingContract: merge adjacent string literals in exception message, add description to @param $content in applyTo() - NamingContractValidator: add descriptions to all bare @param tags in interface and StrictNamingContractValidator methods - NamingContractTest: add /** @return void */ docblocks to all test methods, merge unnecessary string concat on template variable * fix(phan/phpstan): remove readonly, add baselines for test file - NamingContract: replace readonly properties with plain public string (readonly requires PHP 8.1, phan minimum target is PHP 7.2) - dev/tools/phan/baseline.txt: add NamingContractTest.php suppression for PhanUndeclaredMethod (PHPUnit stubs not available in phan scope) - NamingContractTest.php: add @phpstan-ignore class.notFound on class declaration and method.notFound on each assertion call (PHPUnit not in phpstan bootstrap, consistent with CommonClassTest.class.php) * fix(phan/phpstan): suppress typed property warnings, NoopNew, class.notFound - baseline.txt: add PhanCompatibleTypedProperty for NamingContract.class.php (typed properties require PHP 7.4, phan min target is 7.2 — warning only but phan exits 1 on any issue including warnings) - baseline.txt: add PhanNoopNew for NamingContractTest.php (constructor called for exception side-effect in guard tests is intentional) - NamingContractTest.php: move @phpstan-ignore class.notFound to inline comment on class declaration (docblock placement not picked up by phpstan) * fix(phan/phpstan): add missing phan suppress on index.php l.574, fix phpstan class docblock - index.php l.574: add @phan-suppress-next-line PhanPluginSuspiciousParamPosition (same pattern as l.577 and l.587 for identical dolReplaceInFile calls where local variable name $destfile matches parameter #3 of the function) - NamingContractTest.php: use standalone /** @phpstan-ignore class.notFound */ docblock before class declaration (inline // comment not picked up by phpstan on class declarations; separate docblock matches CommonClassTest.class.php pattern) * Update baseline.txt * fix(phan): add phan-file-suppress on NamingContractTest for PHPUnit undeclared methods * fix(phan): suppress PhanNoopNew on expectException patterns in NamingContractTest --------- Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-05-25 12:52:38 +00:00
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* \file htdocs/modulebuilder/class/NamingContractValidator.class.php
* \ingroup modulebuilder
* \brief Validator for generated file content detects residual naming tokens.
*/
/**
* Validates that generated modulebuilder files contain no residual myobject/mymodule tokens.
*/
interface NamingContractValidator
{
/**
* Scan file content for residual naming tokens after substitution.
*
* Lines containing non-renamable MODULEBUILDER structural markers are excluded from validation.
*
* @param string $content Full file content to validate
* @param string $filePath Used in error messages only
* @return string[] List of human-readable errors empty array means valid
*/
public function validateContent(string $content, string $filePath): array;
/**
* Validate that a PHP class name matches the contract's objectNameCase.
*
* @param string $className Class name to validate
* @param NamingContract $nc Naming contract to compare against
* @return bool True if className matches object naming
NEW : Replace MyObject MyModule occurences (#38370) * [COREMB] Centralize myobject/mymodule substitutions via NamingContract value object Introduce NamingContract (immutable value object) and NamingContractValidator to replace 6 scattered ad-hoc $arrayreplacement blocks across index.php and modulebuilder.lib.php. Fixes a critical bug where MYOBJECT token was commented out in initobject, leaving generated class files with unsubstituted placeholders. - NamingContract: canonical ordered substitution map (12 tokens with object, 7 module-only), applyTo() via str_replace (not make_substitutions, to avoid processing Dolibarr __(key)__ patterns in raw templates), applyToFilename() for lowercase-only filename substitution - StrictNamingContractValidator: post-generation scan for residual myobject/ mymodule tokens, skipping /* BEGIN/END MODULEBUILDER */ structural markers - modulebuilderValidateGeneratedFile() helper: non-blocking warnings with XSS-safe output via dol_escape_htmltag() - Collision guard: InvalidArgumentException if module === object name (case-insensitive), wrapped in try-catch at all instantiation sites - 24 PHPUnit tests, 52 assertions * fix(initobject): apply object substitution to all module PHP files After initmodule creates the module structure, files like index.php, lib/module.lib.php, admin/setup.php and class/api_module.class.php still contain myobject/mymodule placeholders. initobject now scans ALL PHP files in the module directory and applies the full NamingContract substitution, and deletes the orphan stats/myobject_index.php placeholder. * fix(initmodule): prevent array_merge from renumbering the '500000' key PHP casts numeric string keys like '500000' to int(500000) internally. array_merge() then renumbers integer keys starting from 0, turning '500000' into key 0. make_substitutions() then calls str_replace(0, ...) which casts 0 to the string '0', replacing EVERY zero digit in every generated file with the idmodule value. Fix: replace the bare '500000' key with '$this->numero = 500000' so that array_merge sees a non-numeric string key and preserves it as-is. * Fix initapi: apply header substitution after addObjectsToApiFile addObjectsToApiFile substitutes properties/constructor/includes via regex but leaves PHPDoc header and class declaration with mymodule/myobject tokens. Fix: apply $arrayreplacement (minus 'MYOBJECT' uppercase key) after addObjectsToApiFile to resolve the remaining residuals. 'MYOBJECT' is excluded to preserve the /* BEGIN MODULEBUILDER API MYOBJECT */ placeholder that addObjectsToApiFile relies on for future object additions. * Move NamingContractTest to core test directory NamingContract and NamingContractValidator are core modulebuilder classes; their tests belong alongside other Dolibarr core tests in test/phpunit/, not in htdocs/modulebuilder/test/phpunit/ (the custom-module pattern). Updated require_once paths accordingly. * fix useless file * fix(phpcs): add missing @param descriptions and function docblocks - NamingContract: merge adjacent string literals in exception message, add description to @param $content in applyTo() - NamingContractValidator: add descriptions to all bare @param tags in interface and StrictNamingContractValidator methods - NamingContractTest: add /** @return void */ docblocks to all test methods, merge unnecessary string concat on template variable * fix(phan/phpstan): remove readonly, add baselines for test file - NamingContract: replace readonly properties with plain public string (readonly requires PHP 8.1, phan minimum target is PHP 7.2) - dev/tools/phan/baseline.txt: add NamingContractTest.php suppression for PhanUndeclaredMethod (PHPUnit stubs not available in phan scope) - NamingContractTest.php: add @phpstan-ignore class.notFound on class declaration and method.notFound on each assertion call (PHPUnit not in phpstan bootstrap, consistent with CommonClassTest.class.php) * fix(phan/phpstan): suppress typed property warnings, NoopNew, class.notFound - baseline.txt: add PhanCompatibleTypedProperty for NamingContract.class.php (typed properties require PHP 7.4, phan min target is 7.2 — warning only but phan exits 1 on any issue including warnings) - baseline.txt: add PhanNoopNew for NamingContractTest.php (constructor called for exception side-effect in guard tests is intentional) - NamingContractTest.php: move @phpstan-ignore class.notFound to inline comment on class declaration (docblock placement not picked up by phpstan) * fix(phan/phpstan): add missing phan suppress on index.php l.574, fix phpstan class docblock - index.php l.574: add @phan-suppress-next-line PhanPluginSuspiciousParamPosition (same pattern as l.577 and l.587 for identical dolReplaceInFile calls where local variable name $destfile matches parameter #3 of the function) - NamingContractTest.php: use standalone /** @phpstan-ignore class.notFound */ docblock before class declaration (inline // comment not picked up by phpstan on class declarations; separate docblock matches CommonClassTest.class.php pattern) * Update baseline.txt * fix(phan): add phan-file-suppress on NamingContractTest for PHPUnit undeclared methods * fix(phan): suppress PhanNoopNew on expectException patterns in NamingContractTest --------- Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-05-25 12:52:38 +00:00
*/
public function validateClassName(string $className, NamingContract $nc): bool;
/**
* Validate that a trigger filename matches the expected pattern.
*
* Expected pattern: interface_NN_mod{ModuleNameCase}_{ModuleNameCase}Triggers.class.php
*
* @param string $filename Trigger filename to validate
* @param NamingContract $nc Naming contract to compare against
* @return bool True if filename matches pattern
NEW : Replace MyObject MyModule occurences (#38370) * [COREMB] Centralize myobject/mymodule substitutions via NamingContract value object Introduce NamingContract (immutable value object) and NamingContractValidator to replace 6 scattered ad-hoc $arrayreplacement blocks across index.php and modulebuilder.lib.php. Fixes a critical bug where MYOBJECT token was commented out in initobject, leaving generated class files with unsubstituted placeholders. - NamingContract: canonical ordered substitution map (12 tokens with object, 7 module-only), applyTo() via str_replace (not make_substitutions, to avoid processing Dolibarr __(key)__ patterns in raw templates), applyToFilename() for lowercase-only filename substitution - StrictNamingContractValidator: post-generation scan for residual myobject/ mymodule tokens, skipping /* BEGIN/END MODULEBUILDER */ structural markers - modulebuilderValidateGeneratedFile() helper: non-blocking warnings with XSS-safe output via dol_escape_htmltag() - Collision guard: InvalidArgumentException if module === object name (case-insensitive), wrapped in try-catch at all instantiation sites - 24 PHPUnit tests, 52 assertions * fix(initobject): apply object substitution to all module PHP files After initmodule creates the module structure, files like index.php, lib/module.lib.php, admin/setup.php and class/api_module.class.php still contain myobject/mymodule placeholders. initobject now scans ALL PHP files in the module directory and applies the full NamingContract substitution, and deletes the orphan stats/myobject_index.php placeholder. * fix(initmodule): prevent array_merge from renumbering the '500000' key PHP casts numeric string keys like '500000' to int(500000) internally. array_merge() then renumbers integer keys starting from 0, turning '500000' into key 0. make_substitutions() then calls str_replace(0, ...) which casts 0 to the string '0', replacing EVERY zero digit in every generated file with the idmodule value. Fix: replace the bare '500000' key with '$this->numero = 500000' so that array_merge sees a non-numeric string key and preserves it as-is. * Fix initapi: apply header substitution after addObjectsToApiFile addObjectsToApiFile substitutes properties/constructor/includes via regex but leaves PHPDoc header and class declaration with mymodule/myobject tokens. Fix: apply $arrayreplacement (minus 'MYOBJECT' uppercase key) after addObjectsToApiFile to resolve the remaining residuals. 'MYOBJECT' is excluded to preserve the /* BEGIN MODULEBUILDER API MYOBJECT */ placeholder that addObjectsToApiFile relies on for future object additions. * Move NamingContractTest to core test directory NamingContract and NamingContractValidator are core modulebuilder classes; their tests belong alongside other Dolibarr core tests in test/phpunit/, not in htdocs/modulebuilder/test/phpunit/ (the custom-module pattern). Updated require_once paths accordingly. * fix useless file * fix(phpcs): add missing @param descriptions and function docblocks - NamingContract: merge adjacent string literals in exception message, add description to @param $content in applyTo() - NamingContractValidator: add descriptions to all bare @param tags in interface and StrictNamingContractValidator methods - NamingContractTest: add /** @return void */ docblocks to all test methods, merge unnecessary string concat on template variable * fix(phan/phpstan): remove readonly, add baselines for test file - NamingContract: replace readonly properties with plain public string (readonly requires PHP 8.1, phan minimum target is PHP 7.2) - dev/tools/phan/baseline.txt: add NamingContractTest.php suppression for PhanUndeclaredMethod (PHPUnit stubs not available in phan scope) - NamingContractTest.php: add @phpstan-ignore class.notFound on class declaration and method.notFound on each assertion call (PHPUnit not in phpstan bootstrap, consistent with CommonClassTest.class.php) * fix(phan/phpstan): suppress typed property warnings, NoopNew, class.notFound - baseline.txt: add PhanCompatibleTypedProperty for NamingContract.class.php (typed properties require PHP 7.4, phan min target is 7.2 — warning only but phan exits 1 on any issue including warnings) - baseline.txt: add PhanNoopNew for NamingContractTest.php (constructor called for exception side-effect in guard tests is intentional) - NamingContractTest.php: move @phpstan-ignore class.notFound to inline comment on class declaration (docblock placement not picked up by phpstan) * fix(phan/phpstan): add missing phan suppress on index.php l.574, fix phpstan class docblock - index.php l.574: add @phan-suppress-next-line PhanPluginSuspiciousParamPosition (same pattern as l.577 and l.587 for identical dolReplaceInFile calls where local variable name $destfile matches parameter #3 of the function) - NamingContractTest.php: use standalone /** @phpstan-ignore class.notFound */ docblock before class declaration (inline // comment not picked up by phpstan on class declarations; separate docblock matches CommonClassTest.class.php pattern) * Update baseline.txt * fix(phan): add phan-file-suppress on NamingContractTest for PHPUnit undeclared methods * fix(phan): suppress PhanNoopNew on expectException patterns in NamingContractTest --------- Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-05-25 12:52:38 +00:00
*/
public function validateTriggerFilename(string $filename, NamingContract $nc): bool;
/**
* Validate that a URL path contains objectNameLower and no residual 'myobject'.
*
* @param string $url URL path to validate
* @param NamingContract $nc Naming contract to compare against
* @return bool True if url path is cleaned from myobject and has objectNameLower
NEW : Replace MyObject MyModule occurences (#38370) * [COREMB] Centralize myobject/mymodule substitutions via NamingContract value object Introduce NamingContract (immutable value object) and NamingContractValidator to replace 6 scattered ad-hoc $arrayreplacement blocks across index.php and modulebuilder.lib.php. Fixes a critical bug where MYOBJECT token was commented out in initobject, leaving generated class files with unsubstituted placeholders. - NamingContract: canonical ordered substitution map (12 tokens with object, 7 module-only), applyTo() via str_replace (not make_substitutions, to avoid processing Dolibarr __(key)__ patterns in raw templates), applyToFilename() for lowercase-only filename substitution - StrictNamingContractValidator: post-generation scan for residual myobject/ mymodule tokens, skipping /* BEGIN/END MODULEBUILDER */ structural markers - modulebuilderValidateGeneratedFile() helper: non-blocking warnings with XSS-safe output via dol_escape_htmltag() - Collision guard: InvalidArgumentException if module === object name (case-insensitive), wrapped in try-catch at all instantiation sites - 24 PHPUnit tests, 52 assertions * fix(initobject): apply object substitution to all module PHP files After initmodule creates the module structure, files like index.php, lib/module.lib.php, admin/setup.php and class/api_module.class.php still contain myobject/mymodule placeholders. initobject now scans ALL PHP files in the module directory and applies the full NamingContract substitution, and deletes the orphan stats/myobject_index.php placeholder. * fix(initmodule): prevent array_merge from renumbering the '500000' key PHP casts numeric string keys like '500000' to int(500000) internally. array_merge() then renumbers integer keys starting from 0, turning '500000' into key 0. make_substitutions() then calls str_replace(0, ...) which casts 0 to the string '0', replacing EVERY zero digit in every generated file with the idmodule value. Fix: replace the bare '500000' key with '$this->numero = 500000' so that array_merge sees a non-numeric string key and preserves it as-is. * Fix initapi: apply header substitution after addObjectsToApiFile addObjectsToApiFile substitutes properties/constructor/includes via regex but leaves PHPDoc header and class declaration with mymodule/myobject tokens. Fix: apply $arrayreplacement (minus 'MYOBJECT' uppercase key) after addObjectsToApiFile to resolve the remaining residuals. 'MYOBJECT' is excluded to preserve the /* BEGIN MODULEBUILDER API MYOBJECT */ placeholder that addObjectsToApiFile relies on for future object additions. * Move NamingContractTest to core test directory NamingContract and NamingContractValidator are core modulebuilder classes; their tests belong alongside other Dolibarr core tests in test/phpunit/, not in htdocs/modulebuilder/test/phpunit/ (the custom-module pattern). Updated require_once paths accordingly. * fix useless file * fix(phpcs): add missing @param descriptions and function docblocks - NamingContract: merge adjacent string literals in exception message, add description to @param $content in applyTo() - NamingContractValidator: add descriptions to all bare @param tags in interface and StrictNamingContractValidator methods - NamingContractTest: add /** @return void */ docblocks to all test methods, merge unnecessary string concat on template variable * fix(phan/phpstan): remove readonly, add baselines for test file - NamingContract: replace readonly properties with plain public string (readonly requires PHP 8.1, phan minimum target is PHP 7.2) - dev/tools/phan/baseline.txt: add NamingContractTest.php suppression for PhanUndeclaredMethod (PHPUnit stubs not available in phan scope) - NamingContractTest.php: add @phpstan-ignore class.notFound on class declaration and method.notFound on each assertion call (PHPUnit not in phpstan bootstrap, consistent with CommonClassTest.class.php) * fix(phan/phpstan): suppress typed property warnings, NoopNew, class.notFound - baseline.txt: add PhanCompatibleTypedProperty for NamingContract.class.php (typed properties require PHP 7.4, phan min target is 7.2 — warning only but phan exits 1 on any issue including warnings) - baseline.txt: add PhanNoopNew for NamingContractTest.php (constructor called for exception side-effect in guard tests is intentional) - NamingContractTest.php: move @phpstan-ignore class.notFound to inline comment on class declaration (docblock placement not picked up by phpstan) * fix(phan/phpstan): add missing phan suppress on index.php l.574, fix phpstan class docblock - index.php l.574: add @phan-suppress-next-line PhanPluginSuspiciousParamPosition (same pattern as l.577 and l.587 for identical dolReplaceInFile calls where local variable name $destfile matches parameter #3 of the function) - NamingContractTest.php: use standalone /** @phpstan-ignore class.notFound */ docblock before class declaration (inline // comment not picked up by phpstan on class declarations; separate docblock matches CommonClassTest.class.php pattern) * Update baseline.txt * fix(phan): add phan-file-suppress on NamingContractTest for PHPUnit undeclared methods * fix(phan): suppress PhanNoopNew on expectException patterns in NamingContractTest --------- Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-05-25 12:52:38 +00:00
*/
public function validateUrl(string $url, NamingContract $nc): bool;
/**
* Validate a rights key matches format "moduleNameLower.objectNameLower.perms".
*
* @param string $rightsKey Rights key to validate
* @param NamingContract $nc Naming contract to compare against
* @return bool True if the rightsKey matches expected format
NEW : Replace MyObject MyModule occurences (#38370) * [COREMB] Centralize myobject/mymodule substitutions via NamingContract value object Introduce NamingContract (immutable value object) and NamingContractValidator to replace 6 scattered ad-hoc $arrayreplacement blocks across index.php and modulebuilder.lib.php. Fixes a critical bug where MYOBJECT token was commented out in initobject, leaving generated class files with unsubstituted placeholders. - NamingContract: canonical ordered substitution map (12 tokens with object, 7 module-only), applyTo() via str_replace (not make_substitutions, to avoid processing Dolibarr __(key)__ patterns in raw templates), applyToFilename() for lowercase-only filename substitution - StrictNamingContractValidator: post-generation scan for residual myobject/ mymodule tokens, skipping /* BEGIN/END MODULEBUILDER */ structural markers - modulebuilderValidateGeneratedFile() helper: non-blocking warnings with XSS-safe output via dol_escape_htmltag() - Collision guard: InvalidArgumentException if module === object name (case-insensitive), wrapped in try-catch at all instantiation sites - 24 PHPUnit tests, 52 assertions * fix(initobject): apply object substitution to all module PHP files After initmodule creates the module structure, files like index.php, lib/module.lib.php, admin/setup.php and class/api_module.class.php still contain myobject/mymodule placeholders. initobject now scans ALL PHP files in the module directory and applies the full NamingContract substitution, and deletes the orphan stats/myobject_index.php placeholder. * fix(initmodule): prevent array_merge from renumbering the '500000' key PHP casts numeric string keys like '500000' to int(500000) internally. array_merge() then renumbers integer keys starting from 0, turning '500000' into key 0. make_substitutions() then calls str_replace(0, ...) which casts 0 to the string '0', replacing EVERY zero digit in every generated file with the idmodule value. Fix: replace the bare '500000' key with '$this->numero = 500000' so that array_merge sees a non-numeric string key and preserves it as-is. * Fix initapi: apply header substitution after addObjectsToApiFile addObjectsToApiFile substitutes properties/constructor/includes via regex but leaves PHPDoc header and class declaration with mymodule/myobject tokens. Fix: apply $arrayreplacement (minus 'MYOBJECT' uppercase key) after addObjectsToApiFile to resolve the remaining residuals. 'MYOBJECT' is excluded to preserve the /* BEGIN MODULEBUILDER API MYOBJECT */ placeholder that addObjectsToApiFile relies on for future object additions. * Move NamingContractTest to core test directory NamingContract and NamingContractValidator are core modulebuilder classes; their tests belong alongside other Dolibarr core tests in test/phpunit/, not in htdocs/modulebuilder/test/phpunit/ (the custom-module pattern). Updated require_once paths accordingly. * fix useless file * fix(phpcs): add missing @param descriptions and function docblocks - NamingContract: merge adjacent string literals in exception message, add description to @param $content in applyTo() - NamingContractValidator: add descriptions to all bare @param tags in interface and StrictNamingContractValidator methods - NamingContractTest: add /** @return void */ docblocks to all test methods, merge unnecessary string concat on template variable * fix(phan/phpstan): remove readonly, add baselines for test file - NamingContract: replace readonly properties with plain public string (readonly requires PHP 8.1, phan minimum target is PHP 7.2) - dev/tools/phan/baseline.txt: add NamingContractTest.php suppression for PhanUndeclaredMethod (PHPUnit stubs not available in phan scope) - NamingContractTest.php: add @phpstan-ignore class.notFound on class declaration and method.notFound on each assertion call (PHPUnit not in phpstan bootstrap, consistent with CommonClassTest.class.php) * fix(phan/phpstan): suppress typed property warnings, NoopNew, class.notFound - baseline.txt: add PhanCompatibleTypedProperty for NamingContract.class.php (typed properties require PHP 7.4, phan min target is 7.2 — warning only but phan exits 1 on any issue including warnings) - baseline.txt: add PhanNoopNew for NamingContractTest.php (constructor called for exception side-effect in guard tests is intentional) - NamingContractTest.php: move @phpstan-ignore class.notFound to inline comment on class declaration (docblock placement not picked up by phpstan) * fix(phan/phpstan): add missing phan suppress on index.php l.574, fix phpstan class docblock - index.php l.574: add @phan-suppress-next-line PhanPluginSuspiciousParamPosition (same pattern as l.577 and l.587 for identical dolReplaceInFile calls where local variable name $destfile matches parameter #3 of the function) - NamingContractTest.php: use standalone /** @phpstan-ignore class.notFound */ docblock before class declaration (inline // comment not picked up by phpstan on class declarations; separate docblock matches CommonClassTest.class.php pattern) * Update baseline.txt * fix(phan): add phan-file-suppress on NamingContractTest for PHPUnit undeclared methods * fix(phan): suppress PhanNoopNew on expectException patterns in NamingContractTest --------- Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-05-25 12:52:38 +00:00
*/
public function validateRightsKey(string $rightsKey, NamingContract $nc): bool;
}
/**
* Strict implementation reports any residual myobject/mymodule token as a warning.
*
* Lines containing MODULEBUILDER structural markers (/* BEGIN MODULEBUILDER ... */) are
* excluded from validation because those markers are intentional template anchors that must
* remain as-is (e.g. /* BEGIN MODULEBUILDER API MYOBJECT */ is used by addObjectsToApiFile).
*/
final class StrictNamingContractValidator implements NamingContractValidator
{
/**
* Substrings that identify a MODULEBUILDER structural marker line.
* Lines containing any of these are excluded from residual-token validation.
*
* @var string[]
*/
private const NON_RENAMABLE_MARKERS = [
'/* BEGIN MODULEBUILDER ',
'/* END MODULEBUILDER ',
];
/**
* @param string $content Full file content to validate
* @param string $filePath File path used in error messages
* @return string[]
*/
public function validateContent(string $content, string $filePath): array
{
$errors = [];
$lines = explode("\n", $content);
foreach ($lines as $lineIndex => $line) {
if ($this->lineContainsNonRenamableMarker($line)) {
continue;
}
if (preg_match('/\bmyobject\b/i', $line)) {
$errors[] = $filePath . ':' . ($lineIndex + 1) . " — residual 'myobject' token detected";
}
if (preg_match('/\bmymodule\b/i', $line)) {
$errors[] = $filePath . ':' . ($lineIndex + 1) . " — residual 'mymodule' token detected";
}
}
return $errors;
}
/**
* @param string $line Line content to check
* @return bool True if there is a NON_RENAMABLE_MARKER in the line
NEW : Replace MyObject MyModule occurences (#38370) * [COREMB] Centralize myobject/mymodule substitutions via NamingContract value object Introduce NamingContract (immutable value object) and NamingContractValidator to replace 6 scattered ad-hoc $arrayreplacement blocks across index.php and modulebuilder.lib.php. Fixes a critical bug where MYOBJECT token was commented out in initobject, leaving generated class files with unsubstituted placeholders. - NamingContract: canonical ordered substitution map (12 tokens with object, 7 module-only), applyTo() via str_replace (not make_substitutions, to avoid processing Dolibarr __(key)__ patterns in raw templates), applyToFilename() for lowercase-only filename substitution - StrictNamingContractValidator: post-generation scan for residual myobject/ mymodule tokens, skipping /* BEGIN/END MODULEBUILDER */ structural markers - modulebuilderValidateGeneratedFile() helper: non-blocking warnings with XSS-safe output via dol_escape_htmltag() - Collision guard: InvalidArgumentException if module === object name (case-insensitive), wrapped in try-catch at all instantiation sites - 24 PHPUnit tests, 52 assertions * fix(initobject): apply object substitution to all module PHP files After initmodule creates the module structure, files like index.php, lib/module.lib.php, admin/setup.php and class/api_module.class.php still contain myobject/mymodule placeholders. initobject now scans ALL PHP files in the module directory and applies the full NamingContract substitution, and deletes the orphan stats/myobject_index.php placeholder. * fix(initmodule): prevent array_merge from renumbering the '500000' key PHP casts numeric string keys like '500000' to int(500000) internally. array_merge() then renumbers integer keys starting from 0, turning '500000' into key 0. make_substitutions() then calls str_replace(0, ...) which casts 0 to the string '0', replacing EVERY zero digit in every generated file with the idmodule value. Fix: replace the bare '500000' key with '$this->numero = 500000' so that array_merge sees a non-numeric string key and preserves it as-is. * Fix initapi: apply header substitution after addObjectsToApiFile addObjectsToApiFile substitutes properties/constructor/includes via regex but leaves PHPDoc header and class declaration with mymodule/myobject tokens. Fix: apply $arrayreplacement (minus 'MYOBJECT' uppercase key) after addObjectsToApiFile to resolve the remaining residuals. 'MYOBJECT' is excluded to preserve the /* BEGIN MODULEBUILDER API MYOBJECT */ placeholder that addObjectsToApiFile relies on for future object additions. * Move NamingContractTest to core test directory NamingContract and NamingContractValidator are core modulebuilder classes; their tests belong alongside other Dolibarr core tests in test/phpunit/, not in htdocs/modulebuilder/test/phpunit/ (the custom-module pattern). Updated require_once paths accordingly. * fix useless file * fix(phpcs): add missing @param descriptions and function docblocks - NamingContract: merge adjacent string literals in exception message, add description to @param $content in applyTo() - NamingContractValidator: add descriptions to all bare @param tags in interface and StrictNamingContractValidator methods - NamingContractTest: add /** @return void */ docblocks to all test methods, merge unnecessary string concat on template variable * fix(phan/phpstan): remove readonly, add baselines for test file - NamingContract: replace readonly properties with plain public string (readonly requires PHP 8.1, phan minimum target is PHP 7.2) - dev/tools/phan/baseline.txt: add NamingContractTest.php suppression for PhanUndeclaredMethod (PHPUnit stubs not available in phan scope) - NamingContractTest.php: add @phpstan-ignore class.notFound on class declaration and method.notFound on each assertion call (PHPUnit not in phpstan bootstrap, consistent with CommonClassTest.class.php) * fix(phan/phpstan): suppress typed property warnings, NoopNew, class.notFound - baseline.txt: add PhanCompatibleTypedProperty for NamingContract.class.php (typed properties require PHP 7.4, phan min target is 7.2 — warning only but phan exits 1 on any issue including warnings) - baseline.txt: add PhanNoopNew for NamingContractTest.php (constructor called for exception side-effect in guard tests is intentional) - NamingContractTest.php: move @phpstan-ignore class.notFound to inline comment on class declaration (docblock placement not picked up by phpstan) * fix(phan/phpstan): add missing phan suppress on index.php l.574, fix phpstan class docblock - index.php l.574: add @phan-suppress-next-line PhanPluginSuspiciousParamPosition (same pattern as l.577 and l.587 for identical dolReplaceInFile calls where local variable name $destfile matches parameter #3 of the function) - NamingContractTest.php: use standalone /** @phpstan-ignore class.notFound */ docblock before class declaration (inline // comment not picked up by phpstan on class declarations; separate docblock matches CommonClassTest.class.php pattern) * Update baseline.txt * fix(phan): add phan-file-suppress on NamingContractTest for PHPUnit undeclared methods * fix(phan): suppress PhanNoopNew on expectException patterns in NamingContractTest --------- Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-05-25 12:52:38 +00:00
*/
private function lineContainsNonRenamableMarker(string $line): bool
{
foreach (self::NON_RENAMABLE_MARKERS as $marker) {
if (strpos($line, $marker) !== false) {
return true;
}
}
return false;
}
/**
* @param string $className Class name to validate
* @param NamingContract $nc Naming contract to compare against
* @return bool True if class name is valid
NEW : Replace MyObject MyModule occurences (#38370) * [COREMB] Centralize myobject/mymodule substitutions via NamingContract value object Introduce NamingContract (immutable value object) and NamingContractValidator to replace 6 scattered ad-hoc $arrayreplacement blocks across index.php and modulebuilder.lib.php. Fixes a critical bug where MYOBJECT token was commented out in initobject, leaving generated class files with unsubstituted placeholders. - NamingContract: canonical ordered substitution map (12 tokens with object, 7 module-only), applyTo() via str_replace (not make_substitutions, to avoid processing Dolibarr __(key)__ patterns in raw templates), applyToFilename() for lowercase-only filename substitution - StrictNamingContractValidator: post-generation scan for residual myobject/ mymodule tokens, skipping /* BEGIN/END MODULEBUILDER */ structural markers - modulebuilderValidateGeneratedFile() helper: non-blocking warnings with XSS-safe output via dol_escape_htmltag() - Collision guard: InvalidArgumentException if module === object name (case-insensitive), wrapped in try-catch at all instantiation sites - 24 PHPUnit tests, 52 assertions * fix(initobject): apply object substitution to all module PHP files After initmodule creates the module structure, files like index.php, lib/module.lib.php, admin/setup.php and class/api_module.class.php still contain myobject/mymodule placeholders. initobject now scans ALL PHP files in the module directory and applies the full NamingContract substitution, and deletes the orphan stats/myobject_index.php placeholder. * fix(initmodule): prevent array_merge from renumbering the '500000' key PHP casts numeric string keys like '500000' to int(500000) internally. array_merge() then renumbers integer keys starting from 0, turning '500000' into key 0. make_substitutions() then calls str_replace(0, ...) which casts 0 to the string '0', replacing EVERY zero digit in every generated file with the idmodule value. Fix: replace the bare '500000' key with '$this->numero = 500000' so that array_merge sees a non-numeric string key and preserves it as-is. * Fix initapi: apply header substitution after addObjectsToApiFile addObjectsToApiFile substitutes properties/constructor/includes via regex but leaves PHPDoc header and class declaration with mymodule/myobject tokens. Fix: apply $arrayreplacement (minus 'MYOBJECT' uppercase key) after addObjectsToApiFile to resolve the remaining residuals. 'MYOBJECT' is excluded to preserve the /* BEGIN MODULEBUILDER API MYOBJECT */ placeholder that addObjectsToApiFile relies on for future object additions. * Move NamingContractTest to core test directory NamingContract and NamingContractValidator are core modulebuilder classes; their tests belong alongside other Dolibarr core tests in test/phpunit/, not in htdocs/modulebuilder/test/phpunit/ (the custom-module pattern). Updated require_once paths accordingly. * fix useless file * fix(phpcs): add missing @param descriptions and function docblocks - NamingContract: merge adjacent string literals in exception message, add description to @param $content in applyTo() - NamingContractValidator: add descriptions to all bare @param tags in interface and StrictNamingContractValidator methods - NamingContractTest: add /** @return void */ docblocks to all test methods, merge unnecessary string concat on template variable * fix(phan/phpstan): remove readonly, add baselines for test file - NamingContract: replace readonly properties with plain public string (readonly requires PHP 8.1, phan minimum target is PHP 7.2) - dev/tools/phan/baseline.txt: add NamingContractTest.php suppression for PhanUndeclaredMethod (PHPUnit stubs not available in phan scope) - NamingContractTest.php: add @phpstan-ignore class.notFound on class declaration and method.notFound on each assertion call (PHPUnit not in phpstan bootstrap, consistent with CommonClassTest.class.php) * fix(phan/phpstan): suppress typed property warnings, NoopNew, class.notFound - baseline.txt: add PhanCompatibleTypedProperty for NamingContract.class.php (typed properties require PHP 7.4, phan min target is 7.2 — warning only but phan exits 1 on any issue including warnings) - baseline.txt: add PhanNoopNew for NamingContractTest.php (constructor called for exception side-effect in guard tests is intentional) - NamingContractTest.php: move @phpstan-ignore class.notFound to inline comment on class declaration (docblock placement not picked up by phpstan) * fix(phan/phpstan): add missing phan suppress on index.php l.574, fix phpstan class docblock - index.php l.574: add @phan-suppress-next-line PhanPluginSuspiciousParamPosition (same pattern as l.577 and l.587 for identical dolReplaceInFile calls where local variable name $destfile matches parameter #3 of the function) - NamingContractTest.php: use standalone /** @phpstan-ignore class.notFound */ docblock before class declaration (inline // comment not picked up by phpstan on class declarations; separate docblock matches CommonClassTest.class.php pattern) * Update baseline.txt * fix(phan): add phan-file-suppress on NamingContractTest for PHPUnit undeclared methods * fix(phan): suppress PhanNoopNew on expectException patterns in NamingContractTest --------- Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-05-25 12:52:38 +00:00
*/
public function validateClassName(string $className, NamingContract $nc): bool
{
return $className === $nc->objectNameCase;
}
/**
* @param string $filename Trigger filename to validate
* @param NamingContract $nc Naming contract to compare against
* @return bool True if filename for Trigger is valid
NEW : Replace MyObject MyModule occurences (#38370) * [COREMB] Centralize myobject/mymodule substitutions via NamingContract value object Introduce NamingContract (immutable value object) and NamingContractValidator to replace 6 scattered ad-hoc $arrayreplacement blocks across index.php and modulebuilder.lib.php. Fixes a critical bug where MYOBJECT token was commented out in initobject, leaving generated class files with unsubstituted placeholders. - NamingContract: canonical ordered substitution map (12 tokens with object, 7 module-only), applyTo() via str_replace (not make_substitutions, to avoid processing Dolibarr __(key)__ patterns in raw templates), applyToFilename() for lowercase-only filename substitution - StrictNamingContractValidator: post-generation scan for residual myobject/ mymodule tokens, skipping /* BEGIN/END MODULEBUILDER */ structural markers - modulebuilderValidateGeneratedFile() helper: non-blocking warnings with XSS-safe output via dol_escape_htmltag() - Collision guard: InvalidArgumentException if module === object name (case-insensitive), wrapped in try-catch at all instantiation sites - 24 PHPUnit tests, 52 assertions * fix(initobject): apply object substitution to all module PHP files After initmodule creates the module structure, files like index.php, lib/module.lib.php, admin/setup.php and class/api_module.class.php still contain myobject/mymodule placeholders. initobject now scans ALL PHP files in the module directory and applies the full NamingContract substitution, and deletes the orphan stats/myobject_index.php placeholder. * fix(initmodule): prevent array_merge from renumbering the '500000' key PHP casts numeric string keys like '500000' to int(500000) internally. array_merge() then renumbers integer keys starting from 0, turning '500000' into key 0. make_substitutions() then calls str_replace(0, ...) which casts 0 to the string '0', replacing EVERY zero digit in every generated file with the idmodule value. Fix: replace the bare '500000' key with '$this->numero = 500000' so that array_merge sees a non-numeric string key and preserves it as-is. * Fix initapi: apply header substitution after addObjectsToApiFile addObjectsToApiFile substitutes properties/constructor/includes via regex but leaves PHPDoc header and class declaration with mymodule/myobject tokens. Fix: apply $arrayreplacement (minus 'MYOBJECT' uppercase key) after addObjectsToApiFile to resolve the remaining residuals. 'MYOBJECT' is excluded to preserve the /* BEGIN MODULEBUILDER API MYOBJECT */ placeholder that addObjectsToApiFile relies on for future object additions. * Move NamingContractTest to core test directory NamingContract and NamingContractValidator are core modulebuilder classes; their tests belong alongside other Dolibarr core tests in test/phpunit/, not in htdocs/modulebuilder/test/phpunit/ (the custom-module pattern). Updated require_once paths accordingly. * fix useless file * fix(phpcs): add missing @param descriptions and function docblocks - NamingContract: merge adjacent string literals in exception message, add description to @param $content in applyTo() - NamingContractValidator: add descriptions to all bare @param tags in interface and StrictNamingContractValidator methods - NamingContractTest: add /** @return void */ docblocks to all test methods, merge unnecessary string concat on template variable * fix(phan/phpstan): remove readonly, add baselines for test file - NamingContract: replace readonly properties with plain public string (readonly requires PHP 8.1, phan minimum target is PHP 7.2) - dev/tools/phan/baseline.txt: add NamingContractTest.php suppression for PhanUndeclaredMethod (PHPUnit stubs not available in phan scope) - NamingContractTest.php: add @phpstan-ignore class.notFound on class declaration and method.notFound on each assertion call (PHPUnit not in phpstan bootstrap, consistent with CommonClassTest.class.php) * fix(phan/phpstan): suppress typed property warnings, NoopNew, class.notFound - baseline.txt: add PhanCompatibleTypedProperty for NamingContract.class.php (typed properties require PHP 7.4, phan min target is 7.2 — warning only but phan exits 1 on any issue including warnings) - baseline.txt: add PhanNoopNew for NamingContractTest.php (constructor called for exception side-effect in guard tests is intentional) - NamingContractTest.php: move @phpstan-ignore class.notFound to inline comment on class declaration (docblock placement not picked up by phpstan) * fix(phan/phpstan): add missing phan suppress on index.php l.574, fix phpstan class docblock - index.php l.574: add @phan-suppress-next-line PhanPluginSuspiciousParamPosition (same pattern as l.577 and l.587 for identical dolReplaceInFile calls where local variable name $destfile matches parameter #3 of the function) - NamingContractTest.php: use standalone /** @phpstan-ignore class.notFound */ docblock before class declaration (inline // comment not picked up by phpstan on class declarations; separate docblock matches CommonClassTest.class.php pattern) * Update baseline.txt * fix(phan): add phan-file-suppress on NamingContractTest for PHPUnit undeclared methods * fix(phan): suppress PhanNoopNew on expectException patterns in NamingContractTest --------- Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-05-25 12:52:38 +00:00
*/
public function validateTriggerFilename(string $filename, NamingContract $nc): bool
{
$pattern = '/^interface_\d{2}_mod'
. preg_quote($nc->moduleNameCase, '/')
. '_'
. preg_quote($nc->moduleNameCase, '/')
. 'Triggers\.class\.php$/';
return (bool) preg_match($pattern, $filename);
}
/**
* @param string $url URL path to validate
* @param NamingContract $nc Naming contract to compare against
* @return bool True if urlpath for module item is valid
NEW : Replace MyObject MyModule occurences (#38370) * [COREMB] Centralize myobject/mymodule substitutions via NamingContract value object Introduce NamingContract (immutable value object) and NamingContractValidator to replace 6 scattered ad-hoc $arrayreplacement blocks across index.php and modulebuilder.lib.php. Fixes a critical bug where MYOBJECT token was commented out in initobject, leaving generated class files with unsubstituted placeholders. - NamingContract: canonical ordered substitution map (12 tokens with object, 7 module-only), applyTo() via str_replace (not make_substitutions, to avoid processing Dolibarr __(key)__ patterns in raw templates), applyToFilename() for lowercase-only filename substitution - StrictNamingContractValidator: post-generation scan for residual myobject/ mymodule tokens, skipping /* BEGIN/END MODULEBUILDER */ structural markers - modulebuilderValidateGeneratedFile() helper: non-blocking warnings with XSS-safe output via dol_escape_htmltag() - Collision guard: InvalidArgumentException if module === object name (case-insensitive), wrapped in try-catch at all instantiation sites - 24 PHPUnit tests, 52 assertions * fix(initobject): apply object substitution to all module PHP files After initmodule creates the module structure, files like index.php, lib/module.lib.php, admin/setup.php and class/api_module.class.php still contain myobject/mymodule placeholders. initobject now scans ALL PHP files in the module directory and applies the full NamingContract substitution, and deletes the orphan stats/myobject_index.php placeholder. * fix(initmodule): prevent array_merge from renumbering the '500000' key PHP casts numeric string keys like '500000' to int(500000) internally. array_merge() then renumbers integer keys starting from 0, turning '500000' into key 0. make_substitutions() then calls str_replace(0, ...) which casts 0 to the string '0', replacing EVERY zero digit in every generated file with the idmodule value. Fix: replace the bare '500000' key with '$this->numero = 500000' so that array_merge sees a non-numeric string key and preserves it as-is. * Fix initapi: apply header substitution after addObjectsToApiFile addObjectsToApiFile substitutes properties/constructor/includes via regex but leaves PHPDoc header and class declaration with mymodule/myobject tokens. Fix: apply $arrayreplacement (minus 'MYOBJECT' uppercase key) after addObjectsToApiFile to resolve the remaining residuals. 'MYOBJECT' is excluded to preserve the /* BEGIN MODULEBUILDER API MYOBJECT */ placeholder that addObjectsToApiFile relies on for future object additions. * Move NamingContractTest to core test directory NamingContract and NamingContractValidator are core modulebuilder classes; their tests belong alongside other Dolibarr core tests in test/phpunit/, not in htdocs/modulebuilder/test/phpunit/ (the custom-module pattern). Updated require_once paths accordingly. * fix useless file * fix(phpcs): add missing @param descriptions and function docblocks - NamingContract: merge adjacent string literals in exception message, add description to @param $content in applyTo() - NamingContractValidator: add descriptions to all bare @param tags in interface and StrictNamingContractValidator methods - NamingContractTest: add /** @return void */ docblocks to all test methods, merge unnecessary string concat on template variable * fix(phan/phpstan): remove readonly, add baselines for test file - NamingContract: replace readonly properties with plain public string (readonly requires PHP 8.1, phan minimum target is PHP 7.2) - dev/tools/phan/baseline.txt: add NamingContractTest.php suppression for PhanUndeclaredMethod (PHPUnit stubs not available in phan scope) - NamingContractTest.php: add @phpstan-ignore class.notFound on class declaration and method.notFound on each assertion call (PHPUnit not in phpstan bootstrap, consistent with CommonClassTest.class.php) * fix(phan/phpstan): suppress typed property warnings, NoopNew, class.notFound - baseline.txt: add PhanCompatibleTypedProperty for NamingContract.class.php (typed properties require PHP 7.4, phan min target is 7.2 — warning only but phan exits 1 on any issue including warnings) - baseline.txt: add PhanNoopNew for NamingContractTest.php (constructor called for exception side-effect in guard tests is intentional) - NamingContractTest.php: move @phpstan-ignore class.notFound to inline comment on class declaration (docblock placement not picked up by phpstan) * fix(phan/phpstan): add missing phan suppress on index.php l.574, fix phpstan class docblock - index.php l.574: add @phan-suppress-next-line PhanPluginSuspiciousParamPosition (same pattern as l.577 and l.587 for identical dolReplaceInFile calls where local variable name $destfile matches parameter #3 of the function) - NamingContractTest.php: use standalone /** @phpstan-ignore class.notFound */ docblock before class declaration (inline // comment not picked up by phpstan on class declarations; separate docblock matches CommonClassTest.class.php pattern) * Update baseline.txt * fix(phan): add phan-file-suppress on NamingContractTest for PHPUnit undeclared methods * fix(phan): suppress PhanNoopNew on expectException patterns in NamingContractTest --------- Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-05-25 12:52:38 +00:00
*/
public function validateUrl(string $url, NamingContract $nc): bool
{
return $nc->objectNameLower !== ''
&& strpos($url, $nc->objectNameLower) !== false
&& strpos($url, 'myobject') === false;
}
/**
* @param string $rightsKey Rights key to validate
* @param NamingContract $nc Naming contract to compare against
* @return bool True if $rightskey is valid for this module
NEW : Replace MyObject MyModule occurences (#38370) * [COREMB] Centralize myobject/mymodule substitutions via NamingContract value object Introduce NamingContract (immutable value object) and NamingContractValidator to replace 6 scattered ad-hoc $arrayreplacement blocks across index.php and modulebuilder.lib.php. Fixes a critical bug where MYOBJECT token was commented out in initobject, leaving generated class files with unsubstituted placeholders. - NamingContract: canonical ordered substitution map (12 tokens with object, 7 module-only), applyTo() via str_replace (not make_substitutions, to avoid processing Dolibarr __(key)__ patterns in raw templates), applyToFilename() for lowercase-only filename substitution - StrictNamingContractValidator: post-generation scan for residual myobject/ mymodule tokens, skipping /* BEGIN/END MODULEBUILDER */ structural markers - modulebuilderValidateGeneratedFile() helper: non-blocking warnings with XSS-safe output via dol_escape_htmltag() - Collision guard: InvalidArgumentException if module === object name (case-insensitive), wrapped in try-catch at all instantiation sites - 24 PHPUnit tests, 52 assertions * fix(initobject): apply object substitution to all module PHP files After initmodule creates the module structure, files like index.php, lib/module.lib.php, admin/setup.php and class/api_module.class.php still contain myobject/mymodule placeholders. initobject now scans ALL PHP files in the module directory and applies the full NamingContract substitution, and deletes the orphan stats/myobject_index.php placeholder. * fix(initmodule): prevent array_merge from renumbering the '500000' key PHP casts numeric string keys like '500000' to int(500000) internally. array_merge() then renumbers integer keys starting from 0, turning '500000' into key 0. make_substitutions() then calls str_replace(0, ...) which casts 0 to the string '0', replacing EVERY zero digit in every generated file with the idmodule value. Fix: replace the bare '500000' key with '$this->numero = 500000' so that array_merge sees a non-numeric string key and preserves it as-is. * Fix initapi: apply header substitution after addObjectsToApiFile addObjectsToApiFile substitutes properties/constructor/includes via regex but leaves PHPDoc header and class declaration with mymodule/myobject tokens. Fix: apply $arrayreplacement (minus 'MYOBJECT' uppercase key) after addObjectsToApiFile to resolve the remaining residuals. 'MYOBJECT' is excluded to preserve the /* BEGIN MODULEBUILDER API MYOBJECT */ placeholder that addObjectsToApiFile relies on for future object additions. * Move NamingContractTest to core test directory NamingContract and NamingContractValidator are core modulebuilder classes; their tests belong alongside other Dolibarr core tests in test/phpunit/, not in htdocs/modulebuilder/test/phpunit/ (the custom-module pattern). Updated require_once paths accordingly. * fix useless file * fix(phpcs): add missing @param descriptions and function docblocks - NamingContract: merge adjacent string literals in exception message, add description to @param $content in applyTo() - NamingContractValidator: add descriptions to all bare @param tags in interface and StrictNamingContractValidator methods - NamingContractTest: add /** @return void */ docblocks to all test methods, merge unnecessary string concat on template variable * fix(phan/phpstan): remove readonly, add baselines for test file - NamingContract: replace readonly properties with plain public string (readonly requires PHP 8.1, phan minimum target is PHP 7.2) - dev/tools/phan/baseline.txt: add NamingContractTest.php suppression for PhanUndeclaredMethod (PHPUnit stubs not available in phan scope) - NamingContractTest.php: add @phpstan-ignore class.notFound on class declaration and method.notFound on each assertion call (PHPUnit not in phpstan bootstrap, consistent with CommonClassTest.class.php) * fix(phan/phpstan): suppress typed property warnings, NoopNew, class.notFound - baseline.txt: add PhanCompatibleTypedProperty for NamingContract.class.php (typed properties require PHP 7.4, phan min target is 7.2 — warning only but phan exits 1 on any issue including warnings) - baseline.txt: add PhanNoopNew for NamingContractTest.php (constructor called for exception side-effect in guard tests is intentional) - NamingContractTest.php: move @phpstan-ignore class.notFound to inline comment on class declaration (docblock placement not picked up by phpstan) * fix(phan/phpstan): add missing phan suppress on index.php l.574, fix phpstan class docblock - index.php l.574: add @phan-suppress-next-line PhanPluginSuspiciousParamPosition (same pattern as l.577 and l.587 for identical dolReplaceInFile calls where local variable name $destfile matches parameter #3 of the function) - NamingContractTest.php: use standalone /** @phpstan-ignore class.notFound */ docblock before class declaration (inline // comment not picked up by phpstan on class declarations; separate docblock matches CommonClassTest.class.php pattern) * Update baseline.txt * fix(phan): add phan-file-suppress on NamingContractTest for PHPUnit undeclared methods * fix(phan): suppress PhanNoopNew on expectException patterns in NamingContractTest --------- Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-05-25 12:52:38 +00:00
*/
public function validateRightsKey(string $rightsKey, NamingContract $nc): bool
{
$pattern = '/^'
. preg_quote($nc->moduleNameLower, '/')
. '\.'
. preg_quote($nc->objectNameLower, '/')
. '\.\w+$/';
return (bool) preg_match($pattern, $rightsKey);
}
}