diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Products/AdaptUrlRewritesToVisibilityAttribute.php b/app/code/Magento/CatalogUrlRewrite/Model/Products/AdaptUrlRewritesToVisibilityAttribute.php
new file mode 100644
index 0000000000000..f5851cf1e11b1
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Products/AdaptUrlRewritesToVisibilityAttribute.php
@@ -0,0 +1,127 @@
+productCollectionFactory = $collectionFactory;
+ $this->urlRewriteGenerator = $urlRewriteGenerator;
+ $this->urlPersist = $urlPersist;
+ $this->urlPathGenerator = $urlPathGenerator;
+ }
+
+ /**
+ * Process Url Rewrites according to the products visibility attribute
+ *
+ * @param array $productIds
+ * @param int $visibility
+ * @throws UrlAlreadyExistsException
+ */
+ public function execute(array $productIds, int $visibility): void
+ {
+ $products = $this->getProductsByIds($productIds);
+
+ /** @var Product $product */
+ foreach ($products as $product) {
+ if ($visibility == Visibility::VISIBILITY_NOT_VISIBLE) {
+ $this->urlPersist->deleteByData(
+ [
+ UrlRewrite::ENTITY_ID => $product->getId(),
+ UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE,
+ ]
+ );
+ } elseif ($visibility !== Visibility::VISIBILITY_NOT_VISIBLE) {
+ $product->setVisibility($visibility);
+ $productUrlPath = $this->urlPathGenerator->getUrlPath($product);
+ $productUrlRewrite = $this->urlRewriteGenerator->generate($product);
+ $product->unsUrlPath();
+ $product->setUrlPath($productUrlPath);
+
+ try {
+ $this->urlPersist->replace($productUrlRewrite);
+ } catch (UrlAlreadyExistsException $e) {
+ throw new UrlAlreadyExistsException(
+ __(
+ 'Can not change the visibility of the product with SKU equals "%1". '
+ . 'URL key "%2" for specified store already exists.',
+ $product->getSku(),
+ $product->getUrlKey()
+ ),
+ $e,
+ $e->getCode(),
+ $e->getUrls()
+ );
+ }
+ }
+ }
+ }
+
+ /**
+ * Get Product Models by Id's
+ *
+ * @param array $productIds
+ * @return array
+ */
+ private function getProductsByIds(array $productIds): array
+ {
+ $productCollection = $this->productCollectionFactory->create();
+ $productCollection->addAttributeToSelect(ProductInterface::VISIBILITY);
+ $productCollection->addAttributeToSelect('url_key');
+ $productCollection->addFieldToFilter(
+ 'entity_id',
+ ['in' => array_unique($productIds)]
+ );
+
+ return $productCollection->getItems();
+ }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/ProcessUrlRewriteOnChangeProductVisibilityObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/ProcessUrlRewriteOnChangeProductVisibilityObserver.php
new file mode 100644
index 0000000000000..2337bb3646bd7
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Observer/ProcessUrlRewriteOnChangeProductVisibilityObserver.php
@@ -0,0 +1,54 @@
+adaptUrlRewritesToVisibility = $adaptUrlRewritesToVisibility;
+ }
+
+ /**
+ * Generate urls for UrlRewrites and save it in storage
+ *
+ * @param Observer $observer
+ * @return void
+ * @throws UrlAlreadyExistsException
+ */
+ public function execute(Observer $observer)
+ {
+ $event = $observer->getEvent();
+ $attrData = $event->getAttributesData();
+ $productIds = $event->getProductIds();
+ $visibility = $attrData[ProductInterface::VISIBILITY] ?? 0;
+
+ if (!$visibility || !$productIds) {
+ return;
+ }
+
+ $this->adaptUrlRewritesToVisibility->execute($productIds, (int)$visibility);
+ }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/etc/events.xml b/app/code/Magento/CatalogUrlRewrite/etc/events.xml
index cc558fe81f16d..728442acf7a44 100644
--- a/app/code/Magento/CatalogUrlRewrite/etc/events.xml
+++ b/app/code/Magento/CatalogUrlRewrite/etc/events.xml
@@ -27,6 +27,9 @@
+
+
+
diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/ProcessUrlRewriteOnChangeVisibilityObserverTest.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/ProcessUrlRewriteOnChangeVisibilityObserverTest.php
new file mode 100644
index 0000000000000..d3f0e9fa2a5ab
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/ProcessUrlRewriteOnChangeVisibilityObserverTest.php
@@ -0,0 +1,195 @@
+objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+ $this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class);
+ $this->eventManager = $this->objectManager->create(ManagerInterface::class);
+ }
+
+ /**
+ * @magentoDataFixture Magento/CatalogUrlRewrite/_files/product_rewrite_multistore.php
+ * @magentoAppIsolation enabled
+ */
+ public function testMakeProductInvisibleViaMassAction()
+ {
+ /** @var \Magento\Catalog\Model\Product $product*/
+ $product = $this->productRepository->get('product1');
+
+ /** @var StoreManagerInterface $storeManager */
+ $storeManager = $this->objectManager->get(StoreManagerInterface::class);
+ $storeManager->setCurrentStore(0);
+
+ $testStore = $storeManager->getStore('test');
+ $productFilter = [
+ UrlRewrite::ENTITY_TYPE => 'product',
+ ];
+
+ $expected = [
+ [
+ 'request_path' => "product-1.html",
+ 'target_path' => "catalog/product/view/id/" . $product->getId(),
+ 'is_auto_generated' => 1,
+ 'redirect_type' => 0,
+ 'store_id' => '1',
+ ],
+ [
+ 'request_path' => "product-1.html",
+ 'target_path' => "catalog/product/view/id/" . $product->getId(),
+ 'is_auto_generated' => 1,
+ 'redirect_type' => 0,
+ 'store_id' => $testStore->getId(),
+ ]
+ ];
+
+ $actual = $this->getActualResults($productFilter);
+ foreach ($expected as $row) {
+ $this->assertContains($row, $actual);
+ }
+
+ $this->eventManager->dispatch(
+ 'catalog_product_attribute_update_before',
+ [
+ 'attributes_data' => [ ProductInterface::VISIBILITY => Visibility::VISIBILITY_NOT_VISIBLE ],
+ 'product_ids' => [$product->getId()]
+ ]
+ );
+
+ $actual = $this->getActualResults($productFilter);
+ $this->assertCount(0, $actual);
+ }
+
+ /**
+ * @magentoDataFixture Magento/CatalogUrlRewrite/_files/product_invisible_multistore.php
+ * @magentoAppIsolation enabled
+ */
+ public function testMakeProductVisibleViaMassAction()
+ {
+ /** @var \Magento\Catalog\Model\Product $product*/
+ $product = $this->productRepository->get('product1');
+
+ /** @var StoreManagerInterface $storeManager */
+ $storeManager = $this->objectManager->get(StoreManagerInterface::class);
+ $storeManager->setCurrentStore(0);
+
+ $testStore = $storeManager->getStore('test');
+ $productFilter = [
+ UrlRewrite::ENTITY_TYPE => 'product',
+ ];
+
+ $actual = $this->getActualResults($productFilter);
+ $this->assertCount(0, $actual);
+
+ $this->eventManager->dispatch(
+ 'catalog_product_attribute_update_before',
+ [
+ 'attributes_data' => [ ProductInterface::VISIBILITY => Visibility::VISIBILITY_BOTH ],
+ 'product_ids' => [$product->getId()]
+ ]
+ );
+
+ $expected = [
+ [
+ 'request_path' => "product-1.html",
+ 'target_path' => "catalog/product/view/id/" . $product->getId(),
+ 'is_auto_generated' => 1,
+ 'redirect_type' => 0,
+ 'store_id' => '1',
+ ],
+ [
+ 'request_path' => "product-1.html",
+ 'target_path' => "catalog/product/view/id/" . $product->getId(),
+ 'is_auto_generated' => 1,
+ 'redirect_type' => 0,
+ 'store_id' => $testStore->getId(),
+ ]
+ ];
+
+ $actual = $this->getActualResults($productFilter);
+ foreach ($expected as $row) {
+ $this->assertContains($row, $actual);
+ }
+ }
+
+ /**
+ * @magentoDataFixture Magento/CatalogUrlRewrite/_files/products_invisible.php
+ * @magentoAppIsolation enabled
+ */
+ public function testErrorOnDuplicatedUrlKey()
+ {
+ $skus = ['product1', 'product2'];
+ foreach ($skus as $sku) {
+ /** @var \Magento\Catalog\Model\Product $product */
+ $productIds[] = $this->productRepository->get($sku)->getId();
+ }
+ $this->expectException(UrlAlreadyExistsException::class);
+ $this->expectExceptionMessage('Can not change the visibility of the product with SKU equals "product2". '
+ . 'URL key "product-1" for specified store already exists.');
+
+ $this->eventManager->dispatch(
+ 'catalog_product_attribute_update_before',
+ [
+ 'attributes_data' => [ ProductInterface::VISIBILITY => Visibility::VISIBILITY_BOTH ],
+ 'product_ids' => $productIds
+ ]
+ );
+ }
+
+ /**
+ * @param array $filter
+ * @return array
+ */
+ private function getActualResults(array $filter)
+ {
+ /** @var \Magento\UrlRewrite\Model\UrlFinderInterface $urlFinder */
+ $urlFinder = $this->objectManager->get(\Magento\UrlRewrite\Model\UrlFinderInterface::class);
+ $actualResults = [];
+ foreach ($urlFinder->findAllByData($filter) as $url) {
+ $actualResults[] = [
+ 'request_path' => $url->getRequestPath(),
+ 'target_path' => $url->getTargetPath(),
+ 'is_auto_generated' => (int)$url->getIsAutogenerated(),
+ 'redirect_type' => $url->getRedirectType(),
+ 'store_id' => $url->getStoreId()
+ ];
+ }
+ return $actualResults;
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_invisible_multistore.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_invisible_multistore.php
new file mode 100644
index 0000000000000..d50b29383b1ef
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_invisible_multistore.php
@@ -0,0 +1,40 @@
+loadArea(\Magento\Backend\App\Area\FrontNameResolver::AREA_CODE);
+
+require __DIR__ . '/../../Store/_files/store.php';
+
+/** @var $installer CategorySetup */
+$objectManager = Bootstrap::getObjectManager();
+$installer = $objectManager->create(CategorySetup::class);
+$storeManager = $objectManager->get(StoreManagerInterface::class);
+$storeManager->setCurrentStore(0);
+
+/** @var $product \Magento\Catalog\Model\Product */
+$product = $objectManager->create(\Magento\Catalog\Model\Product::class);
+$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE)
+ ->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default'))
+ ->setStoreId(0)
+ ->setWebsiteIds([1])
+ ->setName('Product1')
+ ->setSku('product1')
+ ->setPrice(10)
+ ->setWeight(18)
+ ->setStockData(['use_config_manage_stock' => 0])
+ ->setUrlKey('product-1')
+ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_NOT_VISIBLE)
+ ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED);
+
+/** @var ProductRepositoryInterface $productRepository */
+$productRepository = $objectManager->get(ProductRepositoryInterface::class);
+$productRepository->save($product);
diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_invisible_multistore_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_invisible_multistore_rollback.php
new file mode 100644
index 0000000000000..bcf399cb5e552
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_invisible_multistore_rollback.php
@@ -0,0 +1,33 @@
+getInstance()->reinitialize();
+
+/** @var \Magento\Framework\Registry $registry */
+$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class);
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', true);
+
+/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */
+$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+ ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class);
+try {
+ $product = $productRepository->get('product1', true);
+ if ($product->getId()) {
+ $productRepository->delete($product);
+ }
+} catch (NoSuchEntityException $e) {
+}
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', false);
+
+require __DIR__ . '/../../Store/_files/store_rollback.php';
+require __DIR__ . '/../../Store/_files/second_store_rollback.php';
diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/products_invisible.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/products_invisible.php
new file mode 100644
index 0000000000000..c72d9c8284db3
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/products_invisible.php
@@ -0,0 +1,38 @@
+loadArea(\Magento\Backend\App\Area\FrontNameResolver::AREA_CODE);
+
+/** @var $installer CategorySetup */
+$objectManager = Bootstrap::getObjectManager();
+$installer = $objectManager->create(CategorySetup::class);
+/** @var ProductRepositoryInterface $productRepository */
+$productRepository = $objectManager->get(ProductRepositoryInterface::class);
+
+$skus = ['product1', 'product2'];
+foreach ($skus as $sku) {
+ /** @var $product \Magento\Catalog\Model\Product */
+ $product = $objectManager->create(\Magento\Catalog\Model\Product::class);
+ $product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE)
+ ->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default'))
+ ->setStoreId(0)
+ ->setWebsiteIds([1])
+ ->setName('Product1')
+ ->setSku($sku)
+ ->setPrice(10)
+ ->setWeight(18)
+ ->setStockData(['use_config_manage_stock' => 0])
+ ->setUrlKey('product-1')
+ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_NOT_VISIBLE)
+ ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED);
+ $productRepository->save($product);
+}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/products_invisible_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/products_invisible_rollback.php
new file mode 100644
index 0000000000000..d3d17542aa6ab
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/products_invisible_rollback.php
@@ -0,0 +1,37 @@
+getInstance()->reinitialize();
+
+/** @var \Magento\Framework\Registry $registry */
+$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class);
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', true);
+
+/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */
+$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+ ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class);
+
+$skus = ['product1', 'product2'];
+foreach ($skus as $sku) {
+ try {
+ $product = $productRepository->get($sku, true);
+ if ($product->getId()) {
+ $productRepository->delete($product);
+ }
+ } catch (NoSuchEntityException $e) {
+ }
+}
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', false);
+
+require __DIR__ . '/../../Store/_files/store_rollback.php';
+require __DIR__ . '/../../Store/_files/second_store_rollback.php';