|
34 | 34 | case 'joomla-latest': |
35 | 35 | doJoomlaLatest(); |
36 | 36 | break; |
| 37 | + case 'verify': |
| 38 | + doVerifyExtensions($verbose); |
| 39 | + break; |
37 | 40 | case 'lint-syntax': |
38 | 41 | doLintSyntax($verbose); |
39 | 42 | break; |
@@ -64,6 +67,7 @@ function showHelp(): void |
64 | 67 | echo " build Build component package (zip)\n"; |
65 | 68 | echo " install-joomla Download and install Joomla\n"; |
66 | 69 | echo " joomla-latest Show latest available Joomla version\n"; |
| 70 | + echo " verify Verify all sub-extensions are registered in dev Joomla DB(s)\n"; |
67 | 71 | echo " lint-syntax Check PHP syntax errors\n"; |
68 | 72 | echo "\nOptions:\n"; |
69 | 73 | 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 |
477 | 481 | } |
478 | 482 | } |
479 | 483 |
|
| 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 | + |
480 | 707 | /** |
481 | 708 | * Builds the component package (ZIP file). |
482 | 709 | * |
|
0 commit comments