fix(2.10.2): AfterInstall must be a no-namespace class

EspoCRM's extension installer (ExtensionManager.php → Base.php:397) does
require_once on scripts/AfterInstall.php and then `new AfterInstall()`
without any namespace prefix. The 2.10.0/2.10.1 version had
`namespace Espo\Modules\SmartAssistant\Scripts;` which resolved at
install time to "Class AfterInstall not found" and aborted the install
with HTTP 500 from KlearBrandingExtension/action/install.

Match the pattern used by GoogleIntegration/scripts/AfterInstall.php:
plain top-level class, no namespace, optional `use` for the Container
type hint. Same behavior (idempotent seed of 7 AssistantPrompt rows
with skipAll), just loadable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 09:36:42 +00:00
parent d266380e6d
commit bd1d484e74
2 changed files with 19 additions and 26 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
"module": "SmartAssistant",
"description": "Unified AI Assistant for Legal CRM — floating chat with case memory, office alerts, and AI Gateway integration",
"author": "klear",
"version": "2.10.1",
"version": "2.10.2",
"acceptableVersions": [
">=8.0.0"
],
+18 -25
View File
@@ -1,16 +1,19 @@
<?php
namespace Espo\Modules\SmartAssistant\Scripts;
/**
* AfterInstall script for SmartAssistant extension 2.10.x.
*
* Idempotently seeds the AssistantPrompt entity with 7 default prompt
* sections (tool_rules, writing_style, legal_assistance, personality_case,
* personality_office, other_rules_case, other_rules_office). If a row with
* the same `key` already exists it is left alone — admin edits win.
*
* Class must be named `AfterInstall` with NO namespace — that's what the
* EspoCRM extension installer looks for in scripts/AfterInstall.php.
* See application/Espo/Core/Upgrades/ExtensionManager.php scriptNames map.
*/
use Espo\Core\Container;
/**
* Runs after the extension is installed/upgraded.
*
* Idempotent: only inserts AssistantPrompt rows whose `key` doesn't exist yet.
* If the row exists (even with edits), we leave it alone — the user's
* customizations win.
*/
class AfterInstall
{
public function run(Container $container, $params = null): void
@@ -18,21 +21,11 @@ class AfterInstall
$entityManager = $container->get('entityManager');
$log = $container->get('log');
$resourceFile = __DIR__ . '/../files/custom/Espo/Modules/SmartAssistant/Resources/data/default-prompts.json';
// The JSON file lives inside the installed module resources at runtime.
$resourceFile = 'custom/Espo/Modules/SmartAssistant/Resources/data/default-prompts.json';
if (!is_file($resourceFile)) {
// Fall back to the module Resources path when the extension is
// installed (Resources/ is the canonical location at runtime).
$resourceFile = dirname(__DIR__) . '/files/custom/Espo/Modules/SmartAssistant/Resources/data/default-prompts.json';
}
if (!is_file($resourceFile)) {
// Final fallback: the module's installed Resources path.
$resourceFile = 'custom/Espo/Modules/SmartAssistant/Resources/data/default-prompts.json';
}
if (!is_file($resourceFile)) {
$log->warning("[SmartAssistant] AfterInstall: default-prompts.json not found, skipping seed");
$log->warning("[SmartAssistant] AfterInstall: $resourceFile not found, skipping seed");
return;
}
@@ -40,7 +33,7 @@ class AfterInstall
$defaults = json_decode($json, true);
if (!is_array($defaults)) {
$log->warning("[SmartAssistant] AfterInstall: default-prompts.json invalid JSON");
$log->warning("[SmartAssistant] AfterInstall: invalid JSON in $resourceFile");
return;
}
@@ -73,8 +66,8 @@ class AfterInstall
'isActive' => true,
]);
// skipAll bypasses beforeSave hooks that require a logged-in user
// service (install runs without a session) and ACL checks (we're
// seeding firm-wide defaults; no user is implicated).
// service (installer runs without an HTTP session) and ACL checks
// (we're seeding firm-wide defaults; no user is implicated).
$entityManager->saveEntity($entity, ['skipAll' => true]);
$created++;
}