Erase(); Session::Unset('aDeletionExecutionSummary'); Session::Set('bForceCompilation', true); $oParameters->SetParameter('return_application', 'DataFeatureRemoval'); $this->AddAnalyzeParams(); $aParams['sTransactionId'] = utils::GetNewTransactionId(); $aParams['iColumnCount'] = $this->iColumnCount; $aParams['aAvailableExtensions'] = $this->SplitArrayIntoColumns($this->GetAvailableExtensions(), $this->iColumnCount); $aParams['aAnalysisDataTable'] = $this->aAnalysisDataTable; $aParams['aClasses'] = array_keys($this->aCountClassesToCleanup); $aParams['DataFeatureRemovalErrorMessage'] = $sErrorMessage; $aParams['bHasData'] = $this->iCount > 0; $aParams['sSetupUrl'] = utils::GetAbsoluteUrlAppRoot().'setup'; $aParams['iCount'] = $this->iCount; $this->AddLinkedStylesheet(utils::GetAbsoluteUrlModulesRoot().DataFeatureRemovalHelper::MODULE_NAME.'/assets/css/DataFeatureRemoval.css'); $this->AddLinkedScript(utils::GetAbsoluteUrlModulesRoot().DataFeatureRemovalHelper::MODULE_NAME.'/assets/js/DataFeatureRemoval.js'); $this->DisplayPage($aParams); } public function AddAnalyzeParams(): void { $aData = []; $aColumns = []; $this->iCount = 0; foreach ($this->aCountClassesToCleanup as $sClass => $iCount) { $sModuleName = MetaModel::GetModuleName($sClass); $aExtensions = DataFeatureRemoverExtensionService::GetInstance()->GetIncludingExtensions($sModuleName); $sExtensions = implode(' ', $aExtensions); $aColumns = ['ClassName','FeatureName','Module','Occurrence']; $aData[] = [$sClass,$sExtensions,$sModuleName,$iCount]; $this->iCount += $iCount; } $this->aAnalysisDataTable = $this->GetTableData('Analysis', $aColumns, $aData); } public function OperationAnalysisResult(): void { $aParams = []; //from setup wizard/mtp if (!SetupUtils::IsSessionSetupTokenValid()) { //from same module $this->ValidateTransactionId(); } // Display changed extensions $aSetupParameterNames = [ 'selected_extensions' => '[]', 'selected_modules' => '[]', 'display_choices' => '', 'added_extensions' => '[]', 'removed_extensions' => '[]', 'extensions_not_uninstallable' => '[]', 'copy_setup_files' => 1, 'force-uninstall' => '', 'use_symbolic_links' => MFCompiler::UseSymbolicLinks() ? 'on' : '', 'return_application' => '', 'target_env' => ITOP_DEFAULT_ENV, ]; $oParameters = new SessionParameters(SetupUtils::SESSION_PARAMETERS_NAME); foreach ($aSetupParameterNames as $sInputName => $defaultValue) { $oParameters->SetParameter($sInputName, $oParameters->GetParameter($sInputName, $defaultValue)); } $aAddedExtensions = json_decode($oParameters->GetParameter('added_extensions', '[]'), true); $aRemovedExtensions = json_decode($oParameters->GetParameter('removed_extensions', '[]'), true); if ('[]' === $oParameters->GetParameter('selected_modules', '[]')) { //it does not come from setup // we get extensions from 1st screen uiblocks $this->ReadExtensionsDiff(); $oParameters->SetParameter('force-uninstall', $this->bForcedUninstallation ? 'on' : ''); $aAddedExtensions = $this->aExtensionsToCheck['to_be_installed']; $oParameters->SetParameter('added_extensions', $this->ConvertIntoSetupFormat($aAddedExtensions)); $aRemovedExtensions = $this->aExtensionsToCheck['to_be_removed']; $oParameters->SetParameter('removed_extensions', $this->ConvertIntoSetupFormat($aRemovedExtensions)); } DataFeatureRemovalLog::Debug(__METHOD__.' Extensions given in parameter', null, [ 'added_extensions' => $aAddedExtensions, 'removed_extensions' => $aRemovedExtensions]); $aParams['sTransactionId'] = utils::GetNewTransactionId(); $aParams['iColumnCount'] = $this->iColumnCount; $aAvailableExtensions = $this->GetExtensionsDiff($aAddedExtensions, $aRemovedExtensions); $aParams['aAvailableExtensionsCount'] = count($aAvailableExtensions); $aParams['aAvailableExtensions'] = $this->SplitArrayIntoColumns($aAvailableExtensions, $this->iColumnCount); $aParams['sAjaxURL'] = utils::GetAbsoluteUrlModulePage(DataFeatureRemovalHelper::MODULE_NAME, 'index.php'); $this->DisplayPage($aParams, 'AnalysisResult'); } private function ConvertIntoSetupFormat(array $aData): string { $aNewData = []; foreach ($aData as $sCode => $sLabel) { $aNewData[] = sprintf('"%s":"%s"', $sCode, $sLabel); } return "{".implode(',', $aNewData)."}"; } /** * @return void * @throws \ConfigException * @throws \CoreException */ public function OperationAjaxCompile(): void { $aParams = []; //to make setup redirection work, we need to pass complex data structures to setup wizards (ie extension/module lists) $sSourceEnv = MetaModel::GetEnvironment(); $oRuntimeEnvironment = new RunTimeEnvironment($sSourceEnv, false); $oParameters = new SessionParameters(SetupUtils::SESSION_PARAMETERS_NAME); $aSelectedModules = json_decode($oParameters->GetParameter('selected_modules', '[]'), true); $aSelectedExtensions = json_decode($oParameters->GetParameter('selected_extensions', '[]'), true); $aAddedExtensions = json_decode($oParameters->GetParameter('added_extensions', '[]'), true); $aRemovedExtensions = json_decode($oParameters->GetParameter('removed_extensions', '[]'), true); try { $oConfig = MetaModel::GetConfig(); if (count($aSelectedModules) === 0) { $aSelectedExtensions = DataFeatureRemoverExtensionService::GetInstance()->GetExtensionMap()->GetSelectedExtensions($oConfig, array_keys($aAddedExtensions), array_keys($aRemovedExtensions)); $oParameters->SetParameter('selected_extensions', $this->ConvertIntoSetupFormat($aSelectedExtensions)); } $sBuildDir = APPROOT."/env-$sSourceEnv-build"; if (! is_dir($sBuildDir)) { SetupUtils::builddir($sBuildDir); } $bIsDirEmpty = count(scandir($sBuildDir)) === 2; $bForceCompilation = Session::Get('bForceCompilation', false); if ($bIsDirEmpty || $bForceCompilation) { Session::Unset('bForceCompilation'); $oRuntimeEnvironment->CopySetupFiles(); if (count($aSelectedModules) === 0) { $aSelectedModules = $oRuntimeEnvironment->GetModulesToLoadFromChoices($oConfig, $aSelectedExtensions); } DataFeatureRemovalLog::Debug( __METHOD__, null, ['sSourceEnv' => $sSourceEnv, 'sBuildDir' => $sBuildDir, 'bIsDirEmpty' => $bIsDirEmpty, glob("$sBuildDir/*")] ); $oRuntimeEnvironment->DoCompile($aSelectedExtensions, $aRemovedExtensions, $aSelectedModules, MFCompiler::CanUseSymbolicLinks()); } else { if (count($aSelectedModules) === 0) { $aSelectedModules = $oRuntimeEnvironment->GetModulesToLoadFromChoices($oConfig, $aSelectedExtensions); } } } catch (CoreException $e) { $aParams['error_message'] = $e->getHtmlDesc(); } catch (Exception $e) { $aParams['error_message'] = $e->getMessage(); } $aParams['success_message'] = Dict::S('DataFeatureRemoval:Compile:Success'); $oParameters->SetParameter('selected_modules', json_encode($aSelectedModules)); $this->DisplayJSONPage($aParams); } public function OperationAjaxRunAudit(): void { $oParameters = new SessionParameters(SetupUtils::SESSION_PARAMETERS_NAME); $aParams = []; $aPageParams = []; try { $sSourceEnv = MetaModel::GetEnvironment(); $oSetupAudit = new SetupAudit($sSourceEnv); $aRemovedClasses = array_keys($oSetupAudit->RunDataAudit()); $oParameters->SetParameter('classes', $aRemovedClasses); new ContextTag(ContextTag::TAG_SETUP); $aPageParams['sLaunchSetupUrl'] = utils::GetAbsoluteUrlAppRoot().'setup/wizard.php'; $aPageParams['aSetupParams'] = [ "_class" => "WizStepLandingBeforeAudit", "operation" => "next", ]; $aPageParams['sTransactionId'] = utils::GetNewTransactionId(); $aDeletionPlanSummaryEntities = $this->GetDeletionPlanSummaryEntities($aRemovedClasses); [$aPageParams['aDeletionPlanSummary'], $aPageParams['iQueryCount'], $aPageParams['bDeletionPossible']] = $this->GetDeletionPlanSummaryTable($aDeletionPlanSummaryEntities); [$aPageParams['aDeletionExecutionSummary'], $aPageParams['bHasDeletionExecution']] = $this->GetExecutionSummaryTable(); $aPageParams['bDeletionNeeded'] = ($aPageParams['iQueryCount'] > 0); if (!$aPageParams['bDeletionNeeded']) { // Erase session setup parameters SetupUtils::CreateSetupToken(); } $this->DisplayAjaxPage($aPageParams, 'AjaxRunAudit'); return; } catch (CoreException $e) { $aParams['error_message'] = $e->getHtmlDesc(); } catch (Exception $e) { $aParams['error_message'] = $e->getMessage(); } $this->DisplayJSONPage($aParams); } private function GetExecutionSummaryTable(): array { $sName = 'ExecutionSummary'; $this->aDeletionExecutionSummary = unserialize(Session::Get('aDeletionExecutionSummary') ?? serialize([])); $aTableData = []; if (count($this->aDeletionExecutionSummary) === 0) { return [$aTableData, false]; } $aColumns = ['Class', 'Total Deleted Count' , 'Total Updated Count', 'Deleted Count' , 'Updated Count']; $aRows = []; /** @var DataCleanupSummaryEntity $oSummary */ foreach ($this->aDeletionExecutionSummary as $sClass => $oSummary) { $aRows[] = [ $sClass, $oSummary->iTotalDeleteCount, $oSummary->iTotalUpdateCount, $oSummary->iDeleteCount, $oSummary->iUpdateCount, ]; } $aTableData = $this->GetTableData($sName, $aColumns, $aRows); return [$aTableData, true]; } private function GetDeletionPlanSummaryEntities(array $aRemovedClasses): array { $oDataCleanupService = new StaticDeletionPlan(); return $oDataCleanupService->GetCleanupSummary($aRemovedClasses); } private function GetDeletionPlanSummaryTable(array $aDeletionPlanSummaryEntities): array { $sName = 'DeletionPlanSummary'; $aColumns = ['Class', 'Delete Count' , 'Update Count', 'Issue Count']; $aRows = []; $iQueryCount = 0; $bHasIssues = false; foreach ($aDeletionPlanSummaryEntities as $oDeletionPlanSummaryEntity) { $aRows[] = [ $oDeletionPlanSummaryEntity->sClass, $oDeletionPlanSummaryEntity->iDeleteCount, $oDeletionPlanSummaryEntity->iUpdateCount, $oDeletionPlanSummaryEntity->iIssueCount, ]; $bHasIssues |= ($oDeletionPlanSummaryEntity->iIssueCount !== 0); $iQueryCount += $oDeletionPlanSummaryEntity->iDeleteCount; $iQueryCount += $oDeletionPlanSummaryEntity->iUpdateCount; } return [$this->GetTableData($sName, $aColumns, $aRows), $iQueryCount, !$bHasIssues]; } public function OperationDoDeletion(): void { $this->ValidateTransactionId(); $this->aDeletionExecutionSummary = unserialize(Session::Get('aDeletionExecutionSummary') ?? serialize([])); Session::Unset('aDeletionExecutionSummary'); $oParameters = new SessionParameters(SetupUtils::SESSION_PARAMETERS_NAME); $aClasses = $oParameters->GetParameter('classes', []); $oDataCleanupService = new DataCleanupService(); $aDeletionExecutionSummary = $oDataCleanupService->ExecuteCleanup($aClasses); foreach ($aDeletionExecutionSummary as $sClass => $oExecutionSummary) { if (!array_key_exists($sClass, $this->aDeletionExecutionSummary)) { $this->aDeletionExecutionSummary[$sClass] = new DataCleanupSummaryEntity($sClass); } $oSummary = $this->aDeletionExecutionSummary[$sClass]; $oSummary->iDeleteCount = $oExecutionSummary->iDeleteCount; $oSummary->iUpdateCount = $oExecutionSummary->iUpdateCount; $oSummary->iTotalDeleteCount += $oExecutionSummary->iDeleteCount; $oSummary->iTotalUpdateCount += $oExecutionSummary->iUpdateCount; } Session::Set('aDeletionExecutionSummary', serialize($this->aDeletionExecutionSummary)); $this->OperationAnalysisResult(); } private function GetAvailableExtensions(bool $bIncludePackageExtensions = false): array { $aExtensionsData = []; if ($bIncludePackageExtensions) { $aExtensionsRef = DataFeatureRemoverExtensionService::GetInstance()->GetExtensionMap()->GetAllExtensionsWithPreviouslyInstalled(); } else { $aExtensionsRef = DataFeatureRemoverExtensionService::GetInstance()->ReadItopExtensions(); } foreach ($aExtensionsRef as $oExtension) { /** @var \iTopExtension $oExtension */ $aExtensionsData[$oExtension->sCode] = [ 'version' => $oExtension->sVersion, 'label' => $oExtension->sLabel, 'code' => $oExtension->sCode, 'description' => $oExtension->sDescription, 'source' => $oExtension->GetExtensionSourceLabel(), 'installed' => $oExtension->bInstalled, 'extra_flags' => [ 'uninstallable' => $oExtension->CanBeUninstalled(), 'remote' => $oExtension->IsRemote(), 'missing' => $oExtension->bRemovedFromDisk, 'cannot-be-installed' => $oExtension->HasDependencyIssue(), ], ]; } return $aExtensionsData; } private function GetExtensionsDiff(array $aAddedExtensions, array $aRemovedExtensions): array { $aExtensions = []; foreach ($this->GetAvailableExtensions(true) as $sCode => $aExtension) { $aExtension['extra_flags']['disabled'] = true; if (isset($aAddedExtensions[$sCode])) { $aExtension['extra_flags']['selected'] = true; $aExtensions[$sCode] = $aExtension; } elseif (isset($aRemovedExtensions[$sCode])) { $aExtension['extra_flags']['selected'] = false; $aExtensions[$sCode] = $aExtension; } } return $aExtensions; } private function GetTableData(string $sTableName, array $aColumns, array $aData): array { if (empty($aData)) { return [ 'Type' => 'Table', 'Columns' => [['label' => '']], 'Data' => [[ Dict::S('DataFeatureRemoval:Table:Empty')]], ]; } $aNewColumns = []; foreach ($aColumns as $sColumn) { $aNewColumns[] = ['label' => Dict::S("DataFeatureRemoval:Table:$sTableName:$sColumn", Dict::S("DataFeatureRemoval:Column:$sColumn", $sColumn))]; } $aColumns = $aNewColumns; return [ 'Type' => 'Table', 'Columns' => $aColumns, 'Data' => $aData, ]; } /** * @return void * @throws \Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException */ private function ValidateTransactionId(): void { if (empty($_POST)) { return; } $sTransactionId = utils::ReadPostedParam('transaction_id', null, utils::ENUM_SANITIZATION_FILTER_TRANSACTION_ID); DataFeatureRemovalLog::Debug(__FUNCTION__.": Transaction [$sTransactionId]"); if (empty($sTransactionId) || !utils::IsTransactionValid($sTransactionId, false)) { throw new DataFeatureRemovalException(Dict::S("iTopUpdate:Error:InvalidToken")); } } /** * Read extensions selected from posted parameters */ public function ReadExtensionsDiff(): void { if (!is_null($this->aExtensionsToCheck)) { return; } $aAvailableExtensions = $this->GetAvailableExtensions(); $aSelectedExtensionsFromUI = utils::ReadPostedParam('aSelectedExtensions', []); $this->aExtensionsToCheck = [ 'to_be_installed' => [], 'to_be_removed' => [], ]; foreach ($aAvailableExtensions as $sCode => &$aExtensionData) { if (!isset($aSelectedExtensionsFromUI[$sCode])) { continue; } if ($aExtensionData['installed'] && $aSelectedExtensionsFromUI[$sCode] !== 'on') { $aExtensionData['extra_flags']['selected'] = false; $sLabel = $aAvailableExtensions[$sCode]['label']; $this->aExtensionsToCheck['to_be_removed'][$sCode] = $sLabel; if (! $this->bForcedUninstallation && $aExtensionData['extra_flags']['uninstallable']) { $this->bForcedUninstallation = true; } } elseif (!$aExtensionData['installed'] && $aSelectedExtensionsFromUI[$sCode] === 'on') { $aExtensionData['extra_flags']['selected'] = true; $sLabel = $aAvailableExtensions[$sCode]['label']; $this->aExtensionsToCheck['to_be_installed'][$sCode] = $sLabel; } } } /** * Divide an array into several sub arrays, distributing elements so that every sub array has an equal amount of elements * @param mixed[] $aInput * @param int $iColNumber * * @return array[] */ private function SplitArrayIntoColumns(array $aInput, int $iColNumber) { $aOutput = array_fill(0, $iColNumber, []); $iIndex = 0; foreach ($aInput as $mItem) { //Split extensions in $iColNumber columns $aOutput[$iIndex % $this->iColumnCount][] = $mItem; $iIndex++; } return $aOutput; } }