Skip to content

Commit f11ecf1

Browse files
bcordisclaude
andcommitted
feat: add 'composer verify' command to check/fix dev extension registration
New 'verify' command (composer verify / php build/proclaim_build.php verify) reads each dev Joomla site's configuration.php, connects to its database, and checks that all sub-extensions (libraries, plugins, modules, component) are registered in #__extensions. For missing extensions: - Libraries: inserts the extension record + runs install SQL for tables - Plugins: inserts and enables the extension record - Component: warns to install via Extension Manager For existing extensions: - Fixes enabled/locked state if mismatched This ensures dev boxes (J5-dev, J6-dev) have all extensions properly registered after 'composer symlink', including the new lib_cwmscripture library which needs a DB record for namespace autoloading. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d5d2a24 commit f11ecf1

File tree

2 files changed

+228
-0
lines changed

2 files changed

+228
-0
lines changed

build/proclaim_build.php

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
case 'joomla-latest':
3535
doJoomlaLatest();
3636
break;
37+
case 'verify':
38+
doVerifyExtensions($verbose);
39+
break;
3740
case 'lint-syntax':
3841
doLintSyntax($verbose);
3942
break;
@@ -64,6 +67,7 @@ function showHelp(): void
6467
echo " build Build component package (zip)\n";
6568
echo " install-joomla Download and install Joomla\n";
6669
echo " joomla-latest Show latest available Joomla version\n";
70+
echo " verify Verify all sub-extensions are registered in dev Joomla DB(s)\n";
6771
echo " lint-syntax Check PHP syntax errors\n";
6872
echo "\nOptions:\n";
6973
echo " -v, --verbose Show detailed output (e.g., each symlink path)\n";
@@ -477,6 +481,229 @@ function symlink_force(string $target, string $link, bool $quiet = false): void
477481
}
478482
}
479483

484+
/**
485+
* Read Joomla configuration.php and return DB connection details + table prefix.
486+
*
487+
* @param string $joomlaPath Path to Joomla installation root
488+
*
489+
* @return array{host: string, user: string, password: string, db: string, dbprefix: string}|null
490+
*
491+
* @since 10.3.0
492+
*/
493+
function getJoomlaDbConfig(string $joomlaPath): ?array
494+
{
495+
$configFile = $joomlaPath . '/configuration.php';
496+
497+
if (!file_exists($configFile)) {
498+
return null;
499+
}
500+
501+
// Load JConfig class from configuration.php
502+
$content = file_get_contents($configFile);
503+
// Avoid redeclaring JConfig if already loaded from another site
504+
$content = str_replace('class JConfig', 'class JConfig_' . md5($joomlaPath), $content);
505+
$content = str_replace('<?php', '', $content);
506+
eval($content);
507+
508+
$className = 'JConfig_' . md5($joomlaPath);
509+
510+
if (!class_exists($className)) {
511+
return null;
512+
}
513+
514+
$config = new $className();
515+
516+
return [
517+
'host' => $config->host ?? 'localhost',
518+
'user' => $config->user ?? '',
519+
'password' => $config->password ?? '',
520+
'db' => $config->db ?? '',
521+
'dbprefix' => $config->dbprefix ?? 'jos_',
522+
];
523+
}
524+
525+
/**
526+
* Verify and register all Proclaim sub-extensions in each dev Joomla database.
527+
*
528+
* Checks libraries, plugins, and modules from the install action queue are
529+
* registered in #__extensions. Inserts missing entries and runs install SQL
530+
* for libraries. Also enables plugins that should be enabled.
531+
*
532+
* @param bool $verbose Show detailed output
533+
*
534+
* @return void
535+
*
536+
* @since 10.3.0
537+
*/
538+
function doVerifyExtensions(bool $verbose = false): void
539+
{
540+
$props = getProperties();
541+
$joomlaPaths = getJoomlaPaths($props);
542+
543+
if (\count($joomlaPaths) === 0) {
544+
throw new \RuntimeException('No Joomla paths configured. Run \'composer setup\' first.');
545+
}
546+
547+
// Define all expected extensions
548+
$expected = [
549+
// Libraries
550+
['type' => 'library', 'element' => 'cwmscripture', 'name' => 'lib_cwmscripture', 'folder' => '', 'enabled' => 1, 'locked' => 1],
551+
// Content plugin (from ScriptureLinks)
552+
['type' => 'plugin', 'element' => 'scripturelinks', 'name' => 'plg_content_scripturelinks', 'folder' => 'content', 'enabled' => 1, 'locked' => 0],
553+
// Proclaim plugins
554+
['type' => 'plugin', 'element' => 'proclaim', 'name' => 'plg_finder_proclaim', 'folder' => 'finder', 'enabled' => 1, 'locked' => 0],
555+
['type' => 'plugin', 'element' => 'proclaim', 'name' => 'plg_schemaorg_proclaim', 'folder' => 'schemaorg', 'enabled' => 1, 'locked' => 0],
556+
['type' => 'plugin', 'element' => 'proclaim', 'name' => 'plg_system_proclaim', 'folder' => 'system', 'enabled' => 1, 'locked' => 0],
557+
['type' => 'plugin', 'element' => 'proclaim', 'name' => 'plg_task_proclaim', 'folder' => 'task', 'enabled' => 1, 'locked' => 0],
558+
// Component
559+
['type' => 'component', 'element' => 'com_proclaim', 'name' => 'com_proclaim', 'folder' => '', 'enabled' => 1, 'locked' => 0],
560+
];
561+
562+
foreach ($joomlaPaths as $joomlaPath) {
563+
if (!is_dir($joomlaPath)) {
564+
echo "WARNING: Path not found, skipping: $joomlaPath\n";
565+
continue;
566+
}
567+
568+
echo "\n=== Verifying: $joomlaPath ===\n";
569+
570+
$dbConfig = getJoomlaDbConfig($joomlaPath);
571+
572+
if ($dbConfig === null) {
573+
echo " ERROR: Could not read configuration.php\n";
574+
continue;
575+
}
576+
577+
try {
578+
$pdo = new PDO(
579+
'mysql:host=' . $dbConfig['host'] . ';dbname=' . $dbConfig['db'] . ';charset=utf8mb4',
580+
$dbConfig['user'],
581+
$dbConfig['password'],
582+
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
583+
);
584+
} catch (PDOException $e) {
585+
echo " ERROR: DB connection failed: " . $e->getMessage() . "\n";
586+
continue;
587+
}
588+
589+
$prefix = $dbConfig['dbprefix'];
590+
$ok = 0;
591+
$fixed = 0;
592+
$errors = 0;
593+
594+
foreach ($expected as $ext) {
595+
$type = $ext['type'];
596+
$element = $ext['element'];
597+
$folder = $ext['folder'];
598+
$name = $ext['name'];
599+
600+
// Check if extension exists in #__extensions
601+
$sql = "SELECT extension_id, enabled, locked FROM {$prefix}extensions WHERE type = ? AND element = ?";
602+
$params = [$type, $element];
603+
604+
if ($type === 'plugin' && $folder !== '') {
605+
$sql .= ' AND folder = ?';
606+
$params[] = $folder;
607+
}
608+
609+
$stmt = $pdo->prepare($sql);
610+
$stmt->execute($params);
611+
$row = $stmt->fetch(PDO::FETCH_ASSOC);
612+
613+
if ($row) {
614+
// Extension exists — check enabled/locked state
615+
$needsUpdate = false;
616+
$updates = [];
617+
618+
if ((int) $row['enabled'] !== $ext['enabled']) {
619+
$updates[] = "enabled = {$ext['enabled']}";
620+
$needsUpdate = true;
621+
}
622+
623+
if ($ext['locked'] && (int) $row['locked'] !== 1) {
624+
$updates[] = 'locked = 1';
625+
$needsUpdate = true;
626+
}
627+
628+
if ($needsUpdate) {
629+
$updateSql = "UPDATE {$prefix}extensions SET " . implode(', ', $updates)
630+
. " WHERE extension_id = " . (int) $row['extension_id'];
631+
$pdo->exec($updateSql);
632+
echo " FIXED: $name ($type) — updated " . implode(', ', $updates) . "\n";
633+
$fixed++;
634+
} else {
635+
if ($verbose) {
636+
echo " OK: $name ($type)\n";
637+
}
638+
$ok++;
639+
}
640+
} else {
641+
// Extension missing — register it
642+
if ($type === 'library') {
643+
// Register library and run install SQL
644+
$namespace = 'CWM\\\\Library\\\\Scripture';
645+
$manifest = '{"name":"lib_cwmscripture","libraryname":"cwmscripture","version":"1.0.0"}';
646+
$insertSql = "INSERT INTO {$prefix}extensions "
647+
. "(name, type, element, folder, enabled, access, locked, manifest_cache, params, namespace) "
648+
. "VALUES (?, 'library', ?, '', 1, 1, ?, ?, '{}', ?)";
649+
$stmt = $pdo->prepare($insertSql);
650+
$stmt->execute([$name, $element, $ext['locked'], $manifest, $namespace]);
651+
652+
// Run install SQL for tables
653+
$installSql = BASE_DIR . '/libraries/cwmscripture_src/lib_cwmscripture/sql/install.mysql.utf8.sql';
654+
655+
if (file_exists($installSql)) {
656+
$sql = file_get_contents($installSql);
657+
$sql = str_replace('#__', $prefix, $sql);
658+
// Execute each statement separately
659+
$statements = array_filter(array_map('trim', explode(';', $sql)));
660+
661+
foreach ($statements as $statement) {
662+
if ($statement !== '' && !str_starts_with($statement, '--')) {
663+
try {
664+
$pdo->exec($statement);
665+
} catch (PDOException $e) {
666+
// Table may already exist — that's fine
667+
if ($verbose) {
668+
echo " NOTE: SQL: " . substr($e->getMessage(), 0, 80) . "\n";
669+
}
670+
}
671+
}
672+
}
673+
}
674+
675+
echo " ADDED: $name (library) — registered + tables created\n";
676+
$fixed++;
677+
} elseif ($type === 'plugin') {
678+
$namespace = match ($folder) {
679+
'content' => 'CWM\\\\Plugin\\\\Content\\\\ScriptureLinks',
680+
'finder' => 'CWM\\\\Plugin\\\\Finder\\\\Proclaim',
681+
'system' => 'CWM\\\\Plugin\\\\System\\\\Proclaim',
682+
'task' => 'CWM\\\\Plugin\\\\Task\\\\Proclaim',
683+
'schemaorg' => 'CWM\\\\Plugin\\\\Schemaorg\\\\Proclaim',
684+
default => '',
685+
};
686+
$insertSql = "INSERT INTO {$prefix}extensions "
687+
. "(name, type, element, folder, enabled, access, locked, params, namespace) "
688+
. "VALUES (?, 'plugin', ?, ?, ?, 1, 0, '{}', ?)";
689+
$stmt = $pdo->prepare($insertSql);
690+
$stmt->execute([$name, $element, $folder, $ext['enabled'], $namespace]);
691+
echo " ADDED: $name (plugin/$folder)\n";
692+
$fixed++;
693+
} elseif ($type === 'component') {
694+
// Component should already exist if Proclaim is installed
695+
echo " MISS: $name (component) — install Proclaim via Extension Manager first\n";
696+
$errors++;
697+
}
698+
}
699+
}
700+
701+
echo " Summary: $ok OK, $fixed fixed, $errors errors\n";
702+
}
703+
704+
echo "\nDone.\n";
705+
}
706+
480707
/**
481708
* Builds the component package (ZIP file).
482709
*

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@
116116
"joomla-install": "php build/proclaim_build.php install-joomla",
117117
"joomla-latest": "php build/proclaim_build.php joomla-latest",
118118
"symlink": "php build/proclaim_build.php link",
119+
"verify": "php build/proclaim_build.php verify",
119120
"clean": "php build/proclaim_build.php clean",
120121
"build": "php build/proclaim_build.php build",
121122
"version": "php build/bump.php",

0 commit comments

Comments
 (0)