Skip to content

Instantly share code, notes, and snippets.

@jobee
Created September 17, 2021 09:37
Show Gist options
  • Save jobee/f81717df954c1024f39cc308b422bb6d to your computer and use it in GitHub Desktop.
Save jobee/f81717df954c1024f39cc308b422bb6d to your computer and use it in GitHub Desktop.
Neos CMS - Command controller example to cleanup inline-editable properties
<?php
namespace Your\Package\Command;
use Doctrine\ORM\EntityManagerInterface;
use Neos\ContentRepository\Domain\Model\NodeType;
use Neos\ContentRepository\Domain\Model\Workspace;
use Neos\ContentRepository\Domain\Service\NodeTypeManager;
use Neos\Flow\Cli\CommandController;
use Neos\Flow\Persistence\PersistenceManagerInterface;
use Neos\Flow\Annotations as Flow;
use Neos\Neos\Controller\CreateContentContextTrait;
use Neos\Neos\Domain\Service\ContentDimensionPresetSourceInterface;
use Neos\ContentRepository\Domain\Service\ContentDimensionCombinator;
use Neos\Eel\FlowQuery\FlowQuery;
use Neos\ContentRepository\Domain\Model\NodeInterface;
use Neos\ContentRepository\Domain\Repository\WorkspaceRepository;
/**
* @Flow\Scope("singleton")
*/
class NodeDataCommandController extends CommandController
{
use CreateContentContextTrait;
/**
* Formatting rules get applied top to bottom with preg_replace()
* We also run a general trim() after
*
* @var array
*/
protected $formattingRules = array(
// remove all <span> leftovers
'/<\/span><span>/' => '',
// replace all non-breaking spaces with whitespace
'/&nbsp;/' => ' ',
// replace multiple whitespaces with a single one
'/\s+/' => ' ',
// trim whitespaces within <p> tags
'/<p>\s/' => '<p>',
'/\s<\/p>/' => '</p>',
);
/**
* @Flow\Inject
* @var NodeTypeManager
*/
protected $nodeTypeManager;
/**
* @Flow\Inject
* @var ContentDimensionPresetSourceInterface
*/
protected $contentDimensionPresetSource;
/**
* @Flow\Inject
* @var ContentDimensionCombinator
*/
protected $contentDimensionCombinator;
/**
* @Flow\Inject
* @var WorkspaceRepository
*/
protected $workspaceRepository;
/**
* @Flow\Inject
* @var PersistenceManagerInterface
*/
protected $persistenceManager;
/**
* @Flow\Inject
* @var EntityManagerInterface
*/
protected $entityManager;
/**
* Apply formatting rules on all text properties
*
* @param boolean $verbose
* @param boolean $dryRun
* @param string $nodeType
* @return void
*/
public function cleanupInlineEditablePropertiesCommand($verbose = false, $dryRun = false, $nodeTypeName = null)
{
if ($dryRun)
$this->output->outputLine('Dry run enabled!');
else {
$question = $this->output->ask('<comment>Do you really want to apply all formatting rules? - 1.) run --dry-run before - 2.) Make sure you have a database backup! (y/N)</comment>', 'n');
if ($question == 'n')
exit;
}
$this->output->outputLine('<comment>Apply formatting rules on all inline-editable properties...</comment>');
$countTotalUpdatedNodes = 0;
foreach ($this->nodeTypeManager->getNodeTypes() as $nodeType) {
/** @var NodeType $nodeType */
if ($nodeTypeName && $nodeType->getName() !== $nodeTypeName)
continue;
if ($nodeType->isAbstract())
continue;
if (!($nodeType->isOfType('Neos.Neos:Content') || $nodeType->isOfType('Neos.Neos:Document')))
continue;
$properties = array_keys($nodeType->getProperties());
$properties = array_filter($properties, function ($propertyName) use ($nodeType) {
return $nodeType->getConfiguration('properties.' . $propertyName . '.ui.inlineEditable') === true;
});
if (empty($properties)) {
continue;
}
$this->outputLine(sprintf('NodeType <fg=white;bg=blue>%s</> contains <fg=yellow>%s</> inline editable properties...', $nodeType->getName(), count($properties)));
foreach ($this->workspaceRepository->findAll() as $workspace) {
/** @var Workspace $workspace */
if ($workspace->getNodeCount() <= 1)
continue;
$this->outputLine(sprintf('<fg=yellow>Checking "%s" workspace:</>', $workspace->getName()));
foreach ($this->contentDimensionCombinator->getAllAllowedCombinations() as $contentDimensionCombination) {
$countNodes = 0;
$countUpdatedNodes = 0;
$updatedProperties = [];
$dimensionContext = null;
$dimensionContext = $this->createContentContext($workspace->getName(), $contentDimensionCombination);
// Get existing nodes for the current context
$q = new FlowQuery(array($dimensionContext->getRootNode()));
$nodes = $q->context([
'invisibleContentShown' => true,
'removedContentShown' => true,
'inaccessibleContentShown' => true
])->find('[instanceof ' . $nodeType->getName() . ']')->get();
foreach ($nodes as $node) {
if (!$node instanceof NodeInterface)
continue;
if($node->getWorkspace()->getName() !== $workspace->getName())
continue;
$countNodes++;
$nodeUpdates = 0;
foreach ($properties as $propertyName) {
$propertyValue = $node->getProperty($propertyName);
$propertyValueFormatted = $propertyValue;
foreach ($this->formattingRules as $pattern => $replacement) {
$propertyValueFormatted = preg_replace($pattern, $replacement, $propertyValueFormatted);
}
$propertyValueFormatted = trim($propertyValueFormatted);
if ($propertyValueFormatted == $propertyValue)
continue;
$nodeUpdates++;
if (isset($updatedProperties[$propertyName])) {
$updatedProperties[$propertyName]++;
} else {
$updatedProperties[$propertyName] = 1;
}
if ($dryRun !== true)
$node->setProperty($propertyName, $propertyValueFormatted);
if ($verbose)
$this->outputLine(sprintf('%s %s | <fg=yellow>"%s"</> => <fg=green>"%s"</>', $node->getIdentifier(), $propertyName, $propertyValue, $propertyValueFormatted));
}
if (!empty($nodeUpdates))
$countUpdatedNodes++;
}
if ($countUpdatedNodes) {
$countTotalUpdatedNodes += $countUpdatedNodes;
$this->outputLine(sprintf('=> <fg=blue>%s/%s</> nodes updated in %s - %s', $countUpdatedNodes, $countNodes, json_encode($contentDimensionCombination), json_encode($updatedProperties)));
}
}
}
}
$this->outputLine();
$this->outputLine('SUMMARY');
$this->outputLine(sprintf('<fg=blue>=> %s nodes updated in total</>', $countTotalUpdatedNodes));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment