From b92adbe3313ce4c7e44825c66f121f3a146605ea Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Mon, 29 Jul 2024 22:03:13 -0600 Subject: [PATCH 01/13] Added initial type hinting and better imports for update managing classes --- modules/system/classes/UpdateManager.php | 285 ++++++++--------------- modules/system/controllers/Updates.php | 125 +++++----- modules/system/models/PluginVersion.php | 8 +- 3 files changed, 170 insertions(+), 248 deletions(-) diff --git a/modules/system/classes/UpdateManager.php b/modules/system/classes/UpdateManager.php index 8a9569558c..aed24fa2b3 100644 --- a/modules/system/classes/UpdateManager.php +++ b/modules/system/classes/UpdateManager.php @@ -1,23 +1,29 @@ -getMigrationTableName()); @@ -212,10 +193,9 @@ public function update() /** * Checks for new updates and returns the amount of unapplied updates. * Only requests from the server at a set interval (retry timer). - * @param boolean $force Ignore the retry timer. - * @return int Number of unapplied updates. + * @param $force Ignore the retry timer. */ - public function check($force = false) + public function check(bool $force = false): int { /* * Already know about updates, never retry. @@ -253,10 +233,9 @@ public function check($force = false) /** * Requests an update list used for checking for new updates. - * @param boolean $force Request application and plugins hash list regardless of version. - * @return array + * @param $force Request application and plugins hash list regardless of version. */ - public function requestUpdateList($force = false) + public function requestUpdateList(bool $force = false): array { $installed = PluginVersion::all(); $versions = $installed->lists('version', 'code'); @@ -349,19 +328,16 @@ public function requestUpdateList($force = false) /** * Requests details about a project based on its identifier. - * @param string $projectId - * @return array */ - public function requestProjectDetails($projectId) + public function requestProjectDetails(string $projectId): array { return $this->requestServerData('project/detail', ['id' => $projectId]); } /** * Roll back all modules and plugins. - * @return self */ - public function uninstall() + public function uninstall(): static { /* * Rollback plugins @@ -411,10 +387,9 @@ public function uninstall() * to the code = less confidence. * - `changes`: If $detailed is true, this will include the list of files modified, created and deleted. * - * @param bool $detailed If true, the list of files modified, added and deleted will be included in the result. - * @return array + * @param $detailed If true, the list of files modified, added and deleted will be included in the result. */ - public function getBuildNumberManually($detailed = false) + public function getBuildNumberManually(bool $detailed = false): array { $source = new SourceManifest(); $manifest = new FileManifest(null, null, true); @@ -426,10 +401,9 @@ public function getBuildNumberManually($detailed = false) /** * Sets the build number in the database. * - * @param bool $detailed If true, the list of files modified, added and deleted will be included in the result. - * @return void + * @param $detailed If true, the list of files modified, added and deleted will be included in the result. */ - public function setBuildNumberManually($detailed = false) + public function setBuildNumberManually(bool $detailed = false): array { $build = $this->getBuildNumberManually($detailed); @@ -446,19 +420,16 @@ public function setBuildNumberManually($detailed = false) /** * Returns the currently installed system hash. - * @return string */ - public function getHash() + public function getHash(): string { return Parameter::get('system::core.hash', md5('NULL')); } /** * Run migrations on a single module - * @param string $module Module name - * @return self */ - public function migrateModule($module) + public function migrateModule(string $module): static { if (isset($this->notesOutput)) { $this->migrator->setOutput($this->notesOutput); @@ -475,14 +446,12 @@ public function migrateModule($module) /** * Run seeds on a module - * @param string $module Module name - * @return self */ - public function seedModule($module) + public function seedModule(string $module): static { $className = '\\' . $module . '\Database\Seeds\DatabaseSeeder'; if (!class_exists($className)) { - return; + return $this; } $this->out('', true); @@ -503,19 +472,17 @@ public function seedModule($module) /** * Downloads the core from the update server. - * @param string $hash Expected file hash. - * @return void + * @param $hash Expected file hash. */ - public function downloadCore($hash) + public function downloadCore(string $hash): void { $this->requestServerFile('core/get', 'core', $hash, ['type' => 'update']); } /** * Extracts the core after it has been downloaded. - * @return void */ - public function extractCore() + public function extractCore(): void { $filePath = $this->getFilePath('core'); @@ -528,12 +495,8 @@ public function extractCore() /** * Sets the build number and hash - * @param string $hash - * @param string $build - * @param bool $modified - * @return void */ - public function setBuild($build, $hash = null, $modified = false) + public function setBuild(string $build, ?string $hash = null, bool $modified = false): void { $params = [ 'system::core.build' => $build, @@ -553,37 +516,31 @@ public function setBuild($build, $hash = null, $modified = false) /** * Looks up a plugin from the update server. - * @param string $name Plugin name. - * @return array Details about the plugin. */ - public function requestPluginDetails($name) + public function requestPluginDetails(string $name): array { return $this->requestServerData('plugin/detail', ['name' => $name]); } /** * Looks up content for a plugin from the update server. - * @param string $name Plugin name. - * @return array Content for the plugin. */ - public function requestPluginContent($name) + public function requestPluginContent(string $name): array { return $this->requestServerData('plugin/content', ['name' => $name]); } /** * Runs update on a single plugin - * @param string $name Plugin name. - * @return self */ - public function updatePlugin($name) + public function updatePlugin(string $name): static { /* * Update the plugin database and version */ if (!($plugin = $this->pluginManager->findByIdentifier($name))) { $this->write(Error::class, sprintf('Unable to find plugin %s', $name)); - return; + return $this; } $this->out(sprintf('Migrating %s (%s) plugin...', Lang::get($plugin->pluginDetails()['name']), $name)); @@ -599,11 +556,9 @@ public function updatePlugin($name) /** * Rollback an existing plugin * - * @param string $name Plugin name. - * @param string $stopOnVersion If this parameter is specified, the process stops once the provided version number is reached - * @return self + * @param $stopOnVersion If this parameter is specified, the process stops once the provided version number is reached */ - public function rollbackPlugin(string $name, string $stopOnVersion = null) + public function rollbackPlugin(string $name, string $stopOnVersion = null): static { /* * Remove the plugin database and version @@ -640,24 +595,22 @@ public function rollbackPlugin(string $name, string $stopOnVersion = null) /** * Downloads a plugin from the update server. - * @param string $name Plugin name. - * @param string $hash Expected file hash. - * @param boolean $installation Indicates whether this is a plugin installation request. - * @return self + * @param $installation Indicates whether this is a plugin installation request. */ - public function downloadPlugin($name, $hash, $installation = false) + public function downloadPlugin(string $name, string $hash, bool $installation = false): static { $fileCode = $name . $hash; $this->requestServerFile('plugin/get', $fileCode, $hash, [ 'name' => $name, 'installation' => $installation ? 1 : 0 ]); + return $this; } /** * Extracts a plugin after it has been downloaded. */ - public function extractPlugin($name, $hash) + public function extractPlugin(string $name, string $hash): void { $fileCode = $name . $hash; $filePath = $this->getFilePath($fileCode); @@ -675,31 +628,26 @@ public function extractPlugin($name, $hash) /** * Looks up a theme from the update server. - * @param string $name Theme name. - * @return array Details about the theme. */ - public function requestThemeDetails($name) + public function requestThemeDetails(string $name): array { return $this->requestServerData('theme/detail', ['name' => $name]); } /** * Downloads a theme from the update server. - * @param string $name Theme name. - * @param string $hash Expected file hash. - * @return self */ - public function downloadTheme($name, $hash) + public function downloadTheme(string $name, string $hash): static { $fileCode = $name . $hash; - $this->requestServerFile('theme/get', $fileCode, $hash, ['name' => $name]); + return $this; } /** * Extracts a theme after it has been downloaded. */ - public function extractTheme($name, $hash) + public function extractTheme(string $name, string $hash): void { $fileCode = $name . $hash; $filePath = $this->getFilePath($fileCode); @@ -719,7 +667,7 @@ public function extractTheme($name, $hash) // Products // - public function requestProductDetails($codes, $type = null) + public function requestProductDetails($codes, $type = null): array { if ($type != 'plugin' && $type != 'theme') { $type = 'plugin'; @@ -771,7 +719,7 @@ public function requestProductDetails($codes, $type = null) /** * Returns popular themes found on the marketplace. */ - public function requestPopularProducts($type = null) + public function requestPopularProducts(string $type = null): array { if ($type != 'plugin' && $type != 'theme') { $type = 'plugin'; @@ -797,7 +745,7 @@ public function requestPopularProducts($type = null) return $data; } - protected function loadProductDetailCache() + protected function loadProductDetailCache(): void { $defaultCache = ['theme' => [], 'plugin' => []]; $cacheKey = 'system-updates-product-details'; @@ -809,7 +757,7 @@ protected function loadProductDetailCache() } } - protected function saveProductDetailCache() + protected function saveProductDetailCache(): void { if ($this->productCache === null) { $this->loadProductDetailCache(); @@ -820,7 +768,7 @@ protected function saveProductDetailCache() Cache::put($cacheKey, base64_encode(serialize($this->productCache)), $expiresAt); } - protected function cacheProductDetail($type, $code, $data) + protected function cacheProductDetail(string $type, string $code, array|int $data): void { if ($this->productCache === null) { $this->loadProductDetailCache(); @@ -836,7 +784,7 @@ protected function cacheProductDetail($type, $code, $data) /** * Returns the latest changelog information. */ - public function requestChangelog() + public function requestChangelog(): array { $build = Parameter::get('system::core.build'); @@ -876,12 +824,9 @@ public function requestChangelog() /** * Writes output to the console using a Laravel CLI View component. - * - * @param \Illuminate\Console\View\Components\Component $component - * @param array $arguments - * @return static + * @param $component Class extending \Illuminate\Console\View\Components\Component to be used to render the message */ - protected function write($component, ...$arguments) + protected function write(string $component, ...$arguments): static { if ($this->notesOutput !== null) { with(new $component($this->notesOutput))->render(...$arguments); @@ -892,12 +837,8 @@ protected function write($component, ...$arguments) /** * Writes output to the console. - * - * @param string $message - * @param bool $newline - * @return static */ - protected function out($message, $newline = false) + protected function out(string $message, bool $newline = false): static { if ($this->notesOutput !== null) { $this->notesOutput->write($message, $newline); @@ -908,10 +849,8 @@ protected function out($message, $newline = false) /** * Sets an output stream for writing notes. - * @param Illuminate\Console\Command $output - * @return self */ - public function setNotesOutput($output) + public function setNotesOutput(OutputStyle $output): static { $this->notesOutput = $output; @@ -924,11 +863,8 @@ public function setNotesOutput($output) /** * Contacts the update server for a response. - * @param string $uri Gateway API URI - * @param array $postData Extra post data - * @return array */ - public function requestServerData($uri, $postData = []) + public function requestServerData(string $uri, array $postData = []): array { $result = Http::post($this->createServerUrl($uri), function ($http) use ($postData) { $this->applyHttpAttributes($http, $postData); @@ -963,18 +899,21 @@ public function requestServerData($uri, $postData = []) throw new ApplicationException(Lang::get('system::lang.server.response_invalid')); } + if (!is_array($resultData)) { + throw new ApplicationException(Lang::get('system::lang.server.response_invalid')); + } + return $resultData; } /** * Downloads a file from the update server. - * @param string $uri Gateway API URI - * @param string $fileCode A unique code for saving the file. - * @param string $expectedHash The expected file hash of the file. - * @param array $postData Extra post data - * @return void + * @param $uri Gateway API URI + * @param $fileCode A unique code for saving the file. + * @param $expectedHash The expected file hash of the file. + * @param $postData Extra post data */ - public function requestServerFile($uri, $fileCode, $expectedHash, $postData = []) + public function requestServerFile(string $uri, string $fileCode, string $expectedHash, array $postData = []): void { $filePath = $this->getFilePath($fileCode); @@ -995,10 +934,8 @@ public function requestServerFile($uri, $fileCode, $expectedHash, $postData = [] /** * Calculates a file path for a file code - * @param string $fileCode A unique file code - * @return string Full path on the disk */ - protected function getFilePath($fileCode) + protected function getFilePath(string $fileCode): string { $name = md5($fileCode) . '.arc'; return $this->tempDirectory . '/' . $name; @@ -1006,10 +943,8 @@ protected function getFilePath($fileCode) /** * Set the API security for all transmissions. - * @param string $key API Key - * @param string $secret API Secret */ - public function setSecurity($key, $secret) + public function setSecurity(string $key, string $secret): void { $this->key = $key; $this->secret = $secret; @@ -1017,10 +952,8 @@ public function setSecurity($key, $secret) /** * Create a complete gateway server URL from supplied URI - * @param string $uri URI - * @return string URL */ - protected function createServerUrl($uri) + protected function createServerUrl(string $uri): string { $gateway = Config::get('cms.updateServer', 'https://api.wintercms.com/marketplace'); if (substr($gateway, -1) != '/') { @@ -1032,11 +965,8 @@ protected function createServerUrl($uri) /** * Modifies the Network HTTP object with common attributes. - * @param Http $http Network object - * @param array $postData Post data - * @return void */ - protected function applyHttpAttributes($http, $postData) + protected function applyHttpAttributes(NetworkHttp $http, array $postData): void { $postData['protocol_version'] = '1.1'; $postData['client'] = 'october'; @@ -1071,9 +1001,8 @@ protected function applyHttpAttributes($http, $postData) /** * Create a nonce based on millisecond time - * @return int */ - protected function createNonce() + protected function createNonce(): int { $mt = explode(' ', microtime()); return $mt[1] . substr($mt[0], 2, 6); @@ -1081,29 +1010,21 @@ protected function createNonce() /** * Create a unique signature for transmission. - * @return string */ - protected function createSignature($data, $secret) + protected function createSignature(array $data, string $secret): string { return base64_encode(hash_hmac('sha512', http_build_query($data, '', '&'), base64_decode($secret), true)); } - /** - * @return string - */ - public function getMigrationTableName() + public function getMigrationTableName(): string { return Config::get('database.migrations', 'migrations'); } /** * Adds a message from a specific migration or seeder. - * - * @param string|object $class - * @param string|array $message - * @return void */ - protected function addMessage($class, $message) + protected function addMessage(string|object $class, string|array $message): void { if (empty($message)) { return; @@ -1125,10 +1046,8 @@ protected function addMessage($class, $message) /** * Prints collated messages from the migrations and seeders - * - * @return void */ - protected function printMessages() + protected function printMessages(): void { if (!count($this->messages)) { return; diff --git a/modules/system/controllers/Updates.php b/modules/system/controllers/Updates.php index f27e1e4f5d..bd79ce0265 100644 --- a/modules/system/controllers/Updates.php +++ b/modules/system/controllers/Updates.php @@ -1,30 +1,35 @@ -vars['coreBuild'] = Parameter::get('system::core.build'); $this->vars['coreBuildModified'] = Parameter::get('system::core.modified', false); @@ -80,23 +85,23 @@ public function index() $this->vars['projectOwner'] = Parameter::get('system::project.owner'); $this->vars['pluginsActiveCount'] = PluginVersion::applyEnabled()->count(); $this->vars['pluginsCount'] = PluginVersion::count(); - return $this->asExtension('ListController')->index(); + $this->asExtension('ListController')->index(); } /** * Plugin manage controller */ - public function manage() + public function manage(): void { $this->pageTitle = 'system::lang.plugins.manage'; PluginManager::instance()->clearFlagCache(); - return $this->asExtension('ListController')->index(); + $this->asExtension('ListController')->index(); } /** * Install new plugins / themes */ - public function install($tab = null) + public function install($tab = null): ?HttpResponse { if (get('search')) { return Response::make($this->onSearchProducts()); @@ -116,9 +121,11 @@ public function install($tab = null) catch (Exception $ex) { $this->handleError($ex); } + + return null; } - public function details($urlCode = null, $tab = null) + public function details($urlCode = null, $tab = null): void { try { $this->pageTitle = 'system::lang.updates.details_title'; @@ -178,15 +185,15 @@ public function details($urlCode = null, $tab = null) } } - protected function getPluginMarkdownFile($path, $filenames) + protected function getPluginMarkdownFile(string $path, array $filenames): ?string { $contents = null; foreach ($filenames as $file) { - if (!File::exists($path . '/'.$file)) { + if (!File::exists($path . '/' . $file)) { continue; } - $contents = File::get($path . '/'.$file); + $contents = File::get($path . '/' . $file); /* * Parse markdown, clean HTML, remove first H1 tag @@ -199,7 +206,7 @@ protected function getPluginMarkdownFile($path, $filenames) return $contents; } - protected function getWarnings() + protected function getWarnings(): array { $warnings = []; $missingDependencies = PluginManager::instance()->findMissingDependencies(); @@ -242,9 +249,8 @@ protected function getWarnings() * - positive - Default CSS class * * @see Backend\Behaviors\ListController - * @return string */ - public function listInjectRowClass($record, $definition = null) + public function listInjectRowClass($record, $definition = null): string { if ($record->disabledByConfig) { return 'hidden'; @@ -255,7 +261,7 @@ public function listInjectRowClass($record, $definition = null) } if ($definition != 'manage') { - return; + return ''; } if ($record->disabledBySystem) { @@ -272,7 +278,7 @@ public function listInjectRowClass($record, $definition = null) /** * Runs a specific update step. */ - public function onExecuteStep() + public function onExecuteStep(): ?RedirectResponse { /* * Address timeout limits @@ -321,6 +327,8 @@ public function onExecuteStep() Flash::success(Lang::get('system::lang.install.install_success')); return Redirect::refresh(); } + + return null; } // @@ -330,7 +338,7 @@ public function onExecuteStep() /** * Spawns the update checker popup. */ - public function onLoadUpdates() + public function onLoadUpdates(): string { return $this->makePartial('update_form'); } @@ -338,7 +346,7 @@ public function onLoadUpdates() /** * Contacts the update server for a list of necessary updates. */ - public function onCheckForUpdates() + public function onCheckForUpdates(): array { try { $manager = UpdateManager::instance(); @@ -362,10 +370,8 @@ public function onCheckForUpdates() /** * Loops the update list and checks for actionable updates. - * @param array $result - * @return array */ - protected function processImportantUpdates($result) + protected function processImportantUpdates(array $result): array { $hasImportantUpdates = false; @@ -416,10 +422,8 @@ protected function processImportantUpdates($result) /** * Reverses the update lists for the core and all plugins. - * @param array $result - * @return array */ - protected function processUpdateLists($result) + protected function processUpdateLists(array $result): array { if ($core = array_get($result, 'core')) { $result['core']['updates'] = array_reverse(array_get($core, 'updates', []), true); @@ -435,10 +439,9 @@ protected function processUpdateLists($result) /** * Contacts the update server for a list of necessary updates. * - * @param bool $force Whether or not to force the redownload of existing tools - * @return string The rendered "execute" partial + * @param $force Whether or not to force the redownload of existing tools */ - public function onForceUpdate($force = true) + public function onForceUpdate(bool $force = true): string { try { $manager = UpdateManager::instance(); @@ -485,7 +488,7 @@ public function onForceUpdate($force = true) /** * Converts the update data to an actionable array of steps. */ - public function onApplyUpdates() + public function onApplyUpdates(): string { try { /* @@ -577,7 +580,7 @@ public function onApplyUpdates() return $this->makePartial('execute'); } - protected function buildUpdateSteps($core, $plugins, $themes, $isInstallationRequest) + protected function buildUpdateSteps($core, $plugins, $themes, $isInstallationRequest): array { if (!is_array($core)) { $core = [null, null]; @@ -669,7 +672,7 @@ protected function buildUpdateSteps($core, $plugins, $themes, $isInstallationReq /** * Displays the form for entering a Project ID */ - public function onLoadProjectForm() + public function onLoadProjectForm(): string { return $this->makePartial('project_form'); } @@ -677,7 +680,7 @@ public function onLoadProjectForm() /** * Validate the project ID and execute the project installation */ - public function onAttachProject() + public function onAttachProject(): string { try { if (!$projectId = trim(post('project_id'))) { @@ -701,7 +704,7 @@ public function onAttachProject() } } - public function onDetachProject() + public function onDetachProject(): RedirectResponse { Parameter::set([ 'system::project.id' => null, @@ -719,8 +722,10 @@ public function onDetachProject() /** * Displays changelog information + * + * @throws ApplicationException if the changelog could not be fetched from the server */ - public function onLoadChangelog() + public function onLoadChangelog(): string { try { $fetchedContent = UpdateManager::instance()->requestChangelog(); @@ -746,8 +751,10 @@ public function onLoadChangelog() /** * Validate the plugin code and execute the plugin installation + * + * @throws ApplicationException If validation fails or the plugin cannot be installed */ - public function onInstallPlugin() + public function onInstallPlugin(): string { try { if (!$code = trim(post('code'))) { @@ -791,9 +798,8 @@ public function onInstallPlugin() /** * Rollback and remove a single plugin from the system. - * @return void */ - public function onRemovePlugin() + public function onRemovePlugin(): RedirectResponse { if ($pluginCode = post('code')) { PluginManager::instance()->deletePlugin($pluginCode); @@ -805,9 +811,8 @@ public function onRemovePlugin() /** * Perform a bulk action on the provided plugins - * @return void */ - public function onBulkAction() + public function onBulkAction(): RedirectResponse { if (($bulkAction = post('action')) && ($checkedIds = post('checked')) && @@ -912,9 +917,8 @@ public function onInstallTheme() /** * Deletes a single theme from the system. - * @return void */ - public function onRemoveTheme() + public function onRemoveTheme(): RedirectResponse { if ($themeCode = post('code')) { ThemeManager::instance()->deleteTheme($themeCode); @@ -929,7 +933,7 @@ public function onRemoveTheme() // Product install // - public function onSearchProducts() + public function onSearchProducts(): array { $searchType = get('search', 'plugins'); $serverUri = $searchType == 'plugins' ? 'plugin/search' : 'theme/search'; @@ -938,7 +942,7 @@ public function onSearchProducts() return $manager->requestServerData($serverUri, ['query' => get('query')]); } - public function onGetPopularPlugins() + public function onGetPopularPlugins(): array { $installed = $this->getInstalledPlugins(); $popular = UpdateManager::instance()->requestPopularProducts('plugin'); @@ -947,7 +951,7 @@ public function onGetPopularPlugins() return ['result' => $popular]; } - public function onGetPopularThemes() + public function onGetPopularThemes(): array { $installed = $this->getInstalledThemes(); $popular = UpdateManager::instance()->requestPopularProducts('theme'); @@ -956,14 +960,14 @@ public function onGetPopularThemes() return ['result' => $popular]; } - protected function getInstalledPlugins() + protected function getInstalledPlugins(): array { $installed = PluginVersion::lists('code'); $manager = UpdateManager::instance(); return $manager->requestProductDetails($installed, 'plugin'); } - protected function getInstalledThemes() + protected function getInstalledThemes(): array { $history = Parameter::get('system::theme.history', []); $manager = UpdateManager::instance(); @@ -983,7 +987,7 @@ protected function getInstalledThemes() /* * Remove installed products from the collection */ - protected function filterPopularProducts($popular, $installed) + protected function filterPopularProducts($popular, $installed): array { $installedArray = []; foreach ($installed as $product) { @@ -1007,7 +1011,7 @@ protected function filterPopularProducts($popular, $installed) /** * Encode HTML safe product code, this is to prevent issues with array_get(). */ - protected function encodeCode($code) + protected function encodeCode(string $code): string { return str_replace('.', ':', $code); } @@ -1015,18 +1019,15 @@ protected function encodeCode($code) /** * Decode HTML safe product code. */ - protected function decodeCode($code) + protected function decodeCode(string $code): string { return str_replace(':', '.', $code); } /** * Adds require plugin codes to the collection based on a result. - * @param array $plugins - * @param array $result - * @return array */ - protected function appendRequiredPlugins(array $plugins, array $result) + protected function appendRequiredPlugins(array $plugins, array $result): array { foreach ((array) array_get($result, 'require') as $plugin) { if ( diff --git a/modules/system/models/PluginVersion.php b/modules/system/models/PluginVersion.php index 14f2155dc8..b862ce2ed8 100644 --- a/modules/system/models/PluginVersion.php +++ b/modules/system/models/PluginVersion.php @@ -1,8 +1,10 @@ - Date: Mon, 29 Jul 2024 22:38:13 -0600 Subject: [PATCH 02/13] Fix tests --- .../system/tests/bootstrap/PluginManagerTestCase.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/modules/system/tests/bootstrap/PluginManagerTestCase.php b/modules/system/tests/bootstrap/PluginManagerTestCase.php index f558705200..f1017b9e43 100644 --- a/modules/system/tests/bootstrap/PluginManagerTestCase.php +++ b/modules/system/tests/bootstrap/PluginManagerTestCase.php @@ -2,10 +2,13 @@ namespace System\Tests\Bootstrap; +use Illuminate\Console\OutputStyle; +use ReflectionClass; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\BufferedOutput; +use System\Classes\PluginManager; use System\Classes\UpdateManager; use System\Classes\VersionManager; -use System\Classes\PluginManager; -use ReflectionClass; use Winter\Storm\Database\Model as ActiveRecord; @@ -51,7 +54,10 @@ public function setUp() : void // Forces plugin migrations to be run again on every test VersionManager::forgetInstance(); - $this->output = new \Symfony\Component\Console\Output\BufferedOutput(); + $this->output = new OutputStyle( + new ArrayInput([]), + new BufferedOutput() + ); parent::setUp(); From 00e085f00c7e53e0d86900e9bc534cec9e5e3aad Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Mon, 29 Jul 2024 22:39:33 -0600 Subject: [PATCH 03/13] add missing newline --- modules/system/tests/bootstrap/PluginManagerTestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system/tests/bootstrap/PluginManagerTestCase.php b/modules/system/tests/bootstrap/PluginManagerTestCase.php index f1017b9e43..5628c661f8 100644 --- a/modules/system/tests/bootstrap/PluginManagerTestCase.php +++ b/modules/system/tests/bootstrap/PluginManagerTestCase.php @@ -120,4 +120,4 @@ protected function flushModelEventListeners() ActiveRecord::flushEventListeners(); } -} \ No newline at end of file +} From 79e1a8ef7e2017bd17c6938d3138d58b257f3ca7 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Tue, 30 Jul 2024 00:21:14 -0600 Subject: [PATCH 04/13] Initial implementation of the UI to upload plugins via zip folder --- modules/system/controllers/Updates.php | 80 +++++++++++++++++++ .../controllers/updates/_install_plugins.php | 47 +++++++---- .../updates/_popup_upload_plugin.php | 18 +++++ .../updates/form.plugin_upload.yaml | 6 ++ modules/system/lang/en/lang.php | 5 ++ 5 files changed, 140 insertions(+), 16 deletions(-) create mode 100644 modules/system/controllers/updates/_popup_upload_plugin.php create mode 100644 modules/system/controllers/updates/form.plugin_upload.yaml diff --git a/modules/system/controllers/Updates.php b/modules/system/controllers/Updates.php index bd79ce0265..fbb0eeba49 100644 --- a/modules/system/controllers/Updates.php +++ b/modules/system/controllers/Updates.php @@ -117,6 +117,7 @@ public function install($tab = null): ?HttpResponse $this->vars['activeTab'] = $tab ?: 'plugins'; $this->vars['installedPlugins'] = $this->getInstalledPlugins(); $this->vars['installedThemes'] = $this->getInstalledThemes(); + $this->vars['packageUploadWidget'] = $this->getPackageUploadWidget($tab === 'themes' ? 'theme' : 'plugin'); } catch (Exception $ex) { $this->handleError($ex); @@ -749,6 +750,85 @@ public function onLoadChangelog(): string // Plugin management // + protected ?Form $packageUploadWidget = null; + + /** + * Get the form widget for the import popup. + */ + protected function getPackageUploadWidget(string $type = 'plugin'): Form + { + $type = post('type', $type); + + if (!in_array($type, ['plugin', 'theme'])) { + throw new ApplicationException('Invalid package type'); + } + + if ($this->packageUploadWidget !== null) { + return $this->packageUploadWidget; + } + + $config = $this->makeConfig("form.{$type}_upload.yaml"); + $config->model = new class extends Model { + public $attachOne = [ + 'uploaded_package' => [\System\Models\File::class, 'public' => false], + ]; + }; + $widget = $this->makeWidget(Form::class, $config); + $widget->bindToController(); + + return $this->packageUploadWidget = $widget; + } + + /** + * Displays the plugin uploader form + */ + public function onLoadPluginUploader(): string + { + $this->vars['packageUploadWidget'] = $this->getPackageUploadWidget('plugin'); + return $this->makePartial('popup_upload_plugin'); + } + + /** + * Installs an uploaded plugin + */ + public function onInstallUploadedPlugin(): string + { + try { + $manager = UpdateManager::instance(); + $result = $manager->installUploadedPlugin(); + + if (!isset($result['code']) || !isset($result['hash'])) { + throw new ApplicationException(Lang::get('system::lang.server.response_invalid')); + } + + $name = $result['code']; + $hash = $result['hash']; + $plugins = [$name => $hash]; + $plugins = $this->appendRequiredPlugins($plugins, $result); + + /* + * Update steps + */ + $updateSteps = $this->buildUpdateSteps(null, $plugins, [], true); + + /* + * Finish up + */ + $updateSteps[] = [ + 'code' => 'completeInstall', + 'label' => Lang::get('system::lang.install.install_completing'), + ]; + + $this->vars['updateSteps'] = $updateSteps; + + return $this->makePartial('execute'); + } + catch (Exception $ex) { + $this->handleError($ex); + return $this->makePartial('plugin_uploader'); + } + } + /** * Validate the plugin code and execute the plugin installation * diff --git a/modules/system/controllers/updates/_install_plugins.php b/modules/system/controllers/updates/_install_plugins.php index 461ff78d3f..427f1ff002 100644 --- a/modules/system/controllers/updates/_install_plugins.php +++ b/modules/system/controllers/updates/_install_plugins.php @@ -1,23 +1,38 @@
-
-