diff --git a/CHANGELOG.md b/CHANGELOG.md
index ed89625..5bad2f6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,21 @@
CHANGELOG for Shopware PWA
===================
+### 0.3.1
+
+> CMS landing pages are now supported
+
+**Added**
+* Constant `LANDING_PAGE_ROUTE` in `SwagShopwarePwa\Pwa\Controller\PageController`
+* Class `SwagShopwarePwa\Pwa\PageLoader\LandingPageLoader`
+* Class `SwagShopwarePwa\Pwa\PageResult\Landing\LandingPageResult`
+* Class `SwagShopwarePwa\Pwa\PageResult\Landing\LandingPageResultHydrator`
+* PHPUnit test groups
+ * `pwa-page-product`
+ * `pwa-page-category`
+ * `pwa-page-landing`
+ * `pwa-page-routing`
+
### 0.3.0
> PHP level has been increased to PHP 7.4
diff --git a/src/Pwa/Controller/PageController.php b/src/Pwa/Controller/PageController.php
index bc18bb0..cb740fa 100644
--- a/src/Pwa/Controller/PageController.php
+++ b/src/Pwa/Controller/PageController.php
@@ -29,6 +29,8 @@ class PageController extends AbstractController
const NAVIGATION_PAGE_ROUTE = 'frontend.navigation.page';
+ const LANDING_PAGE_ROUTE = 'frontend.landing.page';
+
/**
* @var PageLoaderContextBuilderInterface
*/
@@ -79,7 +81,7 @@ public function __construct(PageLoaderContextBuilderInterface $pageLoaderContext
* property="resourceType",
* description="Type of page that was fetched. Indicates whether it is a product page or a category page",
* type="string",
- * enum={"frontend.detail.page", "frontend.navigation.page"}
+ * enum={"frontend.detail.page", "frontend.navigation.page", "frontend.landing.page"}
* ),
* @OA\Property(
* property="resourceIdentifier",
@@ -136,6 +138,9 @@ public function __construct(PageLoaderContextBuilderInterface $pageLoaderContext
* description="The category associated with the loaded page.",
* ref="#/components/schemas/category_flat"
* )
+ * ),
+ * @OA\Schema(
+ * description="A landing page result contains no specific fields."
* )
* }
* )
@@ -190,7 +195,6 @@ private function getPageLoader(PageLoaderContext $pageLoaderContext): ?PageLoade
*/
private function getPageResult(PageLoaderInterface $pageLoader, PageLoaderContext $pageLoaderContext): AbstractPageResult
{
-
/** @var AbstractPageResult $pageResult */
$pageResult = $pageLoader->load($pageLoaderContext);
diff --git a/src/Pwa/PageLoader/Context/PathResolver.php b/src/Pwa/PageLoader/Context/PathResolver.php
index 560cc32..4a234c3 100644
--- a/src/Pwa/PageLoader/Context/PathResolver.php
+++ b/src/Pwa/PageLoader/Context/PathResolver.php
@@ -17,7 +17,8 @@ class PathResolver implements PathResolverInterface
{
private const MATCH_MAP = [
PageController::NAVIGATION_PAGE_ROUTE => '/^\/?navigation\/([a-f0-9]{32})$/',
- PageController::PRODUCT_PAGE_ROUTE => '/^\/?detail\/([a-f0-9]{32})$/'
+ PageController::PRODUCT_PAGE_ROUTE => '/^\/?detail\/([a-f0-9]{32})$/',
+ PageController::LANDING_PAGE_ROUTE => '/^\/?landingPage\/([a-f0-9]{32})$/'
];
private const ROOT_ROUTE_NAME = PageController::NAVIGATION_PAGE_ROUTE;
diff --git a/src/Pwa/PageLoader/LandingPageLoader.php b/src/Pwa/PageLoader/LandingPageLoader.php
new file mode 100644
index 0000000..6896e0c
--- /dev/null
+++ b/src/Pwa/PageLoader/LandingPageLoader.php
@@ -0,0 +1,61 @@
+landingPageRoute = $landingPageRoute;
+ $this->resultHydrator = $resultHydrator;
+ }
+
+ public function getResourceType(): string
+ {
+ return self::RESOURCE_TYPE;
+ }
+
+ /**
+ * @param PageLoaderContext $pageLoaderContext
+ *
+ * @return LandingPageResult
+ */
+ public function load(PageLoaderContext $pageLoaderContext): LandingPageResult
+ {
+ $landingPageResult = $this->landingPageRoute->load(
+ $pageLoaderContext->getResourceIdentifier(),
+ $pageLoaderContext->getRequest(),
+ $pageLoaderContext->getContext()
+ );
+
+ $pageResult = $this->resultHydrator->hydrate(
+ $pageLoaderContext,
+ $landingPageResult->getLandingPage()->getCmsPage() ?? null
+ );
+
+ return $pageResult;
+ }
+}
diff --git a/src/Pwa/PageLoader/ProductPageLoader.php b/src/Pwa/PageLoader/ProductPageLoader.php
index bd808d3..b5a163f 100644
--- a/src/Pwa/PageLoader/ProductPageLoader.php
+++ b/src/Pwa/PageLoader/ProductPageLoader.php
@@ -4,13 +4,9 @@
use Shopware\Core\Content\Product\Exception\ProductNumberNotFoundException;
use Shopware\Core\Content\Product\SalesChannel\Detail\AbstractProductDetailRoute;
-use Shopware\Core\Content\Product\SalesChannel\ProductAvailableFilter;
use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductDefinition;
-use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
-use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\RequestCriteriaBuilder;
-use Shopware\Core\System\SalesChannel\Entity\SalesChannelRepositoryInterface;
use SwagShopwarePwa\Pwa\PageLoader\Context\PageLoaderContext;
use SwagShopwarePwa\Pwa\PageResult\Product\ProductPageResult;
use SwagShopwarePwa\Pwa\PageResult\Product\ProductPageResultHydrator;
@@ -80,11 +76,6 @@ public function load(PageLoaderContext $pageLoaderContext): ProductPageResult
$pageLoaderContext->getContext()->getContext()
);
- $criteria->addFilter(
- new ProductAvailableFilter($pageLoaderContext->getContext()->getSalesChannel()->getId()),
- new EqualsFilter('active', 1)
- );
-
$result = $this->productRoute->load(
$pageLoaderContext->getResourceIdentifier(),
$pageLoaderContext->getRequest(),
diff --git a/src/Pwa/PageResult/Landing/LandingPageResult.php b/src/Pwa/PageResult/Landing/LandingPageResult.php
new file mode 100644
index 0000000..56efbfb
--- /dev/null
+++ b/src/Pwa/PageResult/Landing/LandingPageResult.php
@@ -0,0 +1,11 @@
+setCmsPage($cmsPageEntity);
+ $pageResult->setResourceType($pageLoaderContext->getResourceType());
+ $pageResult->setResourceIdentifier($pageLoaderContext->getResourceIdentifier());
+
+ return $pageResult;
+ }
+}
diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml
index 77c7544..63b739f 100644
--- a/src/Resources/config/services.xml
+++ b/src/Resources/config/services.xml
@@ -54,6 +54,12 @@
+
+
+
+
+
+
@@ -69,6 +75,9 @@
+
+
diff --git a/src/Test/Integration/PageControllerTest.php b/src/Test/Integration/PageControllerTest.php
index d7e4078..0a4aff7 100644
--- a/src/Test/Integration/PageControllerTest.php
+++ b/src/Test/Integration/PageControllerTest.php
@@ -12,7 +12,6 @@
use Shopware\Core\Framework\Test\TestCaseBase\SalesChannelApiTestBehaviour;
use Shopware\Core\Framework\Test\TestDataCollection;
use Shopware\Core\Framework\Uuid\Uuid;
-use Shopware\Core\PlatformRequest;
class PageControllerTest extends TestCase
{
@@ -56,6 +55,11 @@ class PageControllerTest extends TestCase
*/
private $salesChannelRepository;
+ /**
+ * @var EntityRepositoryInterface
+ */
+ private $landingPageRepository;
+
public function setUp(): void
{
$this->ids = new TestDataCollection(Context::createDefaultContext());
@@ -83,8 +87,12 @@ public function setUp(): void
$this->cmsPageRepository = $this->getContainer()->get('cms_page.repository');
$this->salesChannelDomainRepository = $this->getContainer()->get('sales_channel_domain.repository');
$this->salesChannelRepository = $this->getContainer()->get('sales_channel.repository');
+ $this->landingPageRepository = $this->getContainer()->get('landing_page.repository');
}
+ /**
+ * @group pwa-page-category
+ */
public function testResolveCategoryPageRootPath(): void
{
$this->createCmsPage();
@@ -109,6 +117,9 @@ public function testResolveCategoryPageRootPath(): void
static::assertEquals($this->ids->get('categoryId'), $response->resourceIdentifier);
}
+ /**
+ * @group pwa-page-category
+ */
public function testResolveCategoryPage(): void
{
$this->createCmsPage();
@@ -139,6 +150,9 @@ public function testResolveCategoryPage(): void
static::assertNotNull($response->resourceIdentifier);
}
+ /**
+ * @group pwa-page-category
+ */
public function testResolveCategoryBreadcrumbLink(): void
{
$this->createCmsPage();
@@ -163,6 +177,9 @@ public function testResolveCategoryBreadcrumbLink(): void
static::assertEquals('/Home-Shoes/Children-level-2/', $response['breadcrumb'][$this->ids->get('child2CategoryId')]['path']);
}
+ /**
+ * @group pwa-page-category
+ */
public function testResolveCategoryPageTechnicalUrl(): void
{
$this->createCmsPage();
@@ -188,6 +205,9 @@ public function testResolveCategoryPageTechnicalUrl(): void
static::assertNotNull($response->resourceIdentifier);
}
+ /**
+ * @group pwa-page-category
+ */
public function testResolveCategoryWithoutCmsPage(): void
{
$this->createCategories(false);
@@ -213,7 +233,132 @@ public function testResolveCategoryWithoutCmsPage(): void
static::assertNotNull($response->resourceIdentifier);
}
- public function testProductPage(): void
+ /**
+ * @group pwa-page-category
+ */
+ public function testResolveCategoryPageWithIncludes(): void
+ {
+ $this->createCmsPage();
+ $this->createCategories();
+ $this->createSeoUrls();
+
+ $content = [
+ 'path' => 'Home-Shoes/',
+ 'includes' => [
+ 'pwa_page_result' => ['cmsPage'],
+ 'section' => ['id']
+ ]
+ ];
+
+ $this->browser->request(
+ 'POST',
+ self::ENDPOINT_PAGE,
+ $content
+ );
+
+ $response = json_decode($this->browser->getResponse()->getContent());
+
+ static::assertObjectHasAttribute('cmsPage', $response);
+ static::assertNotNull($response->cmsPage);
+ static::assertObjectHasAttribute('sections', $response->cmsPage);
+ static::assertObjectNotHasAttribute('blocks', $response->cmsPage);
+ static::assertObjectNotHasAttribute('breadcrumb', $response);
+ }
+
+ /**
+ * @group pwa-page-landing
+ */
+ public function testResolveLandingPage(): void
+ {
+ $this->createCmsPage();
+ $this->createLandingPage(true);
+ $this->createSeoUrls();
+
+ $content = [
+ 'path' => 'my-landing-page/exists'
+ ];
+
+ $this->browser->request(
+ 'POST',
+ self::ENDPOINT_PAGE,
+ $content
+ );
+
+ $response = json_decode($this->browser->getResponse()->getContent());
+
+
+ static::assertObjectHasAttribute('cmsPage', $response);
+
+ static::assertEquals('shopware AG', $response->cmsPage->name);
+ static::assertEquals('frontend.landing.page', $response->resourceType);
+ static::assertObjectHasAttribute('resourceIdentifier', $response);
+ static::assertNotNull($response->resourceIdentifier);
+ }
+
+ /**
+ * @group pwa-page-landing
+ */
+ public function testResolveLandingPageTechnicalUrl(): void
+ {
+ $this->createCmsPage();
+ $this->createLandingPage(true);
+ $this->createSeoUrls();
+
+ $content = [
+ 'path' => 'landingPage/' . $this->ids->get('landingPageId')
+ ];
+
+ $this->browser->request(
+ 'POST',
+ self::ENDPOINT_PAGE,
+ $content
+ );
+
+ $response = json_decode($this->browser->getResponse()->getContent());
+
+
+ static::assertObjectHasAttribute('cmsPage', $response);
+
+ static::assertStringContainsString('my-landing-page', $response->canonicalPathInfo);
+ static::assertEquals('frontend.landing.page', $response->resourceType);
+ static::assertObjectHasAttribute('resourceIdentifier', $response);
+ static::assertNotNull($response->resourceIdentifier);
+ }
+
+ /**
+ * @group pwa-page-landing
+ */
+ public function testResolveLandingPageWihoutCmsPage(): void
+ {
+ $this->createLandingPage(false);
+ $this->createSeoUrls();
+
+ $content = [
+ 'path' => 'my-landing-page/exists'
+ ];
+
+ $this->browser->request(
+ 'POST',
+ self::ENDPOINT_PAGE,
+ $content
+ );
+
+ $response = json_decode($this->browser->getResponse()->getContent());
+
+
+ static::assertObjectHasAttribute('cmsPage', $response);
+
+ static::assertNull($response->cmsPage);
+
+ static::assertEquals('frontend.landing.page', $response->resourceType);
+ static::assertObjectHasAttribute('resourceIdentifier', $response);
+ static::assertNotNull($response->resourceIdentifier);
+ }
+
+ /**
+ * @group pwa-page-product
+ */
+ public function testResolveProductPage(): void
{
$this->createCategories(false);
$this->createProduct();
@@ -239,7 +384,10 @@ public function testProductPage(): void
static::assertNotNull($response->resourceIdentifier);
}
- public function testProductPageWithAssociation(): void
+ /**
+ * @group pwa-page-product
+ */
+ public function testResolveProductPageWithAssociation(): void
{
$this->createCategories(false);
$this->createProduct();
@@ -268,7 +416,10 @@ public function testProductPageWithAssociation(): void
static::assertNotNull($response['product']['categories']);
}
- public function testProductPageTechnicalUrl(): void
+ /**
+ * @group pwa-page-product
+ */
+ public function testResolveProductPageTechnicalUrl(): void
{
$this->createCategories(false);
$this->createProduct();
@@ -293,7 +444,10 @@ public function testProductPageTechnicalUrl(): void
static::assertNotNull($response->resourceIdentifier);
}
- public function testProductPageForInactive(): void
+ /**
+ * @group pwa-page-product
+ */
+ public function testResolveProductPageForInactive(): void
{
$this->createCategories(false);
$this->createProduct();
@@ -320,15 +474,21 @@ public function testProductPageForInactive(): void
}
- public function testProductHasBreadcrumbsLinks(): void
+ /**
+ * @group pwa-page-product
+ */
+ public function testResolveProductPageWithCmsPage(): void
{
- $this->createCategories(false);
- $this->createProduct();
- $this->createSalesChannelDomain();
+ $this->createCmsPage();
+ $this->createCategories();
+ $this->createProduct(true);
$this->createSeoUrls();
$content = [
- 'path' => '/foo-bar/prod-has-breadcrumb'
+ 'path' => '/detail/' . $this->ids->get('productActiveId'),
+ 'includes' => [
+ 'pwa_page_result' => ['cmsPage']
+ ]
];
$this->browser->request(
@@ -337,16 +497,16 @@ public function testProductHasBreadcrumbsLinks(): void
$content
);
- $response = json_decode($this->browser->getResponse()->getContent(), true);
-
- static::assertArrayHasKey('breadcrumb', $response);
+ $response = json_decode($this->browser->getResponse()->getContent());
- static::assertEquals('/Home-Shoes/Children-canonical/', $response['breadcrumb'][$this->ids->get('childCategoryId')]['path']);
- static::assertEquals('/Home-Shoes/Children-level-2/', $response['breadcrumb'][$this->ids->get('child2CategoryId')]['path']);
- static::assertEquals('/navigation/' . $this->ids->get('child3CategoryId'), $response['breadcrumb'][$this->ids->get('child3CategoryId')]['path']);
+ static::assertObjectHasAttribute('cmsPage', $response);
+ static::assertNotNull($response->cmsPage);
}
- public function testProductHasNoBreadcrumbsLinks(): void
+ /**
+ * @group pwa-page-product
+ */
+ public function testResolveProductHasBreadcrumbsLinks(): void
{
$this->createCategories(false);
$this->createProduct();
@@ -354,7 +514,7 @@ public function testProductHasNoBreadcrumbsLinks(): void
$this->createSeoUrls();
$content = [
- 'path' => '/detail/' . $this->ids->get('productActiveId')
+ 'path' => '/foo-bar/prod-has-breadcrumb'
];
$this->browser->request(
@@ -366,21 +526,24 @@ public function testProductHasNoBreadcrumbsLinks(): void
$response = json_decode($this->browser->getResponse()->getContent(), true);
static::assertArrayHasKey('breadcrumb', $response);
- static::assertNull($response['breadcrumb']);
+
+ static::assertEquals('/Home-Shoes/Children-canonical/', $response['breadcrumb'][$this->ids->get('childCategoryId')]['path']);
+ static::assertEquals('/Home-Shoes/Children-level-2/', $response['breadcrumb'][$this->ids->get('child2CategoryId')]['path']);
+ static::assertEquals('/navigation/' . $this->ids->get('child3CategoryId'), $response['breadcrumb'][$this->ids->get('child3CategoryId')]['path']);
}
- public function testResolveCategoryPageWithIncludes(): void
+ /**
+ * @group pwa-page-product
+ */
+ public function testResolveProductHasNoBreadcrumbsLinks(): void
{
- $this->createCmsPage();
- $this->createCategories();
+ $this->createCategories(false);
+ $this->createProduct();
+ $this->createSalesChannelDomain();
$this->createSeoUrls();
$content = [
- 'path' => 'Home-Shoes/',
- 'includes' => [
- 'pwa_page_result' => ['cmsPage'],
- 'section' => ['id']
- ]
+ 'path' => '/detail/' . $this->ids->get('productActiveId')
];
$this->browser->request(
@@ -389,15 +552,15 @@ public function testResolveCategoryPageWithIncludes(): void
$content
);
- $response = json_decode($this->browser->getResponse()->getContent());
+ $response = json_decode($this->browser->getResponse()->getContent(), true);
- static::assertObjectHasAttribute('cmsPage', $response);
- static::assertNotNull($response->cmsPage);
- static::assertObjectHasAttribute('sections', $response->cmsPage);
- static::assertObjectNotHasAttribute('blocks', $response->cmsPage);
- static::assertObjectNotHasAttribute('breadcrumb', $response);
+ static::assertArrayHasKey('breadcrumb', $response);
+ static::assertNull($response['breadcrumb']);
}
+ /**
+ * @group pwa-page-routing
+ */
public function testResolveCanonicalUrl(): void
{
$this->createCmsPage();
@@ -423,30 +586,20 @@ public function testResolveCanonicalUrl(): void
static::assertEquals('/Home-Shoes/canonical/', $response->canonicalPathInfo);
}
- public function testResolveProductPageWithCmsPage(): void
+ /**
+ * @group pwa-page-routing
+ */
+ public function testResolveInvalidUrl(): void
{
- $this->createCmsPage();
- $this->createCategories();
- $this->createProduct(true);
- $this->createSeoUrls();
-
- $content = [
- 'path' => '/detail/' . $this->ids->get('productActiveId'),
- 'includes' => [
- 'pwa_page_result' => ['cmsPage']
- ]
- ];
-
$this->browser->request(
'POST',
- self::ENDPOINT_PAGE,
- $content
+ self::ENDPOINT_PAGE
);
$response = json_decode($this->browser->getResponse()->getContent());
- static::assertObjectHasAttribute('cmsPage', $response);
- static::assertNotNull($response->cmsPage);
+ static::assertEquals(\Symfony\Component\HttpFoundation\Response::HTTP_NOT_FOUND, $this->browser->getResponse()->getStatusCode());
+ static::assertObjectHasAttribute('errors', $response);
}
private function createSalesChannelDomain()
@@ -617,6 +770,16 @@ private function createSeoUrls()
'isValid' => true,
'isCanonical' => false,
],
+ [
+ 'salesChannelId' => $this->ids->get('salesChannelId'),
+ 'languageId' => Defaults::LANGUAGE_SYSTEM,
+ 'routeName' => 'frontend.landing.page',
+ 'pathInfo' => '/landingPage/' . $this->ids->get('landingPageId'),
+ 'seoPathInfo' => 'my-landing-page/exists',
+ 'foreignKey' => $this->ids->get('landingPageId'),
+ 'isValid' => true,
+ 'isCanonical' => false,
+ ],
], Context::createDefaultContext());
}
@@ -660,6 +823,22 @@ private function createCategories(bool $withCmsPage = true)
], Context::createDefaultContext());
}
+ private function createLandingPage(bool $withCmsPage = true) {
+ $this->landingPageRepository->create([
+ [
+ 'id' => $this->ids->get('landingPageId'),
+ 'salesChannels' => [
+ [
+ 'id' => $this->ids->get('salesChannelId')
+ ]
+ ],
+ 'name' => 'My test landing page',
+ 'cmsPageId' => $withCmsPage ? $this->ids->get('cmsPageId') : null,
+ 'url' => 'my-landing-page/exists'
+ ]
+ ], Context::createDefaultContext());
+ }
+
private function createCmsPage()
{
$landingPage = [