Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CommonLogic/AppInstallers/MetaModelInstaller.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,12 @@ public function backup(string $destinationAbsolutePath) : \Iterator
yield $idt . 'Updated composer.json for "' . $app->getAliasWithNamespace() . '".' . PHP_EOL;

// Backup pages.
$tempFolderPages = $this->getTempFolderPath($this->getDataFolderPathAbsolute($destinationAbsolutePath)) .
DIRECTORY_SEPARATOR . self::FOLDER_NAME_PAGES;

$pageInstaller = $this->getPageInstaller();
$pageInstaller->setOutputIndentation($idt);
$pageInstaller->setTempPath($tempFolderPages);
yield from $pageInstaller->backup($destinationAbsolutePath);

// Verify permalinks.
Expand Down
195 changes: 168 additions & 27 deletions CommonLogic/AppInstallers/PageInstaller.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@
use exface\Core\CommonLogic\Selectors\UiPageSelector;
use exface\Core\Interfaces\Selectors\UiPageSelectorInterface;
use exface\Core\Interfaces\Log\LoggerInterface;
use exface\Core\Interfaces\WorkbenchInterface;

/**
* Saves pages as UXON (JSON) files and imports these files back into model when needed.
*
* Each page is stored as a separate JSON file using it's `UiPageInterface::exportUxonObject()`.
* Each page is stored as a separate JSON file using its `UiPageInterface::exportUxonObject()`.
* All pages are stored in a single folder. The path to that folder can be passed to the
* installer's constructor.
*
Expand All @@ -46,6 +47,8 @@ class PageInstaller extends AbstractAppInstaller

private $path = null;

private $tmpPath = null;

private $rootSelector = null;

/**
Expand Down Expand Up @@ -114,35 +117,15 @@ public function install(string $source_absolute_path) : \Iterator
}
}

// Find pages files.
$files = glob($dir . DIRECTORY_SEPARATOR . '*.json');
$files = $this->loadPageFiles($dir);
if ($files === false) {
$workbench->getLogger()->logException((new InstallerRuntimeError($this, 'Error reading folder "' . $dir . DIRECTORY_SEPARATOR . '*.json"! - no pages were installed!')));
}
// Make sure, the array only contains existing files.
$files = array_filter($files, 'is_file');

// Load pages. If anything goes wrong, the installer should not continue to avoid broken menu
// structures etc., so don't silence any exceptions here.
foreach ($files as $file) {
try {
$page = UiPageFactory::createFromUxon($workbench, UxonObject::fromJson(file_get_contents($file)));
$page->setApp($this->getApp()->getSelector());
// Wird eine Seite neu hinzugefuegt ist die menuDefaultPosition gleich der
// gesetzen Position.
$page->setParentPageSelectorDefault($page->getParentPageSelector());
$page->setMenuIndexDefault($page->getMenuIndex());

// Pages marked as top-level explicitly, get this installations root page as parent
if ($page->isMenuHome()) {
$page->setParentPageSelector($this->getServerRootSelector());
$page->setParentPageSelectorDefault($this->getServerRootSelector());
}

$pagesFile[] = $page;
} catch (\Throwable $e) {
throw new InstallerRuntimeError($this, 'Cannot load page model from file "' . $file . '": corrupted UXON?', null, $e);
}
$pagesFile[] = $this->getPageFromFile($workbench, $file);
}
$pagesFile = $this->sortPages($pagesFile);

Expand Down Expand Up @@ -297,6 +280,53 @@ public function install(string $source_absolute_path) : \Iterator
yield $idt.$idt . 'No changes found' . PHP_EOL;
}
}

/**
* Loads stored page data from files and returns all file objects loaded this way.
*
* NOTE: The returned elements still need to be instantiated.
*
* @param string $pathAbsolute
* @return array|bool
*/
protected function loadPageFiles(string $pathAbsolute) : array|bool
{
// Find pages files.
$files = glob($pathAbsolute . DIRECTORY_SEPARATOR . '*.json');
if($files === false) {
return false;
}
// Make sure, the array only contains existing files.
return array_filter($files, 'is_file');
}

/**
* Instantiated a UiPage from a given file.
*
* @param WorkbenchInterface $workbench
* @param string $file
* @return UiPageInterface
*/
protected function getPageFromFile(WorkbenchInterface $workbench, string $file) : UiPageInterface
{
try {
$page = UiPageFactory::createFromUxon($workbench, UxonObject::fromJson(file_get_contents($file)));
$page->setApp($this->getApp()->getSelector());
$page->setParentPageSelectorDefault($page->getParentPageSelector());
// New Pages have menuDefaultPosition set to their current position.
$page->setMenuIndexDefault($page->getMenuIndex());

// Pages marked as top-level explicitly, get current installations root page as parent
if ($page->isMenuHome()) {
$page->setParentPageSelector($this->getServerRootSelector());
$page->setParentPageSelectorDefault($this->getServerRootSelector());
}
} catch (\Throwable $e) {
throw new InstallerRuntimeError($this, 'Cannot load page model from file "' . $file . '": corrupted UXON?', null, $e);
}

return $page;
}

/**
*
Expand Down Expand Up @@ -446,10 +476,11 @@ public function backup(string $destination_absolute_path) : \Iterator
$exportWb = Workbench::startNewInstance($exportConfig);
$exportWb->getContext()->getScopeSession()->setSessionDisabled(true);
$exportApp = $exportWb->getApp($this->getApp()->getSelector());
// Dann alle Dialoge der App als Dateien in den Ordner schreiben.

// Load page data for app from model.
$pages = $this->getPagesForApp($exportApp);

$pages = $this->diffPages($exportWb, $pages);

if (! empty($pages)) {
$fileManager->pathConstruct($path);
}
Expand All @@ -474,6 +505,95 @@ public function backup(string $destination_absolute_path) : \Iterator
yield $idt . 'Exported ' . count($pages) . ' pages successfully.' . PHP_EOL;
}

/**
* Compares a set pages fore changes compared to any pages stored in the temp folder.
*
* Pairs are identified via UID and then compared column by column.
* TODO: At the moment, pages instantiated from database are formatted differently than those from files.
*
* @param WorkbenchInterface $workbench
* @param array $pages
* @return array
*/
protected function diffPages(WorkbenchInterface $workbench, array $pages) : array
{
$tempPath = $this->getTempPath();
if($tempPath === null) {
return $pages;
}

try {
// Load page data from files.
$previousPages = [];
foreach ($this->loadPageFiles($tempPath) as $pageFile) {
$previousPages[] = $this->getPageFromFile($workbench, $pageFile);
}

if(empty($previousPages)) {
return $pages;
}

// Generate data sheet for easy comparison.
$pagesDs = $this->createPageDataSheet();
$compDs = $this->createPageDataSheet();

$changedPages = [];
foreach ($pages as $currPage) {
$uid = $currPage->getUid();

$compPage = null;
foreach ($previousPages as $i => $prevPage) {
if($uid === $prevPage->getUid()) {
$compPage = $prevPage;
unset($previousPages[$i]);
break;
}
}

if($compPage === null) {
$changedPages[] = $currPage;
continue;
}

// Remove white spaces from content to improve comparison accuracy.
$currPage->setContents(preg_replace('/\s/', '', $currPage->getContents()));
$compPage->setContents(preg_replace('/\s/', '', $compPage->getContents()));

// Diff the loaded and exported pages.
$pagesDs->removeRows();
$compDs->removeRows();
$currPage->exportDataRow($pagesDs);
$compPage->exportDataRow($compDs);
$diffs = $pagesDs->getRowsDiffWithInfo(
$compDs,
[
'CREATED_BY_USER',
'CREATED_ON',
'MODIFIED_BY_USER',
'MODIFIED_ON',
'TEMPLATE'
]
);

if(!empty($diffs['rows'])) {
$changedPages[] = $currPage;
}
}

$pages = $changedPages;
} catch (\Throwable $e) {
// If something goes wrong here, we ignore it, because it does not directly affect the export.
$this->getWorkbench()->getLogger()->logException(new InstallerRuntimeError(
$this,
'Cannot check pages for changes: ' . $e->getMessage(),
null,
$e
));
}

return $pages;
}

/**
*
* {@inheritDoc}
Expand Down Expand Up @@ -597,12 +717,33 @@ private function getPagesPathOld(string $source_absolute_path) : string
}
return $source_absolute_path . DIRECTORY_SEPARATOR . 'Install' . DIRECTORY_SEPARATOR . 'Pages' . DIRECTORY_SEPARATOR . $languageCode;
}


/**
* @return UiPageSelectorInterface
*/
protected function getServerRootSelector() : UiPageSelectorInterface
{
if ($this->rootSelector === null) {
$this->rootSelector = UiPageSelector::getServerRootSelector($this->getWorkbench());
}
return $this->rootSelector;
}

/**
* @return string|null
*/
public function getTempPath() : ?string
{
return $this->tmpPath;
}

/**
* @param string $tmpPath
* @return $this
*/
public function setTempPath(string $tmpPath) : PageInstaller
{
$this->tmpPath = $tmpPath;
return $this;
}
}
60 changes: 60 additions & 0 deletions CommonLogic/DataSheets/DataSheet.php
Original file line number Diff line number Diff line change
Expand Up @@ -3557,6 +3557,66 @@ public function getRowsDiff(DataSheetInterface $otherSheet, array $exclude = [])
}
return $diffRows;
}

/**
* Like `getRowsDiff()`, but with additional info on the diffed rows.
*
* @param DataSheetInterface $otherSheet
* @param array $exclude
* @return array
*/
public function getRowsDiffWithInfo(DataSheetInterface $otherSheet, array $exclude = []) : array
{
$diffRows = [];
$diffIdxs = [];

$excludeColumns = [];
foreach ($exclude as $excl) {
switch (true) {
case $excl instanceof MetaAttributeInterface:
if ($col = $this->getColumns()->getByAttribute($excl)) {
$excludeColumns[] = $col;
}
break;
case $excl instanceof DataColumnInterface:
$excludeColumns[] = $excl;
break;
default:
if ($col = $this->getColumns()->getByExpression($excl)) {
$excludeColumns[] = $col;
}
break;
}
}

$cols = [];
foreach ($this->getColumns() as $thisCol) {
if (in_array($thisCol, $excludeColumns)) {
continue;
}
if ($otherCol = $otherSheet->getColumns()->get($thisCol->getName())) {
$idxs = array_keys($thisCol->diffRows($otherCol));
$diffIdxs = array_merge($diffIdxs, $idxs);

if(!empty($idxs)) {
$cols[] = $thisCol->getAttributeAlias();
}
} else {
$diffIdxs = array_merge($diffIdxs, array_keys($thisCol->getValues(false)));
}
}

$diffIdxs = array_unique($diffIdxs);
sort($diffIdxs);
foreach ($diffIdxs as $i) {
$diffRows[$i] = $this->getRow($i);
}

return [
'cols' => $cols,
'rows' => $diffRows
];
}

/**
*
Expand Down
8 changes: 7 additions & 1 deletion CommonLogic/Model/UiPage.php
Original file line number Diff line number Diff line change
Expand Up @@ -1398,7 +1398,13 @@ protected function setReplacesPageAlias($aliasWithNamespace) : UiPageInterface
}
return $this->setReplacesPageSelector($aliasWithNamespace);
}


/**
* Exports this page as a data row to be stored in the model.
*
* @param DataSheetInterface $dataSheet
* @return DataSheetInterface
*/
public function exportDataRow(DataSheetInterface $dataSheet) : DataSheetInterface
{
$createdBySelector = $this->getCreatedByUserSelector();
Expand Down