-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
31 changed files
with
983 additions
and
108 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
Oops, something went wrong.