Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 204 additions & 0 deletions bin/qualify-core-references.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
<?php
/**
* Rewrites bare references to FOG core classes into fully qualified ones.
*
* Plugins are global-namespace by design (ADR 0009) and have always named
* core classes bare -- `extends Hook`, `Route::listem()`, `new Image()`.
* Those resolve only because every file under fogproject's packages/web/src/
* ends in a class_alias() re-exporting itself globally, and that alias set is
* being retired (fogproject docs/composer-psr4-plan.md, ADR 0013 §2).
*
* This qualifies them: `extends \FOG\Base\Hook`, `\FOG\Router\Route::listem()`.
* The plugin stays in the global namespace -- only the names it reaches into
* core with change.
*
* The map is read from a fogproject checkout rather than hardcoded, because
* the bucket a class lives in is fogproject's to decide and a stale copy here
* would rewrite names to somewhere they are not.
*
* Tokenised, never regex: a class name in a docblock, a string or a comment
* must not be rewritten, and `$obj->Route` is not a class reference.
*
* Names the plugin tree itself declares are skipped even when core has one of
* the same name -- a plugin's own class always wins for its own references.
*
* Usage:
* php bin/qualify-core-references.php --core=/path/to/fogproject [--fix]
*
* Without --fix it reports and changes nothing. Exit 1 if anything is left to
* do (so it doubles as a check), 0 when the tree is clean.
*/

$opts = getopt('', ['core:', 'fix']);
$core = $opts['core'] ?? '/home/telliott/fogproject';
$fix = isset($opts['fix']);
$srcRoot = rtrim($core, '/') . '/packages/web/src';
if (!is_dir($srcRoot)) {
fwrite(STDERR, "no fogproject src/ at $srcRoot -- pass --core=/path/to/fogproject\n");
exit(2);
}

/**
* Core: lowercased short name => the name to write, WITHOUT a leading
* backslash (the rewrite adds one).
*
* Three sources, because core does not keep all its classes in one place and
* the src/ map alone silently misses two kinds of reference:
*
* - packages/web/src/ PSR-4, so the path IS the name.
* - packages/web/lib/ the 46 discovery-named classes, whose filenames
* are a contract with FOGPageManager and cannot be
* PSR-4. They sit in the flat FOG namespace, so a
* plugin report extends FOG\ReportManagement.
* - packages/web/commons/ Initiator, which is genuinely global-namespace
* and stays that way; qualifying it is just a
* leading backslash.
*
* Only the first group's aliases are being retired, but the other two are
* bare names resolving by luck of the global namespace, and the test that
* gates this cannot tell the three apart -- nor should it have to.
*/
$core = [];
$walk = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($srcRoot));
foreach ($walk as $file) {
if ($file->isFile() && 'php' === $file->getExtension()) {
$short = $file->getBasename('.php');
$core[strtolower($short)] = 'FOG\\' . basename(dirname($file->getPathname())) . '\\' . $short;
}
}
$webRoot = dirname($srcRoot);
foreach (['pages', 'hooks', 'reports', 'events'] as $dir) {
foreach (glob($webRoot . '/lib/' . $dir . '/*.php') as $path) {
$short = preg_replace('/\\.(page|hook|report|event)$/', '', basename($path, '.php'));
$src = file_get_contents($path);
if (preg_match('/^\\s*(?:final\\s+|abstract\\s+)*class\\s+(\\w+)/mi', $src, $m)) {
$core[strtolower($m[1])] = 'FOG\\' . $m[1];
}
}
}
foreach (glob($webRoot . '/commons/*.php') as $path) {
if (preg_match_all(
'/^\\s*(?:final\\s+|abstract\\s+)*class\\s+(\\w+)/mi',
file_get_contents($path),
$m
)) {
foreach ($m[1] as $name) {
// Global namespace, and staying there -- the leading backslash
// the rewrite adds is the whole change.
$core[strtolower($name)] = $name;
}
}
}

$root = dirname(__DIR__);

/** Every class this tree declares. A plugin's own name always wins. */
$own = [];
$walk = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root));
foreach ($walk as $file) {
// tests/stubs is excluded here as well as from the rewrite. It declares
// test doubles NAMED for core classes -- FOGController, Schema -- and
// counting those as the tree's own would make every reference to them
// look like a plugin's own class and skip it. That is not hypothetical:
// the first run of this tool rewrote 313 references and silently left
// ~90 behind for exactly that reason. The strict stubs caught it.
if (!$file->isFile() || 'php' !== $file->getExtension()
|| false !== strpos($file->getPathname(), '/.git/')
|| false !== strpos($file->getPathname(), '/tests/stubs/')
) {
continue;
}
if (preg_match_all(
'/^\s*(?:final\s+|abstract\s+)*(?:class|interface|trait)\s+(\w+)/mi',
file_get_contents($file->getPathname()),
$m
)) {
foreach ($m[1] as $name) {
$own[strtolower($name)] = true;
}
}
}

$skip = [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT];
$changedFiles = 0;
$changedRefs = 0;
$report = [];

$walk = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root));
foreach ($walk as $file) {
$path = $file->getPathname();
if (!$file->isFile() || 'php' !== $file->getExtension()
|| false !== strpos($path, '/.git/')
|| false !== strpos($path, '/tests/stubs/')
) {
continue;
}
$src = file_get_contents($path);
$tokens = token_get_all($src);
$count = count($tokens);
$out = '';
$hits = 0;
for ($i = 0; $i < $count; $i++) {
$token = $tokens[$i];
if (!is_array($token) || T_STRING !== $token[0]) {
$out .= is_array($token) ? $token[1] : $token;
continue;
}
$name = $token[1];
$key = strtolower($name);
if (!isset($core[$key]) || isset($own[$key])) {
$out .= $name;
continue;
}
// Neighbours, whitespace and comments skipped.
$prev = null;
for ($j = $i - 1; $j >= 0; $j--) {
if (is_array($tokens[$j]) && in_array($tokens[$j][0], $skip, true)) {
continue;
}
$prev = $tokens[$j];
break;
}
$next = null;
for ($j = $i + 1; $j < $count; $j++) {
if (is_array($tokens[$j]) && in_array($tokens[$j][0], $skip, true)) {
continue;
}
$next = $tokens[$j];
break;
}
// Already qualified, or a member/function/const name rather than a
// class reference.
$isQualified = is_array($prev)
&& in_array($prev[0], [T_NS_SEPARATOR, T_OBJECT_OPERATOR, T_DOUBLE_COLON, T_FUNCTION, T_CONST], true);
$followsSeparator = is_array($next) && T_NS_SEPARATOR === $next[0];
$isClassRef = (is_array($next) && T_DOUBLE_COLON === $next[0])
|| (is_array($prev) && in_array($prev[0], [T_NEW, T_EXTENDS, T_IMPLEMENTS, T_INSTANCEOF], true));
if ($isQualified || $followsSeparator || !$isClassRef) {
$out .= $name;
continue;
}
$out .= '\\' . $core[$key];
$hits++;
}
if (!$hits) {
continue;
}
$rel = str_replace($root . '/', '', $path);
$report[] = sprintf('%-58s %d', $rel, $hits);
$changedFiles++;
$changedRefs += $hits;
if ($fix) {
file_put_contents($path, $out);
}
}

sort($report);
echo implode("\n", $report) . "\n";
printf(
"%s: %d reference(s) in %d file(s)\n",
$fix ? 'rewrote' : 'would rewrite',
$changedRefs,
$changedFiles
);
exit($changedRefs && !$fix ? 1 : 0);
2 changes: 1 addition & 1 deletion capone/class/capone.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* @license http://opensource.org/licenses/gpl-3.0 GPLv3
* @link https://fogproject.org
*/
class Capone extends FOGController
class Capone extends \FOG\Base\FOGController
{
/**
* The capone table
Expand Down
8 changes: 4 additions & 4 deletions capone/class/caponemanager.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* @license http://opensource.org/licenses/gpl-3.0 GPLv3
* @link https://fogproject.org
*/
class CaponeManager extends FOGManagerController
class CaponeManager extends \FOG\Base\FOGManagerController
{
/**
* The base table name.
Expand Down Expand Up @@ -149,7 +149,7 @@ function () {
*/
public function install()
{
$res = Schema::applyUpdates($this->schema(), 0);
$res = \FOG\Items\Schema::applyUpdates($this->schema(), 0);
return $res['error'] === null;
}
/**
Expand All @@ -159,11 +159,11 @@ public function install()
*/
public function uninstall()
{
Route::deletemass(
\FOG\Router\Route::deletemass(
'setting',
['name' => 'FOG_PLUGIN_CAPONE_%']
);
Route::deletemass(
\FOG\Router\Route::deletemass(
'pxemenuoptions',
['name' => 'fog.capone']
);
Expand Down
2 changes: 1 addition & 1 deletion capone/hooks/addbootmenuitem.hook.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* @license http://opensource.org/licenses/gpl-3.0 GPLv3
* @link https://fogproject.org
*/
class AddBootMenuItem extends Hook
class AddBootMenuItem extends \FOG\Base\Hook
{
/**
* The name of this hook.
Expand Down
2 changes: 1 addition & 1 deletion capone/hooks/addcaponeapi.hook.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* @license http://opensource.org/licenses/gpl-3.0 GPLv3
* @link https://fogproject.org
*/
class AddCaponeAPI extends Hook
class AddCaponeAPI extends \FOG\Base\Hook
{
/**
* Name of the hook.
Expand Down
2 changes: 1 addition & 1 deletion capone/hooks/addcaponejs.hook.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* @license http://opensource.org/licenses/gpl-3.0 GPLv3
* @link https://fogproject.org
*/
class AddCaponeJS extends Hook
class AddCaponeJS extends \FOG\Base\Hook
{
/**
* The name of this hook.
Expand Down
2 changes: 1 addition & 1 deletion capone/hooks/addcaponemenuitem.hook.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* @license http://opensource.org/licenses/gpl-3.0 GPLv3
* @link https://fogproject.org
*/
class AddCaponeMenuItem extends Hook
class AddCaponeMenuItem extends \FOG\Base\Hook
{
/**
* The name of this hook.
Expand Down
14 changes: 7 additions & 7 deletions capone/pages/caponemanagement.page.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* @license http://opensource.org/licenses/gpl-3.0 GPLv3
* @link https://fogproject.org
*/
class CaponeManagement extends FOGPage
class CaponeManagement extends \FOG\Base\FOGPage
{
/**
* The node this page displays with.
Expand Down Expand Up @@ -133,7 +133,7 @@ function (&$serverFault) {
$key = trim(
filter_input(INPUT_POST, 'key')
);
$image = new Image($imageID);
$image = new \FOG\Items\Image($imageID);
$os = $image->getOS();
$osID = $os->get('id');
if (!$image->isValid()) {
Expand Down Expand Up @@ -261,7 +261,7 @@ public function caponeGeneralPost()
$key = trim(
filter_input(INPUT_POST, 'key')
);
$image = new Image($imageID);
$image = new \FOG\Items\Image($imageID);
$os = $image->getOS();
$osID = $os->get('id');

Expand Down Expand Up @@ -321,7 +321,7 @@ public function globalsettings()
'FOG_PLUGIN_CAPONE_SHUTDOWN'
]
];
$settings = Route::getIds(
$settings = \FOG\Router\Route::getIds(
'setting',
$find,
'value'
Expand Down Expand Up @@ -443,7 +443,7 @@ public function globalsettingsPost()
throw new \Exception(_('Unable to set action field'));
}
$hook = 'CAPONE_GLOBAL_EDIT_SUCCESS';
$code = HTTPResponseCodes::HTTP_ACCEPTED;
$code = \FOG\Router\HTTPResponseCodes::HTTP_ACCEPTED;
$msg = json_encode(
[
'msg' => _('Global settings updated!'),
Expand All @@ -454,8 +454,8 @@ public function globalsettingsPost()
$hook = 'CAPONE_GLOBAL_EDIT_FAIL';
$code = (
$serverFault ?
HTTPResponseCodes::HTTP_INTERNAL_SERVER_ERROR :
HTTPResponseCodes::HTTP_BAD_REQUEST
\FOG\Router\HTTPResponseCodes::HTTP_INTERNAL_SERVER_ERROR :
\FOG\Router\HTTPResponseCodes::HTTP_BAD_REQUEST
);
$msg = json_encode(
[
Expand Down
6 changes: 3 additions & 3 deletions capone/reg-task/caponetasking.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* @license http://opensource.org/licenses/gpl-3.0 GPLv3
* @link https://fogproject.org
*/
class CaponeTasking extends FOGBase
class CaponeTasking extends \FOG\Base\FOGBase
{
/**
* The actions supported fog capone.
Expand Down Expand Up @@ -62,12 +62,12 @@ public function __construct()
try {
$strSetup = "%s|%s|%s|%s|%s|%s|%s";
ob_start();
$capones = Route::getList(
$capones = \FOG\Router\Route::getList(
'capone',
['key' => $key]
);
foreach ($capones as &$Capone) {
$Image = new Image($Capone->imageID);
$Image = new \FOG\Items\Image($Capone->imageID);
$OS = $Image->getOS();
$StorageNode = $Image
->getStorageGroup()
Expand Down
2 changes: 1 addition & 1 deletion helloworld/class/helloworld.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
* @license http://opensource.org/licenses/gpl-3.0 GPLv3
* @link https://fogproject.org
*/
class HelloWorld extends FOGController
class HelloWorld extends \FOG\Base\FOGController
{
/**
* The database table this model maps to.
Expand Down
4 changes: 2 additions & 2 deletions helloworld/class/helloworldmanager.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
* @license http://opensource.org/licenses/gpl-3.0 GPLv3
* @link https://fogproject.org
*/
class HelloWorldManager extends FOGManagerController
class HelloWorldManager extends \FOG\Base\FOGManagerController
{
/**
* The table name.
Expand Down Expand Up @@ -107,7 +107,7 @@ public function schema()
*/
public function install()
{
$res = Schema::applyUpdates($this->schema(), 0);
$res = \FOG\Items\Schema::applyUpdates($this->schema(), 0);
return $res['error'] === null;
}
}
2 changes: 1 addition & 1 deletion helloworld/hooks/addhelloworldapi.hook.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
* @license http://opensource.org/licenses/gpl-3.0 GPLv3
* @link https://fogproject.org
*/
class AddHelloWorldAPI extends Hook
class AddHelloWorldAPI extends \FOG\Base\Hook
{
/**
* The name of this hook.
Expand Down
Loading