getModuleManager()->loadModule('setup'); } public function indexAction(): void { $mm = MigrationManager::instance(); $this->getTabs()->extend(new OutputFormat(['csv'])); $this->addTitleTab($this->translate('Migrations')); $canApply = $this->hasPermission('application/migrations'); if (! $canApply) { $this->addControl( new HtmlElement( 'div', Attributes::create(['class' => 'migration-state-banner']), new HtmlElement( 'span', null, Text::create( $this->translate('You do not have the required permission to apply pending migrations.') ) ) ) ); } $migrateListForm = new MigrationForm(); $migrateListForm->setAttribute('id', $this->getRequest()->protectId('migration-form')); $migrateListForm->setRenderDatabaseUserChange(! $mm->validateDatabasePrivileges()); if ($canApply && $mm->hasPendingMigrations()) { $migrateAllButton = new SubmitButtonElement(sprintf('migrate-%s', DbMigrationHook::ALL_MIGRATIONS), [ 'form' => $migrateListForm->getAttribute('id')->getValue(), 'label' => $this->translate('Migrate All'), 'title' => $this->translate('Migrate all pending migrations') ]); // Is the first button, so will be cloned and that the visible // button is outside the form doesn't matter for Web's JS $migrateListForm->registerElement($migrateAllButton); // Make sure it looks familiar, even if not inside a form $migrateAllButton->setWrapper(new HtmlElement('div', Attributes::create(['class' => 'icinga-controls']))); $this->controls->getAttributes()->add('class', 'default-layout'); $this->addControl($migrateAllButton); } $this->handleFormatRequest($mm->toArray()); $frameworkList = new MigrationList($mm->yieldMigrations(), $migrateListForm); $frameworkListControl = new HtmlElement('div', Attributes::create(['class' => 'migration-list-control'])); $frameworkListControl->addHtml(new HtmlElement('h2', null, Text::create($this->translate('System')))); $frameworkListControl->addHtml($frameworkList); $moduleList = new MigrationList($mm->yieldMigrations(true), $migrateListForm); $moduleListControl = new HtmlElement('div', Attributes::create(['class' => 'migration-list-control'])); $moduleListControl->addHtml(new HtmlElement('h2', null, Text::create($this->translate('Modules')))); $moduleListControl->addHtml($moduleList); $migrateListForm->addHtml($frameworkListControl, $moduleListControl); if ($canApply && $mm->hasPendingMigrations()) { $frameworkList->ensureAssembled(); $moduleList->ensureAssembled(); $this->handleMigrateRequest($migrateListForm); } $migrations = new HtmlElement('div', Attributes::create(['class' => 'migrations'])); $migrations->addHtml($migrateListForm); $this->addContent($migrations); } public function hintAction(): void { // The forwarded request doesn't modify the original server query string, but adds the migration param to the // request param instead. So, there is no way to access the migration param other than via the request instance. /** @var ?string $module */ $module = $this->getRequest()->getParam(DbMigrationHook::MIGRATION_PARAM); if ($module === null) { throw new MissingParameterException( $this->translate('Required parameter \'%s\' missing'), DbMigrationHook::MIGRATION_PARAM ); } $mm = MigrationManager::instance(); if (! $mm->hasMigrations($module)) { $this->httpNotFound(sprintf('There are no pending migrations matching the given name: %s', $module)); } $migration = $mm->getMigration($module); $this->addTitleTab($this->translate('Error')); $this->addContent( new HtmlElement( 'div', Attributes::create(['class' => 'pending-migrations-hint']), new HtmlElement('h2', null, Text::create($this->translate('Error!'))), new HtmlElement( 'p', null, Text::create(sprintf($this->translate('%s has pending migrations.'), $migration->getName())) ), new HtmlElement('p', null, Text::create($this->translate('Please apply the migrations first.'))), new ActionLink($this->translate('View pending Migrations'), 'migrations') ) ); } public function migrationAction(): void { /** @var string $name */ $name = $this->params->getRequired(DbMigrationHook::MIGRATION_PARAM); $this->addTitleTab($this->translate('Migration')); $this->getTabs()->disableLegacyExtensions(); $this->controls->getAttributes()->add('class', 'default-layout'); $mm = MigrationManager::instance(); if (! $mm->hasMigrations($name)) { $migrations = []; } else { $hook = $mm->getMigration($name); $migrations = array_reverse($hook->getMigrations()); if (! $this->hasPermission('application/migrations')) { $this->addControl( new HtmlElement( 'div', Attributes::create(['class' => 'migration-state-banner']), new HtmlElement( 'span', null, Text::create( $this->translate('You do not have the required permission to apply pending migrations.') ) ) ) ); } else { $this->addControl( new HtmlElement( 'div', Attributes::create(['class' => 'migration-controls']), new HtmlElement('span', null, Text::create($hook->getName())) ) ); } } $migrationWidget = new HtmlElement('div', Attributes::create(['class' => 'migrations'])); $migrationWidget->addHtml((new MigrationList($migrations))->setMinimal(false)); $this->addContent($migrationWidget); } public function handleMigrateRequest(MigrationForm $form): void { $this->assertPermission('application/migrations'); $form->on(MigrationForm::ON_SUCCESS, function (MigrationForm $form) { $mm = MigrationManager::instance(); /** @var array $elevatedPrivileges */ $elevatedPrivileges = $form->getValue('database_setup'); if ($elevatedPrivileges !== null && $elevatedPrivileges['grant_privileges'] === 'y') { $mm->fixIcingaWebMysqlGrants($this->getDb(), $elevatedPrivileges); } $pressedButton = $form->getPressedSubmitElement(); if ($pressedButton) { $name = substr($pressedButton->getName(), 8); switch ($name) { case DbMigrationHook::ALL_MIGRATIONS: if ($mm->applyAll($elevatedPrivileges)) { Notification::success($this->translate('Applied all migrations successfully')); } else { Notification::error( $this->translate( 'Applied migrations successfully. Though, one or more migration hooks' . ' failed to run. See logs for details' ) ); } break; default: $migration = $mm->getMigration($name); if ($mm->apply($migration, $elevatedPrivileges)) { Notification::success($this->translate('Applied pending migrations successfully')); } else { Notification::error( $this->translate('Failed to apply pending migration(s). See logs for details') ); } } } $this->sendExtraUpdates(['#col2' => '__CLOSE__']); $this->redirectNow('migrations'); })->handleRequest($this->getServerRequest()); } /** * Handle exports * * @param array $data */ protected function handleFormatRequest(array $data): void { $formatJson = $this->params->get('format') === 'json'; if (! $formatJson && ! $this->getRequest()->isApiRequest()) { return; } $this->getResponse() ->json() ->setSuccessData($data) ->sendResponse(); } }