Skip to content

Commit

Permalink
Merge branch 'v1-develop' into v1
Browse files Browse the repository at this point in the history
  • Loading branch information
hostep committed Jun 26, 2020
2 parents a6e5876 + 5664102 commit 0b0443a
Show file tree
Hide file tree
Showing 31 changed files with 983 additions and 108 deletions.
38 changes: 38 additions & 0 deletions Checker/Catalog/Category/UrlKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Baldwin\UrlDataIntegrityChecker\Checker\Catalog\Category;

use Baldwin\UrlDataIntegrityChecker\Checker\Catalog\Category\UrlKey\DuplicateUrlKey as DuplicateUrlKeyChecker;
use Baldwin\UrlDataIntegrityChecker\Checker\Catalog\Category\UrlKey\EmptyUrlKey as EmptyUrlKeyChecker;

class UrlKey
{
const URL_KEY_ATTRIBUTE = 'url_key';
const STORAGE_IDENTIFIER = 'category-url-key';

private $duplicateUrlKeyChecker;
private $emptyUrlKeyChecker;

public function __construct(
DuplicateUrlKeyChecker $duplicateUrlKeyChecker,
EmptyUrlKeyChecker $emptyUrlKeyChecker
) {
$this->duplicateUrlKeyChecker = $duplicateUrlKeyChecker;
$this->emptyUrlKeyChecker = $emptyUrlKeyChecker;
}

/**
* @return array<array<string, mixed>>
*/
public function execute(): array
{
$categoryData = array_merge(
$this->duplicateUrlKeyChecker->execute(),
$this->emptyUrlKeyChecker->execute()
);

return $categoryData;
}
}
101 changes: 101 additions & 0 deletions Checker/Catalog/Category/UrlKey/DuplicateUrlKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

declare(strict_types=1);

namespace Baldwin\UrlDataIntegrityChecker\Checker\Catalog\Category\UrlKey;

use Baldwin\UrlDataIntegrityChecker\Checker\Catalog\Category\UrlPath as UrlPathChecker;
use Baldwin\UrlDataIntegrityChecker\Util\Stores as StoresUtil;

class DuplicateUrlKey
{
const DUPLICATED_PROBLEM_DESCRIPTION =
'%s categories were found which have a duplicated url_key value: "%s" within the same parent.'
. ' Please fix because this will cause problems.';

private $storesUtil;
private $urlPathChecker;
private $urlPathsInfo;

public function __construct(
StoresUtil $storesUtil,
UrlPathChecker $urlPathChecker
) {
$this->storesUtil = $storesUtil;
$this->urlPathChecker = $urlPathChecker;
$this->urlPathsInfo = [];
}

/**
* @return array<array<string, mixed>>
*/
public function execute(): array
{
$categoryData = $this->checkForDuplicatedUrlKeyAttributeValues();

return $categoryData;
}

/**
* @return array<array<string, mixed>>
*/
private function checkForDuplicatedUrlKeyAttributeValues(): array
{
$categoriesWithProblems = [];

$storeIds = $this->storesUtil->getAllStoreIds();
foreach ($storeIds as $storeId) {
$categoryUrlPaths = $this->getCategoryUrlPathsByStoreId($storeId);
$urlPathsCount = array_count_values($categoryUrlPaths);

foreach ($urlPathsCount as $urlPath => $count) {
if ($count === 1) {
continue;
}

$categories = $this->urlPathsInfo[$urlPath];

foreach ($categories as $category) {
$categoriesWithProblems[] = [
'catId' => (int) $category->getEntityId(),
'name' => $category->getName(),
'storeId' => $storeId,
'problem' => sprintf(
self::DUPLICATED_PROBLEM_DESCRIPTION,
$count,
$category->getUrlKey()
),
];
}
}
}

return $categoriesWithProblems;
}

/**
* @return array<string>
*/
private function getCategoryUrlPathsByStoreId(int $storeId): array
{
$urlPaths = [];

$categories = $this->urlPathChecker->getAllVisibleCategoriesWithStoreId($storeId);
foreach ($categories as $category) {
$urlPath = $this->urlPathChecker->getCalculatedUrlPathForCategory($category, $storeId);

$rootCatId = 0;
$path = $category->getPath() ?: '';
if (preg_match('#^(\d+)/(\d+)/.+#', $path, $matches) === 1) {
$rootCatId = $matches[2];
}

$urlPath = $rootCatId . UrlPathChecker::URL_PATH_SEPARATOR . $urlPath;

$urlPaths[] = $urlPath;
$this->urlPathsInfo[$urlPath][] = $category;
}

return $urlPaths;
}
}
114 changes: 114 additions & 0 deletions Checker/Catalog/Category/UrlKey/EmptyUrlKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

declare(strict_types=1);

namespace Baldwin\UrlDataIntegrityChecker\Checker\Catalog\Category\UrlKey;

use Baldwin\UrlDataIntegrityChecker\Checker\Catalog\Category\UrlKey as UrlKeyChecker;
use Baldwin\UrlDataIntegrityChecker\Util\Stores as StoresUtil;
use Magento\Catalog\Api\Data\CategoryInterface;
use Magento\Catalog\Model\Attribute\ScopeOverriddenValueFactory as AttributeScopeOverriddenValueFactory;
use Magento\Catalog\Model\Category as CategoryModel;
use Magento\Catalog\Model\ResourceModel\Category\Collection as CategoryCollection;
use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory as CategoryCollectionFactory;
use Magento\Store\Model\Store;

class EmptyUrlKey
{
const EMPTY_PROBLEM_DESCRIPTION = 'Category has an empty url_key value. This needs to be fixed.';

private $storesUtil;
private $categoryCollectionFactory;
private $attributeScopeOverriddenValueFactory;

public function __construct(
StoresUtil $storesUtil,
CategoryCollectionFactory $categoryCollectionFactory,
AttributeScopeOverriddenValueFactory $attributeScopeOverriddenValueFactory
) {
$this->storesUtil = $storesUtil;
$this->categoryCollectionFactory = $categoryCollectionFactory;
$this->attributeScopeOverriddenValueFactory = $attributeScopeOverriddenValueFactory;
}

/**
* @return array<array<string, mixed>>
*/
public function execute(): array
{
$categoryData = $this->checkForEmptyUrlKeyAttributeValues();

return $categoryData;
}

/**
* @return array<array<string, mixed>>
*/
private function checkForEmptyUrlKeyAttributeValues(): array
{
$categoriesWithProblems = [];

$storeIds = $this->storesUtil->getAllStoreIds();
foreach ($storeIds as $storeId) {
// we need a left join when using the non-default store view
// and especially for the case where storeId 0 doesn't have a value set for this attribute
$joinType = $storeId === Store::DEFAULT_STORE_ID ? 'inner' : 'left';

$collection = $this->categoryCollectionFactory->create();
$collection
->setStoreId($storeId)
->addAttributeToSelect(UrlKeyChecker::URL_KEY_ATTRIBUTE)
->addAttributeToSelect('name')
->addAttributeToFilter('level', ['gt' => 1]) // cats with levels 0 or 1 aren't used in the frontend
->addAttributeToFilter('entity_id', ['neq' => CategoryModel::TREE_ROOT_ID])
->addAttributeToFilter([
[
'attribute' => UrlKeyChecker::URL_KEY_ATTRIBUTE,
'null' => true,
],
[
'attribute' => UrlKeyChecker::URL_KEY_ATTRIBUTE,
'eq' => '',
],
], null, $joinType)
;

$categoriesWithProblems[] = $this->getCategoriesWithProblems($storeId, $collection);
}

if (!empty($categoriesWithProblems)) {
$categoriesWithProblems = array_merge(...$categoriesWithProblems);
}

return $categoriesWithProblems;
}

/**
* @param CategoryCollection<CategoryModel> $collection
*
* @return array<array<string, mixed>>
*/
private function getCategoriesWithProblems(int $storeId, CategoryCollection $collection): array
{
$problems = [];

foreach ($collection as $category) {
$isOverridden = $this
->attributeScopeOverriddenValueFactory
->create()
->containsValue(CategoryInterface::class, $category, UrlKeyChecker::URL_KEY_ATTRIBUTE, $storeId)
;

if ($isOverridden || $storeId === Store::DEFAULT_STORE_ID) {
$problems[] = [
'catId' => (int) $category->getEntityId(),
'name' => $category->getName(),
'storeId' => $storeId,
'problem' => self::EMPTY_PROBLEM_DESCRIPTION,
];
}
}

return $problems;
}
}
4 changes: 2 additions & 2 deletions Checker/Catalog/Category/UrlPath.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public function checkForIncorrectUrlPathAttributeValues(): array
/**
* @return CategoryCollection<Category>
*/
private function getAllVisibleCategoriesWithStoreId(int $storeId): CategoryCollection
public function getAllVisibleCategoriesWithStoreId(int $storeId): CategoryCollection
{
$categories = $this->categoryCollectionFactory->create()
->addAttributeToSelect('name')
Expand All @@ -111,7 +111,7 @@ private function doesCategoryUrlPathMatchCalculatedUrlPath(Category $category, i
return $calculatedUrlPath === $currentUrlPath;
}

private function getCalculatedUrlPathForCategory(Category $category, int $storeId): string
public function getCalculatedUrlPathForCategory(Category $category, int $storeId): string
{
if ($this->calculatedUrlPathPerCategoryAndStoreId === null) {
$this->fetchAllCategoriesWithUrlPathCalculatedByUrlKey();
Expand Down
80 changes: 80 additions & 0 deletions Console/Command/CheckCategoryUrlKeys.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

declare(strict_types=1);

namespace Baldwin\UrlDataIntegrityChecker\Console\Command;

use Baldwin\UrlDataIntegrityChecker\Checker\Catalog\Category\UrlKey as UrlKeyChecker;
use Baldwin\UrlDataIntegrityChecker\Console\CategoryResultOutput;
use Baldwin\UrlDataIntegrityChecker\Storage\Meta as MetaStorage;
use Baldwin\UrlDataIntegrityChecker\Updater\Catalog\Category\UrlKey as UrlKeyUpdater;
use Magento\Framework\App\Area as AppArea;
use Magento\Framework\App\State as AppState;
use Magento\Framework\Console\Cli;
use Symfony\Component\Console\Command\Command as ConsoleCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class CheckCategoryUrlKeys extends ConsoleCommand
{
private $appState;
private $resultOutput;
private $urlKeyUpdater;
private $metaStorage;

public function __construct(
AppState $appState,
CategoryResultOutput $resultOutput,
UrlKeyUpdater $urlKeyUpdater,
MetaStorage $metaStorage
) {
$this->appState = $appState;
$this->resultOutput = $resultOutput;
$this->urlKeyUpdater = $urlKeyUpdater;
$this->metaStorage = $metaStorage;

parent::__construct();
}

protected function configure()
{
$this->setName('catalog:category:integrity:urlkey');
$this->setDescription('Checks data integrity of the values of the url_key category attribute.');
$this->addOption(
'force',
'f',
InputOption::VALUE_NONE,
'Force the command to run, even if it is already marked as already running'
);

parent::configure();
}

protected function execute(InputInterface $input, OutputInterface $output)
{
try {
$this->appState->setAreaCode(AppArea::AREA_CRONTAB);

$force = $input->getOption('force');
if ($force === true) {
$this->metaStorage->clearStatus(UrlKeyChecker::STORAGE_IDENTIFIER);
}

$categoryData = $this->urlKeyUpdater->refresh(MetaStorage::INITIATOR_CLI);
$cliResult = $this->resultOutput->outputResult($categoryData, $output);

$output->writeln(
"\n<info>Data was stored and you can now also review it in the admin of Magento</info>"
);

return $cliResult;
} catch (\Throwable $ex) {
$output->writeln(
"<error>An unexpected exception occured: '{$ex->getMessage()}'</error>\n{$ex->getTraceAsString()}"
);
}

return Cli::RETURN_FAILURE;
}
}
36 changes: 36 additions & 0 deletions Controller/Adminhtml/Catalog/Category/UrlKey/Index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Baldwin\UrlDataIntegrityChecker\Controller\Adminhtml\Catalog\Category\UrlKey;

use Magento\Backend\App\Action as BackendAction;
use Magento\Backend\App\Action\Context as BackendContext;
use Magento\Backend\Model\View\Result\Page as BackendResultPage;
use Magento\Framework\View\Result\PageFactory as ResultPageFactory;

class Index extends BackendAction
{
const ADMIN_RESOURCE = 'Baldwin_UrlDataIntegrityChecker::catalog_data_integrity';

private $resultPageFactory;

public function __construct(
BackendContext $context,
ResultPageFactory $resultPageFactory
) {
parent::__construct($context);

$this->resultPageFactory = $resultPageFactory;
}

public function execute()
{
/** @var BackendResultPage */
$resultPage = $this->resultPageFactory->create();
$resultPage->setActiveMenu('Baldwin_UrlDataIntegrityChecker::catalog_category_urlkey');
$resultPage->getConfig()->getTitle()->prepend('Data Integrity - Category Url Key');

return $resultPage;
}
}
Loading

0 comments on commit 0b0443a

Please sign in to comment.