Commit 8dea0238 by lmf

同上修复

parent b8f31a3e

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

# CODEOWNERS file for /docs/ folder.
# Forces a review from other writers for anything within /docs/.
/docs/ @bdenham
# Contributing to Magento 2 Page Builder code
Contributions to the Magento 2 Page Builder codebase are done using the fork & pull model.
This contribution model has contributors maintaining their own fork of the Magento 2 Page Builder repository.
The forked repository is then used to submit a request to the base repository to “pull” a set of changes.
For more information on pull requests please refer to [GitHub Help](https://help.github.com/articles/about-pull-requests/).
Contributions can take the form of new components or features, changes to existing features, tests, documentation (such as developer guides, user guides, examples, or specifications), bug fixes or optimizations.
The Magento 2 Page Builder development team or community maintainers will review all issues and contributions submitted by the community of developers in the first in, first out order.
During the review we might require clarifications from the contributor.
If there is no response from the contributor within two weeks, the pull request will be closed.
For more detailed information on contribution please read our [beginners guide](https://github.com/magento/magento2/wiki/Getting-Started).
## Contribution requirements
1. Contributions must adhere to the [Magento coding standards](https://devdocs.magento.com/guides/v2.4/coding-standards/bk-coding-standards.html).
1. Pull requests (PRs) must be accompanied by a meaningful description of their purpose. Comprehensive descriptions increase the chances of a pull request being merged quickly and without additional clarification requests.
1. Commits must be accompanied by meaningful commit messages. Please see the [Magento 2 Page Builder Pull Request Template](PULL_REQUEST_TEMPLATE.md) for more information.
1. PRs which include bug fixes must be accompanied with a step-by-step description of how to reproduce the bug.
1. PRs which include new logic or new features must be submitted along with:
* Unit/integration test coverage
* Proposed [documentation](https://devdocs.magento.com) updates. Documentation contributions can be submitted via the [devdocs GitHub](https://github.com/magento/devdocs).
1. For larger features or changes, please [open an issue](https://github.com/magento/magento2-page-builder/issues) to discuss the proposed changes prior to development. This may prevent duplicate or unnecessary effort and allow other contributors to provide input.
## Contribution process
If you are a new GitHub user, we recommend that you create your own [free github account](https://github.com/signup/free).
Also, you need to participate in [Magento Partner Program](https://magento.com/partners/become).
This will allow you to collaborate with the Magento 2 Page Builder development team, fork the Magento 2 Page Builder project and send pull requests.
1. Search current [listed issues](https://github.com/magento/magento2-page-builder/issues) (open or closed) for similar proposals of intended contribution before starting work on a new contribution.
2. Review the [Contributor License Agreement](https://opensource.adobe.com/cla.html) if this is your first time contributing.
3. Create and test your work.
4. Fork the Magento 2 Page Builder repository according to the [Fork A Repository instructions](https://devdocs.magento.com/guides/v2.4/contributor-guide/contributing.html#fork) and when you are ready to send us a pull request – follow the [Create A Pull Request instructions](https://devdocs.magento.com/guides/v2.4/contributor-guide/contributing.html#pull_request).
5. Once your contribution is received the Magento 2 Page Builder development team will review the contribution and collaborate with you as needed.
## Code of Conduct
Please note that this project is released with a Contributor Code of Conduct. We expect you to agree to its terms when participating in this project.
The full text is available in the repository [Wiki](https://github.com/magento/magento2/wiki/Magento-Code-of-Conduct).
## Connecting with Community
If you have any questions, join us in [#pagebuilder](https://magentocommeng.slack.com/archives/CHB455HPF) Slack chat. If you are not on our slack, [click here](https://opensource.magento.com/slack) to join.
Need to find a project? Check out the [Slack Channels](https://github.com/magento/magento2/wiki/Slack-Channels) (with listed project info) and the [Magento Community Portal](https://opensource.magento.com/).
<!---
Thank you for contributing to Magento.
To help us process this issue we recommend that you add the following information:
- Summary of the issue,
- Information on your environment,
- Steps to reproduce,
- Expected and actual results,
Fields marked with (*) are required. Please don't remove the template.
Please also have a look at our guidelines article before adding a new issue https://github.com/magento/magento2/wiki/Issue-reporting-guidelines
-->
### Preconditions (*)
<!---
Please provide as detailed information about your environment as possible.
For example Magento version, tag, HEAD, PHP & MySQL version, etc..
-->
1.
2.
### Steps to reproduce (*)
<!---
It is important to provide a set of clear steps to reproduce this bug.
If relevant please include code samples
-->
1.
2.
3.
### Expected result (*)
<!--- Tell us what should happen -->
1. [Screenshots, logs or description]
### Actual result (*)
<!--- Tell us what happens instead -->
1. [Screenshots, logs or description]
\ No newline at end of file
---
name: Bug report
about: Technical issue with the Magento 2 Page Builder
---
<!---
Please review our guidelines before adding a new issue: https://github.com/magento/magento2/wiki/Issue-reporting-guidelines
Fields marked with (*) are required. Please don't remove the template.
-->
### Preconditions (*)
<!---
Provide the exact Magento Page Builder version (example: develop) and any important information on the environment where bug is reproducible.
-->
1.
2.
### Steps to reproduce (*)
<!---
Important: Provide a set of clear steps to reproduce this bug. We can not provide support without clear instructions on how to reproduce.
-->
1.
2.
### Expected result (*)
<!--- Tell us what do you expect to happen. -->
1. [Screenshots, logs or description]
2.
### Actual result (*)
<!--- Tell us what happened instead. Include error messages and issues. -->
1. [Screenshots, logs or description]
2.
\ No newline at end of file
---
name: Developer experience issue
about: Issues related to customization, extensibility, modularity
labels: 'Triage: Dev.Experience'
---
<!---
Please review our guidelines before adding a new issue: https://github.com/magento/magento2/wiki/Issue-reporting-guidelines
Fields marked with (*) are required. Please don't remove the template.
-->
### Summary (*)
<!--- Describe the issue you are experiencing. Include general information, error messages, environments, and so on. -->
### Examples (*)
<!--- Provide code examples or a patch with a test (recommended) to clearly indicate the problem. -->
### Proposed solution
<!--- Suggest your potential solutions for this issue. -->
\ No newline at end of file
---
name: Feature request
about: New features and improvements to the existing functionality
labels: 'feature request'
---
<!---
Important: Please describe the value of the requested feature. Fields marked with (*) are required. Please don't remove the template.
-->
### Description (*)
<!--- Describe the feature you would like to add. -->
### Expected behavior (*)
<!--- What is the expected behavior of this feature? How is it going to work? -->
### Benefits
<!--- How do you think this feature would improve Magento? -->
### Additional information
<!--- What other information can you provide about the desired feature? -->
\ No newline at end of file
<!---
Thank you for contributing to Magento.
To help us process this pull request we recommend that you add the following information:
- Summary of the pull request,
- Issue(s) related to the changes made,
- Manual testing scenarios
Fields marked with (*) are required. Please don't remove the template.
-->
<!--- Please provide a general summary of the Pull Request in the Title above -->
### Description (*)
<!---
Please provide a description of the changes proposed in the pull request.
Letting us know what has changed and why it needed changing will help us validate this pull request.
-->
### Story
<!---
* [<issue_number>](https://jira.corp.magento.com/browse/<issue_number>) <issue_title>
-->
### Bug
<!---
* [<issue_number>](https://jira.corp.magento.com/browse/<issue_number>) <issue_title>
-->
### Task
<!---
* [<issue_number>](https://jira.corp.magento.com/browse/<issue_number>) <issue_title>
-->
### Fixed Issues (if relevant)
<!---
If relevant, please provide a list of fixed issues in the format magento/magento2-page-builder#<issue_number>.
There could be 1 or more issues linked here and it will help us find some more information about the reasoning behind this change.
-->
1. magento/magento2-page-builder#<issue_number>: Issue title
### Builds
<!---
[All-User-Requested-Tests](https://m2build-ur.devops.magento.com/job/All-User-Requested-Tests/<build_number>)
-->
### Related Pull Requests
<!---
https://github.com/magento/magento2ce/pull/<related_pr>
-->
<!-- related pull request placeholder -->
### Manual testing scenarios (*)
<!---
Please provide a set of unambiguous steps to test the proposed code change.
Giving us manual testing scenarios will help with the processing and validation process.
-->
1. ...
2. ...
### Questions or comments
<!---
If relevant, here you can ask questions or provide comments on your pull request for the reviewer
For example if you need assistance with writing tests or would like some feedback on one of your development ideas
-->
### Checklist
- [ ] Pull request has a meaningful description of its purpose
- [ ] All commits are accompanied by meaningful commit messages
- [ ] All new or changed code is covered with unit/integration tests (if applicable)
- [ ] All automated tests passed successfully (all builds are green)
.idea
**/node_modules/*
**.js.map
**.d.js
/dev/tests/acceptance/vendor
static-error-log.xml
.DS_Store
.vscode
.history/
/yarn-error.log
[submodule "page-builder-types"]
path = page-builder-types
url = git@github.com:magento-obsessive-owls/page-builder-types.git
app/code/Magento/PageBuilder/view/base/web/js/resource
app/code/Magento/PageBuilder/view/frontend/web/css/source
\ No newline at end of file
{
"extends": "stylelint-config-recommended",
"plugins": [
"stylelint-order"
],
"rules": {
"block-closing-brace-empty-line-before": "never",
"block-closing-brace-newline-after": "always",
"color-no-invalid-hex": true,
"color-hex-case": "lower",
"color-hex-length": "short",
"declaration-colon-space-after": "always",
"declaration-colon-space-before": "never",
"declaration-empty-line-before": "never",
"declaration-bang-space-before": "always",
"max-nesting-depth": 5,
"number-leading-zero": "never",
"order/properties-alphabetical-order": true,
"selector-combinator-space-after": "always",
"selector-combinator-space-before": "always",
"selector-list-comma-newline-after": "always",
"shorthand-property-no-redundant-values": true,
"string-quotes": "single",
"function-calc-no-invalid": null
}
}
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at engcom@magento.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
\ No newline at end of file
Copyright © 2019-present Magento, Inc. All rights reserved.
\ No newline at end of file
# Magento 2 Page Builder
Welcome to the Magento 2 Page Builder project!
## Overview
Page Builder introduces an intuitive, drag-and-drop interface for creating digital content, powered by content types like images, videos, banners, etc. with instant preview capabilities that enable non-technical users to take control of their content. It allows to create new pages, enrich products and categories, and launch content updates quickly and easily without the help of a front-end web developer.
## Wiki
Public access to the Page Builder wiki is found here:
https://github.com/magento/magento2-page-builder-docs/wiki.
The wiki provides more information on the Page Builder project, such as:
- [Links to User Guide tutorials](https://github.com/magento/magento2-page-builder-docs/wiki#page-builder-tutorials)
- [Page Builder roadmaps](https://github.com/magento/magento2-page-builder-docs/wiki#roadmap)
- [MFTF best practices](https://github.com/magento/magento2-page-builder-docs/wiki/%5BRough-Draft%5D-MFTF-Best-Practices)
- [Partners Acceleration Program](https://github.com/magento/magento2-page-builder-docs/wiki/Partners-Acceleration-Program-Team)
## Documentation
Complete documentation located on the [Magento DevDocs](https://devdocs.magento.com/page-builder/docs/), including what you need to know to start local development as described in the [installation guide](https://devdocs.magento.com/page-builder/docs/getting-started/install-pagebuilder.html).
## Community Engineering Slack
To connect with Magento team and the Community, join us on the [Magento Community Engineering Slack](https://magentocommeng.slack.com).
If you are interested in joining Slack, or a specific channel, use our [self signup](https://opensource.magento.com/slack) link.
Magento 2 Page Builder project slack channel: [#pagebuilder](https://magentocommeng.slack.com/archives/CHB455HPF)
\ No newline at end of file
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
const path = require('path');
const fs = require('fs');
const prettier = require('prettier');
const typesFile = 'page-builder-types/index.d.ts';
const copyrightComment = `/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/`;
/**
* Resolve a file system path to the Magento module import path
*
* @param {string} currentModuleId
* @returns {string}
*/
function resolveModuleIdToMagentoPath(currentModuleId) {
return currentModuleId.replace(
'app/code/Magento/PageBuilder/view/adminhtml/web/ts/',
'Magento_PageBuilder/'
);
}
// Use dts-generator to create a single types definition file
require('dts-generator').default({
project: './',
out: typesFile,
resolveModuleId: (params) => {
return resolveModuleIdToMagentoPath(params.currentModuleId);
},
resolveModuleImport: (params) => {
// Convert relative imports into their Magento counterparts
if (params.importedModuleId.startsWith('../') || params.importedModuleId.startsWith('./')) {
return resolveModuleIdToMagentoPath(
path.resolve(
path.dirname(params.currentModuleId),
params.importedModuleId,
).replace(
process.cwd() + '/',
''
)
);
}
return params.importedModuleId;
}
}).then(() => {
const { exec } = require('child_process');
// Lint the generated file
const lint = exec(`./node_modules/tslint/bin/tslint --fix ${typesFile}`);
lint.on("exit", () => {
// Replace all tab characters with 4 spaces
fs.readFile(typesFile, 'utf-8', (error, contents) => {
if (error) {
throw Error(`Unable to read types file ${typesFile}.`);
}
let modifiedContents = contents
.replace(/.*\/\*\*\n.*Copyright © Magento.*\n.*\n.*\*\//gm, '') // Strip all Magento copyright
.replace(/.*\/\*\*\n.*@api.*\n.*\*\//gm, ''); // Strip all @api comments
modifiedContents = `${copyrightComment}\n${modifiedContents}`;
fs.writeFile(typesFile, prettier.format(modifiedContents, {parser: "typescript"}), null, () => {
console.log("Type definition generation completed.");
process.exit();
});
});
});
});
\ No newline at end of file
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\PageBuilder\Api;
class ProductAttributeRepositoryTest extends \Magento\TestFramework\TestCase\WebapiAbstract
{
const SERVICE_NAME = 'catalogProductAttributeRepositoryV1';
const SERVICE_VERSION = 'V1';
const RESOURCE_PATH = '/V1/products/attributes';
public function testCreatePageBuilderAttribute()
{
$attributeCode = uniqid('label_attr_code');
$attribute = $this->createPageBuilderAttribute($attributeCode);
$this->assertEquals('front_lbl', $attribute['default_frontend_label']);
$this->assertNotEmpty($attribute['extension_attributes']);
$this->assertTrue($attribute['extension_attributes']['is_pagebuilder_enabled']);
}
/**
* @param $attributeCode
* @return array
*/
private function createPageBuilderAttribute($attributeCode)
{
$attributeData = [
'attribute' => [
'attribute_code' => $attributeCode,
'entity_type_id' => '4',
'frontend_labels' => [
[
'store_id' => 0,
'label' => 'front_lbl'
],
],
'is_required' => true,
"default_value" => "",
"frontend_input" => "textarea",
"is_wysiwyg_enabled" => 1,
"is_visible_on_front" => true,
"is_searchable" => false,
"is_visible_in_advanced_search" => false,
"is_filterable" => false,
"is_filterable_in_search" => false,
\Magento\Framework\Api\ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY => [
'is_pagebuilder_enabled' => 1
]
],
];
$serviceInfo = [
'rest' => [
'resourcePath' => self::RESOURCE_PATH . '/',
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
],
'soap' => [
'service' => self::SERVICE_NAME,
'serviceVersion' => self::SERVICE_VERSION,
'operation' => self::SERVICE_NAME . 'Save',
],
];
$attribute = $this->_webApiCall($serviceInfo, $attributeData);
return $attribute;
}
}
<?xml version="1.0"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Magento_TestModuleCmsPageBuilderAnalytics" active="true" />
</config>
<?xml version="1.0"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Analytics:etc/reports.xsd">
<report name="pagebuilder_page_cms_test" connection="default">
<source name="cms_page">
<attribute name="content"/>
<link-source name="cms_page_store" link-type="inner">
<using glue="and">
<condition attribute="page_id" operator="eq" type="identifier">page_id</condition>
<condition attribute="store_id" operator="in">0,1</condition>
</using>
</link-source>
<filter glue="and">
<condition attribute="identifier" operator="like">page-builder-analytics-test-page%</condition>
</filter>
</source>
</report>
<report name="pagebuilder_page_cms_staging_test" connection="default">
<source name="cms_page">
<attribute name="content"/>
<link-source name="cms_page_store" link-type="inner">
<using glue="and">
<condition attribute="row_id" operator="eq" type="identifier">row_id</condition>
<condition attribute="store_id" operator="in">0,1</condition>
</using>
</link-source>
<filter glue="and">
<condition attribute="identifier" operator="like">page-builder-analytics-test-page%</condition>
</filter>
</source>
</report>
</config>
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
use Magento\Framework\Component\ComponentRegistrar;
$registrar = new ComponentRegistrar();
if ($registrar->getPath(ComponentRegistrar::MODULE, 'Magento_TestModuleCmsPageBuilderAnalytics') === null) {
ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_TestModuleCmsPageBuilderAnalytics', __DIR__);
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\TestModulePageBuilderExtensionPoints\Model\Config\ContentType\AdditionalData\Provider;
use Magento\PageBuilder\Model\Config\ContentType\AdditionalData\ProviderInterface;
/**
* Class TestData
*/
class TestData implements ProviderInterface
{
public function getData(string $itemName) : array
{
return [$itemName => 'test data'];
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\TestModulePageBuilderExtensionPoints\Model\Stage\Renderer;
/**
* Class Test
*/
class Test implements \Magento\PageBuilder\Model\Stage\RendererInterface
{
/**
* Render test data
*
* @param array $params
* @return array
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function render(array $params): array
{
return ['content' => 'Test Content'];
}
}
<?xml version="1.0"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\PageBuilder\Model\Stage\RendererPool">
<arguments>
<argument name="renderers" xsi:type="array">
<item name="test" xsi:type="object">Magento\TestModulePageBuilderExtensionPoints\Model\Stage\Renderer\Test</item>
</argument>
</arguments>
</type>
</config>
<?xml version="1.0"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Magento_TestModulePageBuilderExtensionPoints" setup_version="1.0.0" active="true">
</module>
</config>
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
use Magento\Framework\Component\ComponentRegistrar;
$registrar = new ComponentRegistrar();
if ($registrar->getPath(ComponentRegistrar::MODULE, 'Magento_TestModulePageBuilderExtensionPoints') === null) {
ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_TestModulePageBuilderExtensionPoints', __DIR__);
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
// @codingStandardsIgnoreFile
?>
<div data-content-type="html">
&lt;img src=&quot;http://example.com&quot;&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/owsfdh4gxyc&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;
&amp;amp;
</div>
\ No newline at end of file
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
// @codingStandardsIgnoreFile
?>
<div>
<img src="http://example.com/block">
</div>
\ No newline at end of file
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
// @codingStandardsIgnoreFile
?>
Hello world
\ No newline at end of file
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\CmsPageBuilderAnalytics\Model;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\Framework\App\ResourceConnection;
/**
* @magentoAppArea adminhtml
*/
class ContentTypeUsageReportProviderTest extends \PHPUnit\Framework\TestCase
{
/**
* @magentoAppIsolation enabled
* @magentoDataFixture Magento/CmsPageBuilderAnalytics/_files/pages.php
* @dataProvider reportDataProvider
*/
public function testGetReport($expectedReportData, $ignoredContentTypes)
{
/* @var $resourceConnection ResourceConnection */
$resourceConnection = Bootstrap::getObjectManager()->get(ResourceConnection::class)
->getConnection(ResourceConnection::DEFAULT_CONNECTION);
$connectionFactoryMock = $this->createMock(\Magento\Analytics\ReportXml\ConnectionFactory::class);
$connectionFactoryMock->expects($this->once())
->method('getConnection')
->willReturn($resourceConnection);
/* @var $contentTypeUsageReportProvider \Magento\PageBuilderAnalytics\Model\ContentTypeUsageReportProvider */
$contentTypeUsageReportProvider = Bootstrap::getObjectManager()->create(
\Magento\PageBuilderAnalytics\Model\ContentTypeUsageReportProvider::class,
[
'connectionFactory' => $connectionFactoryMock
]
);
$moduleManager = Bootstrap::getObjectManager()->get(\Magento\Framework\Module\Manager::class);
// If CMS Staging is enabled we need to use an alternative query
if ($moduleManager->isEnabled('Magento_CmsStaging')) {
$reportData = $contentTypeUsageReportProvider->getReport('pagebuilder_page_cms_staging_test');
} else {
$reportData = $contentTypeUsageReportProvider->getReport('pagebuilder_page_cms_test');
}
$expectedReportDataByType = array_combine(array_column($expectedReportData, 'type'), $expectedReportData);
foreach ($reportData->getInnerIterator() as $reportItem) {
// Skip over any ignored content types
if (in_array($reportItem['type'], $ignoredContentTypes)) {
continue;
}
// Verify we have expected report data for the content type
if (!isset($expectedReportDataByType[$reportItem['type']])) {
$this->fail('There is no report data for ' . $reportItem['type'] . '.');
}
// Verify the count values match the expected report data
$this->assertEquals($expectedReportDataByType[$reportItem['type']]['count'], $reportItem['count']);
}
}
/**
* @return array
*/
public function reportDataProvider(): array
{
return [
[
[
['type' => 'button-item', 'count' => 6],
['type' => 'slide', 'count' => 12],
['type' => 'text', 'count' => 1],
['type' => 'image', 'count' => 2],
['type' => 'block', 'count' => 1],
['type' => 'row', 'count' => 7],
['type' => 'column-group', 'count' => 5],
['type' => 'column', 'count' => 12],
['type' => 'video', 'count' => 2],
['type' => 'heading', 'count' => 3],
['type' => 'tabs', 'count' => 1],
['type' => 'products', 'count' => 0],
['type' => 'tab-item', 'count' => 2],
['type' => 'banner', 'count' => 4],
['type' => 'buttons', 'count' => 2],
['type' => 'slider', 'count' => 3],
['type' => 'divider', 'count' => 5],
['type' => 'map', 'count' => 2],
['type' => 'html', 'count' => 2]
],
// Ignored content types
[
'dynamic_block'
]
]
];
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\PageBuilder\Block\Catalog\Category;
use Magento\Catalog\Api\CategoryRepositoryInterface;
use Magento\Catalog\Block\Category\View;
use Magento\Framework\Registry;
use Magento\Framework\View\LayoutInterface;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\Catalog\Api\Data\CategoryInterface;
use Magento\Framework\ObjectManagerInterface;
use PHPUnit\Framework\TestCase;
/**
* @magentoAppArea frontend
*/
class ViewTest extends TestCase
{
/** @var ObjectManagerInterface */
private $objectManager;
/** @var CategoryRepositoryInterface */
private $categoryRepository;
/** @var View */
private $block;
/** @var LayoutInterface */
private $layout;
/** @var Registry */
private $registry;
/**
* @inheritdoc
*/
protected function setUp(): void
{
$this->objectManager = Bootstrap::getObjectManager();
$this->categoryRepository = $this->objectManager->create(CategoryRepositoryInterface::class);
$this->registry = $this->objectManager->get(Registry::class);
$this->layout = $this->objectManager->get(LayoutInterface::class);
}
/**
* Check that PageBuilder category description block contents selector
*
* @return void
* @magentoDataFixture Magento/Catalog/_files/category.php
*/
public function testDescription(): void
{
/** @var CategoryInterface $category */
$category = $this->categoryRepository->get(333);
$category->setDescription('This is the description for Category 333 without PageBuilder styles');
$this->categoryRepository->save($category);
$this->registerCategory($category);
$this->block = $this->layout->createBlock(View::class);
$this->block->setTemplate('Magento_PageBuilder::catalog/category/view/description.phtml');
$this->assertStringContainsString('data-appearance="contained"', $this->block->toHtml());
}
/**
* Register the category
*
* @param CategoryInterface $category
* @return void
*/
private function registerCategory(CategoryInterface $category): void
{
$this->registry->unregister('current_category');
$this->registry->register('current_category', $category);
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\PageBuilder\Block\Catalog\Product;
use Magento\Framework\View\LayoutInterface;
use Magento\Review\Block\Product\Review;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\Framework\View\Element\Template;
use PHPUnit\Framework\TestCase;
/**
* PageBuilder Product view integration tests.
*
* @magentoAppArea frontend
*/
class ViewTest extends TestCase
{
/**
* @var string
*/
private $wrapperTemplatePath = 'Magento_PageBuilder::catalog/product/view/%s';
/**
* @var LayoutInterface
*/
private $layout;
/**
* @inheritDoc
*/
protected function setUp(): void
{
$this->layout = Bootstrap::getObjectManager()->get(LayoutInterface::class);
}
/**
* Check that Section Wrapper page contains section ID if it was provided.
*
* @param string $sectionId
* @return void
* @dataProvider sectionWrapperDataProvider
*/
public function testSectionWrapperWithProvidedSectionId(string $sectionId): void
{
$wrapperBlock = $this->prepareSectionWrapperBlock();
$wrapperBlock->setSectionId($sectionId);
$this->assertStringContainsString(sprintf('id="%s"', $sectionId), $wrapperBlock->toHtml());
}
/**
* Check that Section Wrapper page does NOT contain section ID if it was NOT provided.
*
* @param string $sectionId
* @return void
* @dataProvider sectionWrapperDataProvider
*/
public function testSectionWrapperWithoutSectionId(string $sectionId): void
{
$wrapperBlock = $this->prepareSectionWrapperBlock();
$this->assertStringNotContainsString(sprintf('id="%s"', $sectionId), $wrapperBlock->toHtml());
}
/**
* DataProvider for testSectionWrapper().
*
* @return array
*/
public function sectionWrapperDataProvider(): array
{
return [
['description'],
['additional'],
['reviews'],
];
}
/**
* Create and retrieve Section Wrapper block instance.
*
* @return Template
*/
private function prepareSectionWrapperBlock(): Template
{
/** @var Template $wrapperBlock */
$wrapperBlock = $this->layout->createBlock(Template::class);
$wrapperBlock->setTemplate(
sprintf(
$this->wrapperTemplatePath,
'section_wrapper.phtml'
)
);
/** @var Review $childReviewBlock */
$childReviewBlock = $this->layout->createBlock(Review::class);
$childReviewBlock->setTemplate('Magento_Review::review.phtml');
$wrapperBlock->setChild('child.review', $childReviewBlock);
return $wrapperBlock;
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\PageBuilder\CatalogWidget\Block\Product;
use Magento\Catalog\Api\Data\CategoryInterface;
use Magento\CatalogWidget\Block\Product\ProductsList;
use Magento\Store\Model\Store;
use Magento\TestFramework\Helper\Bootstrap;
use PHPUnit\Framework\TestCase;
/**
* Test catalog products list widget block with page builder
*
* @magentoAppArea adminhtml
* @magentoAppIsolation enabled
*/
class ProductListTest extends TestCase
{
/**
* @var ProductsList
*/
private $block;
/**
* @inheritdoc
*/
protected function setUp(): void
{
parent::setUp();
$objectManager = Bootstrap::getObjectManager();
$this->block = $objectManager->create(ProductsList::class);
}
/**
* Test that sorting by price works correctly
*
* @magentoDbIsolation disabled
* @magentoConfigFixture default_store catalog/price/scope 1
* @magentoDataFixture Magento/Catalog/_files/category_with_different_price_products.php
* @param string $order
* @param array $skus
* @dataProvider priceSortDataProvider
*/
public function testPriceSort(string $order, array $skus)
{
$encodedConditions = '^[`1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Combine`,
`aggregator`:`all`,`value`:`1`,`new_child`:``^],
`1--1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Product`,
`attribute`:`sku`,`operator`:`()`,`value`:`simple1000,simple1001`^]^]';
$this->block->setData('sort_order', $order);
$this->block->setData('conditions_encoded', $encodedConditions);
$this->block->setStoreId(Store::DEFAULT_STORE_ID);
$productCollection = $this->block->createCollection();
$productCollection->load();
$this->assertEquals($skus, $productCollection->getColumnValues('sku'));
}
/**
* Test that filtering by price works correctly
*
* @magentoDbIsolation disabled
* @magentoConfigFixture default_store catalog/price/scope 1
* @magentoDataFixture Magento/Catalog/_files/category_with_different_price_products.php
* @param string $operator
* @param int $value
* @param array $matches
* @dataProvider priceFilterDataProvider
*/
public function testPriceFilter(string $operator, int $value, array $matches)
{
$encodedConditions = '^[`1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Combine`,
`aggregator`:`all`,`value`:`1`,`new_child`:``^],
`1--1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Product`,
`attribute`:`sku`,`operator`:`()`,`value`:`simple1000,simple1001`^],
`1--2`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Product`,
`attribute`:`price`,`operator`:`' . $operator . '`,`value`:`' . $value . '`^]^]';
$this->block->setData('conditions_encoded', $encodedConditions);
$this->block->setStoreId(Store::DEFAULT_STORE_ID);
$productCollection = $this->block->createCollection();
$productCollection->load();
$this->assertEqualsCanonicalizing($matches, $productCollection->getColumnValues('sku'));
}
/**
* Test product list widget with product that has different price on each website
*
* @magentoDbIsolation disabled
* @magentoConfigFixture default_store catalog/price/scope 1
* @magentoDataFixture Magento/Catalog/_files/product_with_price_on_second_website.php
*/
public function testProductWithDifferentPriceOnEachWebsite(): void
{
$sku = 'second-website-price-product';
$encodedConditions = '^[`1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Combine`,
`aggregator`:`all`,`value`:`1`,`new_child`:``^],
`1--1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Product`,
`attribute`:`sku`,`operator`:`==`,`value`:`' . $sku . '`^]^]';
$this->block->setData('conditions_encoded', $encodedConditions);
$this->block->setStoreId(Store::DEFAULT_STORE_ID);
$productCollection = $this->block->createCollection();
$productCollection->load();
$this->assertEquals([$sku], $productCollection->getColumnValues('sku'));
}
/**
* Test that filtering by category works correctly together with sorting
*
* @magentoDataFixture Magento/Catalog/_files/multiple_products.php
* @magentoDataFixture Magento/Catalog/_files/products_list.php
* @magentoDataFixture Magento/Catalog/_files/categories_no_products.php
* @param array $categories
* @param int $categoryId
* @param string $order
* @param array $skus
* @dataProvider categoryFilterAndSortDataProvider
*/
public function testCategoryFilterAndSort(array $categories, int $categoryId, string $order, array $skus): void
{
$objectManager = Bootstrap::getObjectManager();
foreach ($categories as $id => $data) {
/** @var CategoryInterface $categoryAnchor */
$category = $objectManager->create(CategoryInterface::class);
$category->load($id);
$category->setIsAnchor($data['is_anchor']);
$category->setPostedProducts($data['products']);
$category->save();
}
$encodedConditions = '^[`1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Combine`,
`aggregator`:`all`,`value`:`1`,`new_child`:``^],
`1--1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Product`,
`attribute`:`category_ids`,`operator`:`==`,`value`:`' . $categoryId . '`^]^]';
$this->block->setData('sort_order', $order);
$this->block->setData('condition_option', 'category_ids');
$this->block->setData('condition_option_value', $categoryId);
$this->block->setData('conditions_encoded', $encodedConditions);
$this->block->setStoreId(Store::DEFAULT_STORE_ID);
$productCollection = $this->block->createCollection();
$productCollection->load();
$this->assertEquals($skus, $productCollection->getColumnValues('sku'));
}
/**
* @return array
*/
public function priceFilterDataProvider(): array
{
return [
[
'>',
10,
[
'simple1001',
]
],
[
'>=',
10,
[
'simple1000',
'simple1001',
]
],
[
'<',
10,
[]
],
[
'<',
20,
[
'simple1000',
]
],
];
}
/**
* @return array
*/
public function priceSortDataProvider(): array
{
return [
[
'price_low_to_high',
[
'simple1000',
'simple1001',
]
],
[
'price_high_to_low',
[
'simple1001',
'simple1000',
]
],
];
}
/**
* @return array
*/
public function categoryFilterAndSortDataProvider(): array
{
$categories = [
//Category 1
3 => [
'is_anchor' => true,
'products' => [
//simple-249
153 => 0,
//simple-156
156 => 1,
]
],
//Category 1.1
4 => [
'is_anchor' => false,
'products' => [
//simple-156
156 => 0,
//simple2
11 => 1,
//simple-249
153 => 2,
]
],
//Category 1.1.1
5 => [
'is_anchor' => false,
'products' => [
//simple1
10 => 1,
]
]
];
return [
[
$categories,
3,
'position',
[
'simple-249',
'simple-156',
'simple2',
'simple1',
]
],
[
$categories,
4,
'position',
[
'simple-156',
'simple2',
'simple-249',
]
],
[
$categories,
3,
'date_newest_top',
[
'simple-156',
'simple-249',
'simple2',
'simple1',
]
],
[
$categories,
4,
'date_newest_top',
[
'simple-156',
'simple-249',
'simple2',
]
]
];
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\PageBuilder\Controller\Adminhtml\ContentType\Image;
use Magento\Framework\App\Request\Http as HttpRequest;
use Magento\Framework\Exception\FileSystemException;
use Magento\Framework\Filesystem;
use Magento\Framework\Filesystem\DirectoryList;
use Magento\TestFramework\TestCase\AbstractBackendController;
/**
* @magentoAppArea adminhtml
*/
class UploadTest extends AbstractBackendController
{
private const URI = 'backend/pagebuilder/contentType/image_upload';
/**
* @inheritdoc
*/
protected function tearDown(): void
{
$_FILES = [];
parent::tearDown();
}
/**
* Assert that file validation passes when uploaded file has correct extension and valid mime type
*/
public function testFileValidationPassesWhenFileHasCorrectExtensionAndValidMimeType()
{
$filePath = realpath(__DIR__ . '/../../../../_files/uploader/a.png');
$this->createUploadFixture($filePath);
$this->makeRequest();
$this->assertEquals(200, $this->getResponse()->getStatusCode());
$result = json_decode($this->getResponse()->getBody(), true);
$this->assertEquals(0, $result['error']);
$this->assertNotEmpty($result['url']);
}
/**
* Assert that file validation fails when uploaded file has correct extension but invalid mime type
*/
public function testFileValidationFailsWhenFileHasCorrectExtensionButInvalidMimeType()
{
$filePath = realpath(__DIR__ . '/../../../../_files/uploader/not-a.png');
$this->createUploadFixture($filePath);
$this->makeRequest();
$this->assertEquals(200, $this->getResponse()->getStatusCode());
$result = json_decode($this->getResponse()->getBody(), true);
$this->assertEquals(
[
'error' => 'File validation failed.',
'errorcode' => 0
],
$result
);
}
/**
* Assert that file url should be based on backend base url
*
* @magentoConfigFixture default_store web/unsecure/base_url http://storefront.magento.test/
* @magentoConfigFixture default_store web/secure/base_url https://storefront.magento.test/
*/
public function testFileUrlShouldBeBaseOnBackendBaseUrl()
{
$filePath = realpath(__DIR__ . '/../../../../_files/uploader/a.png');
$this->createUploadFixture($filePath);
$this->makeRequest();
$this->assertEquals(200, $this->getResponse()->getStatusCode());
$result = json_decode($this->getResponse()->getBody(), true);
$this->assertStringStartsWith('http://localhost/media/', $result['url']);
}
/**
* Initiates request
*
* @param string $fileParam
*/
private function makeRequest(string $fileParam = 'image'): void
{
$this->getRequest()
->setParams(['param_name' => $fileParam])
->setMethod(HttpRequest::METHOD_POST);
$this->dispatch(self::URI);
}
/**
* Creates a fixture for testing uploaded file
*
* @param string $filePath
* @param string $fileType
* @param string $fileParam
* @return void
* @throws FileSystemException
*/
private function createUploadFixture(
string $filePath,
string $fileType = 'image/png',
string $fileParam = 'image'
): void {
$filename = basename($filePath);
$filesize = filesize($filePath);
/** @var \Magento\TestFramework\App\Filesystem $filesystem */
$filesystem = $this->_objectManager->get(Filesystem::class);
$tmpDir = $filesystem->getDirectoryWrite(DirectoryList::SYS_TMP);
// phpcs:ignore
$subDir = md5(get_class($this));
$tmpDir->create($subDir);
$tmpPath = $tmpDir->getAbsolutePath("{$subDir}/{$filename}");
copy($filePath, $tmpPath);
$_FILES = [
$fileParam => [
'type' => $fileType,
'name' => $filename,
'tmp_name' => $tmpPath,
'size' => $filesize,
'error' => UPLOAD_ERR_OK,
],
];
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\PageBuilder\Controller\Adminhtml\Form\Element\ProductConditions;
class ChildTest extends \Magento\TestFramework\TestCase\AbstractBackendController
{
public function testFormLoadsEmptyFormUsingParams()
{
$this->getRequest()
->setParams([
'js_object_name' => 'myobject',
'form_namespace' => 'test_namespace',
])
->setPostValue([
'type' => 'Magento\CatalogWidget\Model\Rule\Condition\Product|category_ids',
'id' => '1--3',
])
->setMethod($this->getRequest()::METHOD_POST);
$this->dispatch('backend/pagebuilder/form/element_productconditions_child');
$responseBody = $this->getResponse()->getBody();
// Assert form is associated correctly
$this->assertStringContainsString('data-form-part="test_namespace"', $responseBody);
// Assert the form object is propagated
$this->assertStringContainsString('form/myobject', $responseBody);
// Assert id is used
$this->assertStringContainsString('name="parameters[conditions][1--3][type]"', $responseBody);
// Assert type is used
$this->assertStringContainsString('value="Magento\CatalogWidget\Model\Rule\Condition\Product"', $responseBody);
}
public function testFormLoadsCustomPrefix()
{
$this->getRequest()
->setParams([
'js_object_name' => 'myobject',
'form_namespace' => 'test_namespace',
'prefix' => 'myprefix',
])
->setPostValue([
'type' => 'Magento\CatalogWidget\Model\Rule\Condition\Product|category_ids',
'id' => '1--3',
])
->setMethod($this->getRequest()::METHOD_POST);
$this->dispatch('backend/pagebuilder/form/element_productconditions_child');
$responseBody = $this->getResponse()->getBody();
// Assert the form object is propagated
$this->assertStringContainsString('form/myobject', $responseBody);
// Assert id is used
$this->assertStringContainsString('name="parameters[myprefix][1--3][type]"', $responseBody);
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\PageBuilder\Controller\Adminhtml\Form\Element;
class ProductConditionsTest extends \Magento\TestFramework\TestCase\AbstractBackendController
{
public function testFormLoadsEmptyFormUsingParams()
{
$this->getRequest()
->setParams([
'form_namespace' => 'test_namespace',
])
->setPostValue([
'conditions' => '[]',
]);
$this->dispatch('backend/pagebuilder/form/element_productconditions');
$responseBody = $this->getResponse()->getBody();
// Assert form is associated correctly
$this->assertStringContainsString('data-form-part="test_namespace"', $responseBody);
// Assert correct conditions are loaded
$this->assertStringContainsString(\Magento\CatalogWidget\Model\Rule\Condition\Combine::class, $responseBody);
}
public function testFormLoadsConditionsFromPost()
{
$conditions = [
'1--1' => [
'type' => \Magento\CatalogWidget\Model\Rule\Condition\Product::class,
'attribute' => 'description',
'operator' => '{}',
'value' => 'foo',
],
'1--2' => [
'type' => \Magento\CatalogWidget\Model\Rule\Condition\Combine::class,
'aggregator' => 'all',
'value' => '1',
'new_child' => '',
],
'1--2--1' => [
'type' => \Magento\CatalogWidget\Model\Rule\Condition\Product::class,
'attribute' => 'amounts',
'operator' => '==',
'value' => '123',
],
];
$this->getRequest()
->setParams([
'form_namespace' => 'test_namespace',
])
->setPostValue([
'conditions' => json_encode($conditions),
]);
$this->dispatch('backend/pagebuilder/form/element_productconditions');
$responseBody = $this->getResponse()->getBody();
// Assert the description rule is loaded correctly
$this->assertMatchesRegularExpression(
'/<option value="{}" id="(.+)"\s selected="selected">contains<\/option>/',
$responseBody
);
$expected = 'data-ui-id="editable-0-text-parameters-conditions-1-1-value"' .
' value="foo" data-form-part="test_namespace"';
$this->assertStringContainsString($expected, $responseBody);
// Assert the combine form has form-part
$expected = 'name="parameters[conditions][1--2][value]" data-form-part="test_namespace"';
$this->assertStringContainsString($expected, $responseBody);
// Assert the combine condition has the correct child value and form-part
$expected = 'data-ui-id="editable-0-text-parameters-conditions-1-2-1-value"' .
' value="123" data-form-part="test_namespace"';
$this->assertStringContainsString($expected, $responseBody);
}
public function testFormLoadsProperPrefix()
{
$conditions = [
'1--1' => [
'type' => \Magento\CatalogWidget\Model\Rule\Condition\Product::class,
'attribute' => 'description',
'operator' => '{}',
'value' => 'foo',
],
'1--2' => [
'type' => \Magento\CatalogWidget\Model\Rule\Condition\Combine::class,
'aggregator' => 'all',
'value' => '1',
'new_child' => '',
],
'1--2--1' => [
'type' => \Magento\CatalogWidget\Model\Rule\Condition\Product::class,
'attribute' => 'amounts',
'operator' => '==',
'value' => '123',
],
];
$this->getRequest()
->setParams([
'form_namespace' => 'test_namespace',
'prefix' => 'myprefix',
])
->setPostValue([
'conditions' => json_encode($conditions),
]);
$this->dispatch('backend/pagebuilder/form/element_productconditions');
$responseBody = $this->getResponse()->getBody();
// Assert the combine form has form-part
$expected = 'name="parameters[myprefix][1--2][value]"';
$this->assertStringContainsString($expected, $responseBody);
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\PageBuilder\Controller\Adminhtml\Template;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Framework\App\Request\Http as HttpRequest;
use Magento\PageBuilder\Api\TemplateRepositoryInterface;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\ObjectManager;
/**
* Perform tests upon Template delete controller
*
* @magentoAppArea adminhtml
*/
class DeleteTest extends \Magento\TestFramework\TestCase\AbstractBackendController
{
/**
* @var ObjectManager
*/
private $objectManager;
/**
* @var \Magento\PageBuilder\Controller\Adminhtml\Template\Delete
*/
private $deleteController;
/**
* @var TemplateRepositoryInterface
*/
private $templateRepository;
/**
* @var SearchCriteriaBuilder
*/
private $searchCriteriaBuilder;
/**
* @inheritDoc
*/
protected function setUp(): void
{
parent::setUp();
$this->objectManager = Bootstrap::getObjectManager();
$this->templateRepository = $this->objectManager->get(TemplateRepositoryInterface::class);
$this->searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class);
$this->deleteController = $this->objectManager->create(
\Magento\PageBuilder\Controller\Adminhtml\Template\Delete::class
);
}
/**
* Test deleting a content type
*
* @magentoDataFixture Magento/PageBuilder/_files/template/template.php
*/
public function testDeleteAction()
{
$findTemplate = $this->templateRepository->getList(
$this->searchCriteriaBuilder->addFilter('name', 'Test Template')->create()
);
$this->assertEquals(1, $findTemplate->getTotalCount());
$items = $findTemplate->getItems();
$templateId = reset($items)->getId();
$this->getRequest()->setPostValue(['template_id' => $templateId])->setMethod(HttpRequest::METHOD_POST);
$this->deleteController->execute();
$this->expectExceptionMessage('Template with id "' . $templateId . '" does not exist.');
$this->templateRepository->get($templateId);
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\PageBuilder\Controller\Adminhtml\Template;
use Magento\Framework\App\Request\Http as HttpRequest;
use Magento\Framework\Filesystem;
use Magento\Framework\Filesystem\Directory\Write;
use Magento\Framework\Filesystem\DriverInterface;
use Magento\Framework\Image\Adapter\Gd2;
use Magento\Framework\Image\AdapterFactory;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\MediaStorage\Helper\File\Storage\Database;
use Magento\PageBuilder\Api\TemplateRepositoryInterface;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\ObjectManager;
/**
* Perform tests upon Template save controller
*
* @magentoAppArea adminhtml
*/
class SaveTest extends \Magento\TestFramework\TestCase\AbstractBackendController
{
/**
* @var TemplateRepositoryInterface
*/
private $templateRepository;
/**
* @var ObjectManager
*/
private $objectManager;
/**
* @var Write|\PHPUnit\Framework\MockObject\MockObject
*/
private $directoryWrite;
/**
* @var Filesystem|\PHPUnit\Framework\MockObject\MockObject
*/
private $filesystem;
/**
* @var Database|\PHPUnit\Framework\MockObject\MockObject
*/
private $mediaStorage;
/**
* @var \Magento\PageBuilder\Controller\Adminhtml\Template\Save
*/
private $saveController;
/**
* @var Json
*/
private $serializer;
/**
* @var Gd2|\PHPUnit\Framework\MockObject\MockObject
*/
private $imageAdapter;
/**
* @var AdapterFactory|\PHPUnit\Framework\MockObject\MockObject
*/
private $imageAdapterFactory;
/**
* @inheritDoc
*/
protected function setUp(): void
{
parent::setUp();
$this->objectManager = Bootstrap::getObjectManager();
$this->templateRepository = $this->objectManager->get(TemplateRepositoryInterface::class);
$this->directoryWrite = $this->getMockBuilder(Write::class)
->disableOriginalConstructor()
->getMock();
$this->filesystem = $this->getMockBuilder(Filesystem::class)
->setMethods(['getDirectoryWrite'])
->disableOriginalConstructor()
->getMock();
$this->mediaStorage = $this->getMockBuilder(Database::class)
->setMethods(['checkDbUsage', 'saveFile'])
->disableOriginalConstructor()
->getMock();
$this->imageAdapter = $this->getMockBuilder(Gd2::class)
->setMethods(['open', 'resize', 'save'])
->disableOriginalConstructor()
->getMock();
$this->imageAdapterFactory = $this->getMockBuilder(AdapterFactory::class)
->setMethods(['create'])
->disableOriginalConstructor()
->getMock();
$this->saveController = $this->objectManager->create(
\Magento\PageBuilder\Controller\Adminhtml\Template\Save::class,
[
'filesystem' => $this->filesystem,
'mediaStorage' => $this->mediaStorage,
'imageAdapterFactory' => $this->imageAdapterFactory
]
);
$this->serializer = $this->objectManager->get(Json::class);
}
/**
* Test saving a template using the controller
*
* @magentoDbIsolation enabled
*/
public function testSaveAction()
{
$this->directoryWrite->expects(self::atLeastOnce())->method('getAbsolutePath')
->with('.template-manager')
->willReturn('absolute/path/.template-manager/');
$this->directoryWrite->expects(self::atLeastOnce())->method('create')
->with('absolute/path/.template-manager/');
$driver = $this->getMockBuilder(DriverInterface::class)
->disableOriginalConstructor()
->getMock();
$this->directoryWrite->expects(self::atLeastOnce())->method('getDriver')
->willReturn($driver);
$this->directoryWrite->expects(self::atLeastOnce())->method('getRelativePath')
->willReturn('.template-manager/automatedtemplate');
$driver->expects(self::atLeastOnce())->method('filePutContents');
$this->imageAdapterFactory->expects($this->once())
->method('create')
->willReturn($this->imageAdapter);
$this->imageAdapter->expects($this->once())
->method('save')
->with(
$this->stringContains('-thumb.jpg')
);
$this->filesystem->expects($this->once())
->method('getDirectoryWrite')
->willReturn($this->directoryWrite);
$post = [
'name' => 'Automated Template',
'template' => '<div data-content-type="row"></div>',
'createdFor' => 'any',
// phpcs:disable Generic.Files.LineLength
'previewImage' => 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAZABkDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAn/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AJVAAAAAAAA//9k='
];
$this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST);
$response = $this->saveController->execute();
$response->renderResult($this->getResponse());
$this->assertEquals(
'application/json',
$this->getResponse()->getHeader('Content-Type')->getFieldValue()
);
$response = $this->serializer->unserialize($this->getResponse()->getBody());
$this->assertNotNull($response['status']);
$this->assertEquals(
'ok',
$response['status'],
isset($response['message']) ? $response['message'] : null
);
$template = $this->templateRepository->get($response['data']['id']);
$this->assertEquals('Automated Template', $template->getName());
$this->assertEquals('<div data-content-type="row"></div>', $template->getTemplate());
$this->assertEquals('any', $template->getCreatedFor());
$this->assertStringContainsString('.template-manager/automatedtemplate', $template->getPreviewImage());
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\PageBuilder\Model\Config;
use Magento\TestFramework\Helper\Bootstrap;
class AllowedParentTest extends \PHPUnit\Framework\TestCase
{
/**
* @var \Magento\PageBuilder\Model\Config\ContentType\Reader
*/
private $model;
/**
* @var \Magento\PageBuilder\Model\Config\FileResolver|\PHPUnit\Framework\MockObject\MockObject
*/
private $fileResolverMock;
protected function setUp(): void
{
$objectManager = Bootstrap::getObjectManager();
$this->fileResolverMock = $this->createMock(
\Magento\PageBuilder\Model\Config\FileResolver::class
);
$this->model = $objectManager->create(
\Magento\PageBuilder\Model\Config\ContentType\Reader::class,
['fileResolver' => $this->fileResolverMock]
);
}
public function testParentsAndChildrenConvertToAllowedParents()
{
$filePath = '/../../_files/allowed_parent/';
$fileList = [
file_get_contents(__DIR__ . $filePath . 'parents_and_children_allow.xml'),
file_get_contents(__DIR__ . $filePath . 'parents_and_children_deny.xml'),
file_get_contents(__DIR__ . $filePath . 'parents_allow.xml'),
file_get_contents(__DIR__ . $filePath . 'parents_deny.xml'),
file_get_contents(__DIR__ . $filePath . 'children_allow.xml'),
file_get_contents(__DIR__ . $filePath . 'children_deny.xml'),
file_get_contents(__DIR__ . $filePath . 'no_parents_and_children.xml'),
file_get_contents(__DIR__ . $filePath . 'parents_allow_with_parent.xml'),
file_get_contents(__DIR__ . $filePath . 'parents_deny_with_parent.xml'),
file_get_contents(__DIR__ . $filePath . 'children_allow_with_child.xml'),
file_get_contents(__DIR__ . $filePath . 'children_deny_with_child.xml'),
];
$this->fileResolverMock->expects($this->once())
->method('get')
->with('content_type/*.xml', 'global')
->willReturn($fileList);
$expected = include __DIR__ . '/../../_files/allowed_parent/expected_merged_array.php';
$this->assertEquals($expected, $this->model->read('global'));
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\PageBuilder\Model\Config\ContentType;
use Magento\TestFramework\Helper\Bootstrap;
class AppearanceTest extends \PHPUnit\Framework\TestCase
{
/**
* @var \Magento\TestFramework\ObjectManager
*/
private $objectManager;
/**
* @var \Magento\PageBuilder\Model\Config\ContentType\Reader
*/
private $contentTypesReader;
protected function setUp(): void
{
parent::setUp();
$this->objectManager = Bootstrap::getObjectManager();
$this->contentTypesReader = $this->objectManager->create(
\Magento\PageBuilder\Model\Config\ContentType\Reader::class
);
}
/**
* @magentoAppArea adminhtml
*/
public function testContentTypeAndFormConfigurationAppearancesMatch()
{
$contentTypes = $this->contentTypesReader->read();
foreach ($contentTypes as $configName => $type) {
$form = $type['form'] ?? null;
if (!$form) {
continue;
}
// get appearance names in content type config
$contentTypeAppearanceNames = array_keys($type['appearances'] ?? []);
sort($contentTypeAppearanceNames);
// get appearance names in ui component form config
$uiReader = $this->objectManager->create(
\Magento\Ui\Config\Reader::class,
['fileName' => $form . '.xml']
);
$formData = $uiReader->read();
$fieldSet = $formData['children']['appearance_fieldset'];
$field = $fieldSet['children']['appearance'];
$appearanceOptions = $this->objectManager->get(
$field['arguments']['data']['item']['options']['value']
)->toOptionArray();
$uiComponentFormAppearanceNames = array_column($appearanceOptions, 'value');
sort($uiComponentFormAppearanceNames);
$this->assertEquals(
$contentTypeAppearanceNames,
$uiComponentFormAppearanceNames,
'appearances do not match in form "' . $form . '" and config type "' . $configName . '"'
);
}
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\PageBuilder\Model\Config\ContentType;
use Magento\TestFramework\Helper\Bootstrap;
class ReaderTest extends \PHPUnit\Framework\TestCase
{
/**
* @var \Magento\PageBuilder\Model\Config\ContentType\Reader
*/
private $model;
/**
* @var \Magento\PageBuilder\Model\Config\FileResolver|\PHPUnit\Framework\MockObject\MockObject
*/
private $fileResolverMock;
protected function setUp(): void
{
$objectManager = Bootstrap::getObjectManager();
$this->fileResolverMock = $this->createMock(
\Magento\PageBuilder\Model\Config\FileResolver::class
);
$this->model = $objectManager->create(
\Magento\PageBuilder\Model\Config\ContentType\Reader::class,
['fileResolver' => $this->fileResolverMock]
);
}
public function testPartial()
{
$this->expectException(\Magento\Framework\Exception\LocalizedException::class);
$file = file_get_contents(__DIR__ . '/../../../_files/content_type/type3_content_type2.xml');
$this->fileResolverMock->expects($this->once())
->method('get')
->willReturn([$file]);
$this->model->read('global');
}
public function testMergeCompleteAndPartial()
{
$fileList = [
file_get_contents(__DIR__ . '/../../../_files/content_type/type3_content_type1.xml'),
file_get_contents(__DIR__ . '/../../../_files/content_type/type3_content_type2.xml'),
];
$this->fileResolverMock->expects($this->once())
->method('get')
->with('content_type/*.xml', 'global')
->willReturn($fileList);
$expected = include __DIR__ . '/../../../_files/content_type/type3_expected_merged_array.php';
$this->assertEquals($expected, $this->model->read('global'));
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\PageBuilder\Model\Config\MenuSection;
use Magento\TestFramework\Helper\Bootstrap;
class ReaderTest extends \PHPUnit\Framework\TestCase
{
/**
* @var \Magento\PageBuilder\Model\Config\MenuSection\Reader
*/
private $model;
/**
* @var \Magento\PageBuilder\Model\Config\FileResolver|\PHPUnit\Framework\MockObject\MockObject
*/
private $fileResolverMock;
protected function setUp(): void
{
$objectManager = Bootstrap::getObjectManager();
$this->fileResolverMock = $this->createMock(
\Magento\PageBuilder\Model\Config\FileResolver::class
);
$this->model = $objectManager->create(
\Magento\PageBuilder\Model\Config\MenuSection\Reader::class,
['fileResolver' => $this->fileResolverMock]
);
}
public function testPartial()
{
$this->expectException(\Magento\Framework\Exception\LocalizedException::class);
$file = file_get_contents(__DIR__ . '/../../../_files/content_type/menu_section3.xml');
$this->fileResolverMock->expects($this->once())
->method('get')
->willReturn([$file]);
$this->model->read('global');
}
public function testMergeCompleteAndPartial()
{
$fileList = [
file_get_contents(__DIR__ . '/../../../_files/content_type/menu_section1.xml'),
file_get_contents(__DIR__ . '/../../../_files/content_type/menu_section2.xml'),
file_get_contents(__DIR__ . '/../../../_files/content_type/menu_section3.xml'),
];
$this->fileResolverMock->expects($this->once())
->method('get')
->with('menu_section.xml', 'global')
->willReturn($fileList);
$expected = include __DIR__ . '/../../../_files/content_type/menu_sections_expected_merged_array.php';
$this->assertEquals($expected, $this->model->read('global'));
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\PageBuilder\Model\Config;
use Magento\TestFramework\Helper\Bootstrap;
class ReaderTest extends \PHPUnit\Framework\TestCase
{
/**
* @var \Magento\Framework\ObjectManagerInterface
*/
private $objectManager;
/**
* @var \Magento\PageBuilder\Model\Config\CompositeReader
*/
private $model;
/**
* @var \Magento\PageBuilder\Model\Config\FileResolver|\PHPUnit\Framework\MockObject\MockObject
*/
private $menuSectionsFileResolverMock;
/**
* @var \Magento\PageBuilder\Model\Config\FileResolver|\PHPUnit\Framework\MockObject\MockObject
*/
private $contentTypesFileResolverMock;
protected function setUp(): void
{
$this->objectManager = Bootstrap::getObjectManager();
$this->menuSectionsFileResolverMock = $this->createMock(
\Magento\PageBuilder\Model\Config\FileResolver::class
);
$this->contentTypesFileResolverMock = $this->createMock(
\Magento\PageBuilder\Model\Config\FileResolver::class
);
$menuSectionsReader = $this->objectManager->create(
\Magento\PageBuilder\Model\Config\MenuSection\Reader::class,
['fileResolver' => $this->menuSectionsFileResolverMock]
);
$contentTypesReader = $this->objectManager->create(
\Magento\PageBuilder\Model\Config\ContentType\Reader::class,
['fileResolver' => $this->contentTypesFileResolverMock]
);
$this->model = $this->objectManager->create(
\Magento\PageBuilder\Model\Config\CompositeReader::class,
['readers' => [$menuSectionsReader, $contentTypesReader]]
);
}
public function testMerge()
{
$menuSectionsFileList = [
file_get_contents(__DIR__ . '/../../_files/content_type/menu_section1.xml'),
file_get_contents(__DIR__ . '/../../_files/content_type/menu_section2.xml'),
file_get_contents(__DIR__ . '/../../_files/content_type/menu_section3.xml')
];
$contentTypesFiles = [
file_get_contents(__DIR__ . '/../../_files/content_type/type1_content_type1.xml'),
file_get_contents(__DIR__ . '/../../_files/content_type/type1_content_type2.xml'),
file_get_contents(__DIR__ . '/../../_files/content_type/type2_content_type1.xml'),
file_get_contents(__DIR__ . '/../../_files/content_type/type2_content_type2.xml'),
file_get_contents(__DIR__ . '/../../_files/content_type/type3_content_type1.xml'),
file_get_contents(__DIR__ . '/../../_files/content_type/type3_content_type2.xml')
];
$this->menuSectionsFileResolverMock->expects($this->any())
->method('get')
->with('menu_section.xml', 'global')
->willReturn($menuSectionsFileList);
$this->contentTypesFileResolverMock->expects($this->any())
->method('get')
->with('content_type/*.xml', 'global')
->willReturn($contentTypesFiles);
$expected = include __DIR__ . '/../../_files/content_type/expected_merged_array.php';
$this->assertEquals($expected, $this->model->read());
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\PageBuilder\Model\Dom;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\PageBuilder\Model\Dom\Adapter\HtmlDocumentInterface;
use Magento\PageBuilder\Model\Dom\Adapter\ElementInterface;
use Magento\PageBuilder\Model\Dom\HtmlDocument;
use PHPUnit\Framework\TestCase;
class ElementTest extends TestCase
{
/**
* @var ObjectManagerInterface
*/
private $objectManager;
/**
* @inheritdoc
*/
protected function setUp(): void
{
$this->objectManager = Bootstrap::getObjectManager();
}
/**
* Tests the removeStyle function
*
* @dataProvider removeStylesDataProvider
* @param string $elementData
* @param string $styleProperty
* @param string $expectedResult
*/
public function testRemoveStyle(string $elementData, string $styleProperty, string $expectedResult)
{
$document = $this->objectManager->create(
HtmlDocumentInterface::class,
[ 'document' => $elementData ]
);
/** @var ElementInterface $element */
$element = $document->querySelector('div');
$this->assertEquals($expectedResult, $element->removeStyle($styleProperty));
}
public function removeStylesDataProvider()
{
// phpcs:disable Generic.Files.LineLength.TooLong
return [
[
'<div data-element="inner" style="border-style: none; border-width: 1px; border-radius: 0px; margin: 0px 0px 10px; padding: 10px;">',
'margin',
'border-style: none; border-width: 1px; border-radius: 0px; padding: 10px;',
],
[
'<div data-element="inner" style="border-style: none; border-width: 1px; border-radius: 0px; padding: 10px;">',
'margin',
'border-style: none; border-width: 1px; border-radius: 0px; padding: 10px;',
],
[
'<div data-element="inner">',
'margin',
'',
],
];
// phpcs:enable Generic.Files.LineLength.TooLong
}
/**
* Tests the addStyle function
*
* @dataProvider addStyleDataProvider
* @param string $elementData
* @param string $styleProperty
* @param string $styleValue
* @param string $expectedResult
*/
public function testAddStyle(string $elementData, string $styleProperty, string $styleValue, string $expectedResult)
{
$document = $this->objectManager->create(
HtmlDocumentInterface::class,
[ 'document' => $elementData ]
);
/** @var ElementInterface $element */
$element = $document->querySelector('div');
$this->assertEquals($expectedResult, $element->addStyle($styleProperty, $styleValue));
}
public function addStyleDataProvider()
{
// phpcs:disable Generic.Files.LineLength.TooLong
return [
[
'<div data-element="inner" style="border-style: none; border-width: 1px; border-radius: 0px; padding: 10px;">',
'margin',
'10px',
'margin: 10px; border-style: none; border-width: 1px; border-radius: 0px; padding: 10px;',
],
[
'<div data-element="inner" style="border-style: none; border-width: 1px; border-radius: 0px; margin: 0px 0px 10px; padding: 10px;">',
'margin',
'10px',
'margin: 10px; border-style: none; border-width: 1px; border-radius: 0px; padding: 10px;',
],
[
'<div data-element="inner">',
'margin',
'10px',
'margin: 10px; ',
],
];
// phpcs:enable Generic.Files.LineLength.TooLong
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\PageBuilder\Model\Filter;
use Magento\TestFramework\ObjectManager;
class TemplateTest extends \PHPUnit\Framework\TestCase
{
/**
* @var Template
*/
private $templateFilter;
protected function setUp(): void
{
$this->templateFilter = ObjectManager::getInstance()->create(Template::class);
}
/**
* @param string $results
* @param bool $contains
* @param string $value
* @dataProvider getFilterForDataProvider
*/
public function testFilterFor(string $results, bool $contains, string $value)
{
$contains ?
self::assertStringContainsString($results, $this->templateFilter->filter($value)) :
self::assertStringNotContainsString($results, $this->templateFilter->filter($value));
}
/**
* @return array
*/
public function getFilterForDataProvider() : array
{
$template = <<<TEMPLATE
<div data-content-type="row" data-appearance="contained" data-element="main">
<div data-enable-parallax="0" data-parallax-speed="0.5"
data-background-images="{\&quot;desktop_image\&quot;:\&quot;{{media url=jb-decorating.jpg}}\&quot;}"
data-element="inner" style="justify-content: flex-start; display: flex; flex-direction: column;
background-position: center center; background-size: cover; background-repeat: repeat;
background-attachment: scroll; border-style: none; border-width: 1px; border-radius: 0px; min-height: 350px;
margin: 0px 0px 10px; padding: 10px;"></div>
</div>
TEMPLATE;
$template2 = <<<TEMPLATE
<div data-content-type="row" data-element="main" data-appearance="contained">
<div style="background-position: center; border-width: 1px; border-style: none; margin: 0px 0px 10px;
padding: 10px; border-radius: 0px; background-repeat: repeat; background-attachment: scroll; display: flex;
min-height: 350px; background-size: cover; flex-direction: column; justify-content: flex-start;"
data-element="inner" data-background-images='{\"desktop_image\":\"{{media url=jb-decorating.jpg}}\"}'
data-parallax-speed="0.5" data-enable-parallax="0"></div>
</div>
TEMPLATE;
$template3 = <<<TEMPLATE
<div data-content-type="row" data-element="main" data-appearance="contained">
<div style="background-position: center; border-width: 1px; border-style: none; margin: 0px 0px 10px;
padding: 10px; border-radius: 0px; background-repeat: repeat; background-attachment: scroll; display: flex;
min-height: 350px; background-size: cover; flex-direction: column; justify-content: flex-start;"
data-element="inner" data-background-images='{}' data-parallax-speed="0.5" data-enable-parallax="0"></div>
</div>
TEMPLATE;
$expectedResult = <<<EXPECTED_RESULT
<style type="text/css">.background-image-
EXPECTED_RESULT;
$expectedResult2 = <<<EXPECTED_RESULT
class="background-image-
EXPECTED_RESULT;
return [
[
$expectedResult,
true,
$template
],
[
$expectedResult2,
true,
$template
],
[
$expectedResult,
true,
$template2
],
[
$expectedResult2,
true,
$template2
],
[
$expectedResult,
false,
$template3
],
[
$expectedResult2,
false,
$template3
],
];
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\PageBuilder\Model\Stage\Config;
use Magento\TestFramework\Helper\Bootstrap;
class UiComponentConfigTest extends \PHPUnit\Framework\TestCase
{
/**
* @var \Magento\Framework\ObjectManagerInterface
*/
private $objectManager;
/**
* @var \Magento\Framework\Config\DataInterfaceFactory|\PHPUnit\Framework\MockObject\MockObject
*/
private $dataInterfaceFactoryMock;
/**
* @var \Magento\PageBuilder\Model\Stage\Config\UiComponentConfig
*/
private $model;
protected function setUp(): void
{
$this->objectManager = Bootstrap::getObjectManager();
$this->dataInterfaceFactoryMock = $this->getMockBuilder(\Magento\Framework\Config\DataInterfaceFactory::class)
->setMethods(['create'])
->disableOriginalConstructor()
->getMock();
$this->model = $this->objectManager->create(
\Magento\PageBuilder\Model\Stage\Config\UiComponentConfig::class,
[
'configFactory' => $this->dataInterfaceFactoryMock
]
);
}
/**
* Verify getFields will return the expected output given an example UI component configuration
*
* @param array $uiConfig
* @param array $expectedFields
* @dataProvider uiConfigDataProvider
*/
public function testGetFields(array $uiConfig, array $expectedFields)
{
$uiConfigMock = $this->createMock(\Magento\Framework\Config\DataInterface::class);
$this->dataInterfaceFactoryMock->expects($this->once())
->method('create')
->willReturn($uiConfigMock);
$uiConfigMock->expects($this->once())
->method('get')
->willReturn($uiConfig);
$this->assertEquals($this->model->getFields('test_form'), $expectedFields);
}
/**
* Provide UI component config with expected output for 2 test cases, one with a normal field and another field
* which sets a dataScope
*
* @return array
*/
public function uiConfigDataProvider() : array
{
return [
[
[
'children' => [
'test_field_name' => [
'arguments' => [
'data' => [
'config' => [
'formElement' => 'text',
'componentType' => 'field',
'default' => 'default_value',
'visible' => 1,
'required' => 1,
'label' => __('Test Field Name')
]
]
]
],
]
],
[
'test_field_name' => [
'default' => 'default_value'
]
]
],
[
[
'children' => [
'test_datascope_name' => [
'arguments' => [
'data' => [
'config' => [
'dataScope' => 'namespace.test_datascope_name',
'formElement' => 'text',
'componentType' => 'field',
'default' => 'default_value',
'visible' => 1,
'required' => 1,
'label' => __('Test DataScope Name')
]
]
]
]
]
],
[
'namespace' => [
'test_datascope_name' => [
'default' => 'default_value'
]
]
]
]
];
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\PageBuilder\Model\Stage;
use Magento\Framework\Session\SessionManagerInterface;
use Magento\Store\Api\Data\StoreInterface;
use Magento\Store\Model\StoreManagerInterface;
use Magento\TestFramework\Helper\Bootstrap;
use PHPUnit\Framework\TestCase;
/**
* Test config for page builder
*
* @magentoAppIsolation enabled
* @magentoAppArea adminhtml
*/
class ConfigTest extends TestCase
{
/**
* @var Config
*/
private $model;
/**
* @var StoreManagerInterface
*/
private $storeManager;
/**
* @var int
*/
private $currentStoreId;
/**
* @inheritDoc
*/
protected function setUp(): void
{
$objectManager = Bootstrap::getObjectManager();
$this->model = $objectManager->get(Config::class);
$this->storeManager = $objectManager->get(StoreManagerInterface::class);
$this->currentStoreId = $this->storeManager->getStore()->getId();
}
/**
* @inheritdoc
*/
protected function tearDown(): void
{
$this->storeManager->setCurrentStore($this->currentStoreId);
parent::tearDown();
}
/**
* Test that "media_url" should be the same as backend media URL
*
* @magentoDataFixture Magento/Store/_files/second_website_with_store_group_and_store.php
* @magentoConfigFixture admin/url/use_custom_path 1
* @magentoConfigFixture admin/url/custom_path secret
* @magentoConfigFixture admin/url/use_custom 1
* @magentoConfigFixture admin/url/custom https://backend.magento.test/
* @magentoConfigFixture admin_store web/secure/base_url https://backend.magento.test/
* @magentoConfigFixture admin_store web/unsecure/base_url http://backend.magento.test/
* @magentoConfigFixture fixture_second_store_store web/unsecure/base_url http://website2.magento.test/
* @magentoConfigFixture fixture_second_store_store web/secure/base_url https://website2.magento.test/
* @param string $store
* @param string $mediaUrl
* @dataProvider storeDataProvider
*/
public function testMediaUrlShouldBeTheSameAsBackendMediaURL(string $store, string $mediaUrl): void
{
$this->storeManager->setCurrentStore($store);
$this->assertEquals($mediaUrl, $this->model->getConfig()['media_url']);
}
/**
* @return array
*/
public function storeDataProvider(): array
{
return [
[
'admin',
'http://backend.magento.test/media/'
],
[
'default',
'http://backend.magento.test/media/'
],
[
'fixture_second_store',
'http://backend.magento.test/media/'
],
];
}
/**
* Test that page builder config should not be cached across different sessions if secret key is used in URLs
*
* @magentoConfigFixture admin/security/use_form_key 1
*/
public function testConfigShouldNotBeCachedAcrossDifferentSessions(): void
{
$config = $this->model->getConfig();
$this->startNewSession();
$this->assertNotEquals($config, $this->model->getConfig());
}
/**
* Test that page builder config should be cached within same session
*
* @magentoConfigFixture admin/security/use_form_key 1
*/
public function testConfigShouldBeCachedWithinSameSession(): void
{
$config = $this->model->getConfig();
$this->assertEquals($config, $this->model->getConfig());
}
/**
* @param string|null $sessionId
*/
private function startNewSession(string $sessionId = null): void
{
/** @var SessionManagerInterface $session */
$session = Bootstrap::getObjectManager()->get(SessionManagerInterface::class);
// close current session and cleanup session variable
if ($session->isSessionExists()) {
$session->writeClose();
$session->clearStorage();
}
// open new session
$session->setSessionId($sessionId ?? uniqid('session-' . time() . '-'));
$session->start();
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\PageBuilder\Model\Stage\Renderer;
class CmsStaticBlockTest extends \PHPUnit\Framework\TestCase
{
/**
* @magentoDataFixture Magento/PageBuilder/_files/block_with_script.php
* @magentoDataFixture Magento/Variable/_files/variable.php
* @magentoAppArea frontend
*/
public function testRender()
{
/** @var \Magento\Cms\Model\Block $cmsBlock */
$cmsBlock = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
\Magento\Cms\Model\Block::class
);
$cmsBlock->load('block_with_script', 'identifier');
$blockRenderer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
\Magento\PageBuilder\Model\Stage\Renderer\CmsStaticBlock::class
);
$result = $blockRenderer->render([
'block_id' => $cmsBlock->getData('block_id'),
'directive' => $cmsBlock->getContent(),
]);
$this->assertArrayHasKey('content', $result);
$content = $result['content'];
$this->assertStringNotContainsString('<script>', $content);
$this->assertStringContainsString('<p>Custom variable: "HTML Value".</p>', $content);
$this->assertStringNotContainsString('<html>', $content);
$this->assertStringNotContainsString('<!DOCTYPE', $content);
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\PageBuilder\Model\Stage;
use Magento\TestFramework\Helper\Bootstrap;
class RendererPoolTest extends \PHPUnit\Framework\TestCase
{
/**
* @var \Magento\Framework\ObjectManagerInterface
*/
private $objectManager;
/**
* @var \Magento\PageBuilder\Model\Stage\RendererPool
*/
private $model;
protected function setUp(): void
{
$this->objectManager = Bootstrap::getObjectManager();
$this->model = $this->objectManager->create(
\Magento\PageBuilder\Model\Stage\RendererPool::class
);
}
public function testGetRenderer()
{
$renderer = $this->model->getRenderer('test');
$this->assertEquals(['content' => 'Test Content'], $renderer->render([]));
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\PageBuilder\Plugin\Filter;
use Magento\Store\Model\Store;
use Magento\Widget\Model\Template\Filter as TemplateFilter;
use Magento\TestFramework\Helper\Bootstrap;
/**
* @magentoAppArea frontend
*/
class TemplatePluginTest extends \PHPUnit\Framework\TestCase
{
/**
* @var \Magento\Framework\ObjectManagerInterface
*/
private $objectManager;
/**
* @var TemplateFilter
*/
private $templateFilter;
protected function setUp(): void
{
$this->objectManager = Bootstrap::getObjectManager();
$this->templateFilter = $this->objectManager->get(TemplateFilter::class);
// set store id to 0 to recognize that escaping is required in custom variable
$this->templateFilter->setStoreId(Store::DEFAULT_STORE_ID);
}
/**
* @param string $preFiltered
* @param string $postFiltered
* @param string $preFilteredBasename
* @dataProvider filterDataProvider
* @magentoDataFixture Magento/PageBuilder/_files/custom_variable_xss.php
* @magentoDbIsolation enabled
*/
public function testFiltering(string $preFiltered, string $postFiltered, string $preFilteredBasename)
{
$this->assertEquals(
$this->formatHtml($postFiltered),
$this->formatHtml($this->templateFilter->filter($preFiltered)),
"Failed asserting that two strings are equal after filtering $preFilteredBasename"
);
}
/**
* @return array
*/
public function filterDataProvider(): array
{
$preFilteredFiles = glob(__DIR__ . '/../../_files/template_plugin/*pre_filter*');
$dataProviderArgs = [];
foreach ($preFilteredFiles as $preFilteredFile) {
$preFilteredBasename = basename($preFilteredFile);
$postFilteredFile = pathinfo($preFilteredFile, PATHINFO_DIRNAME) . '/' . str_replace(
'pre_filter',
'post_filter',
$preFilteredBasename
);
$dataProviderArgs[] = [
file_get_contents($preFilteredFile),
file_get_contents($postFilteredFile),
$preFilteredBasename
];
}
return $dataProviderArgs;
}
/**
* Strip whitespace from the HTML to conduct a fairer comparison
*
* @param string $html
* @return string|string[]|null
*/
private function formatHtml(string $html): string
{
return preg_replace('/(?<=>)\s+|\s+(?=<)/m', '', $html);
}
}
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\PageBuilder\Ui\Component\UrlInput\Page;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\Cms\Model\ResourceModel\Page\CollectionFactory;
class OptionsTest extends \PHPUnit\Framework\TestCase
{
/**
* @magentoDataFixture Magento/Cms/_files/pages.php
*/
public function testToOptionArray() : void
{
$expectedResult = $this->prepareExpectedResult();
/** @var Options $cmsOptions */
$cmsOptions = Bootstrap::getObjectManager()->get(Options::class);
$result = $cmsOptions->toOptionArray();
$this->assertEquals($expectedResult, $result);
}
private function prepareExpectedResult() : array
{
$options = [];
$collection = Bootstrap::getObjectManager()->get(CollectionFactory::class)->create();
foreach ($collection as $item) {
$pageId = $item->getId();
$options[$pageId] = [
'value' => $pageId,
'label' => $item->getTitle(),
'identifier' => sprintf(__('ID: %s'), $pageId)
];
}
return $options;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/content_type.xsd">
<type name="children_allow" translate="label" sortOrder="5" label="Type 5" icon="pagebuilder-type-icon" component="Path/to/component" form="pagebuilder_type_form" menu_section="menu_section">
<children default_policy="allow"/>
</type>
</config>
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/content_type.xsd">
<type name="children_allow_with_child" translate="label" sortOrder="10" label="Type 10" icon="pagebuilder-type-icon" component="Path/to/component" form="pagebuilder_type_form" menu_section="menu_section">
<children default_policy="allow">
<child name="parents_allow" policy="deny"/>
<child name="children_deny" policy="allow"/>
<child name="no_parents_and_children" policy="deny"/>
</children>
</type>
</config>
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/content_type.xsd">
<type name="children_deny" translate="label" sortOrder="6" label="Type 6" icon="pagebuilder-type-icon" component="Path/to/component" form="pagebuilder_type_form" menu_section="menu_section">
<children default_policy="deny"/>
</type>
</config>
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/content_type.xsd">
<type name="children_deny_with_child" translate="label" sortOrder="11" label="Type 11" icon="pagebuilder-type-icon" component="Path/to/component" form="pagebuilder_type_form" menu_section="menu_section">
<children default_policy="deny">
<child name="parents_deny" policy="allow"/>
<child name="children_allow" policy="deny"/>
<child name="no_parents_and_children" policy="allow"/>
</children>
</type>
</config>
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
return [
'types' => [
'parents_and_children_allow' => [
'name' => 'parents_and_children_allow',
'label' => 'Type 1',
'icon' => 'pagebuilder-type-icon',
'component' => 'Path/to/component',
'form' => 'pagebuilder_type_form',
'menu_section' => 'menu_section',
'sortOrder' => '1',
'translate' => 'label',
'allowed_parents' => [
0 => 'parents_and_children_allow',
1 => 'parents_and_children_deny',
2 => 'parents_allow',
3 => 'parents_deny',
4 => 'children_allow',
5 => 'children_deny',
6 => 'no_parents_and_children',
7 => 'parents_allow_with_parent',
8 => 'parents_deny_with_parent',
9 => 'children_allow_with_child',
10 => 'children_deny_with_child'
]
],
'parents_and_children_deny' => [
'name' => 'parents_and_children_deny',
'label' => 'Type 2',
'icon' => 'pagebuilder-type-icon',
'component' => 'Path/to/component',
'form' => 'pagebuilder_type_form',
'menu_section' => 'menu_section',
'sortOrder' => '2',
'translate' => 'label',
'allowed_parents' => []
],
'parents_allow' => [
'name' => 'parents_allow',
'label' => 'Type 3',
'icon' => 'pagebuilder-type-icon',
'component' => 'Path/to/component',
'form' => 'pagebuilder_type_form',
'menu_section' => 'menu_section',
'sortOrder' => '3',
'translate' => 'label',
'allowed_parents' => [
0 => 'parents_and_children_allow',
1 => 'parents_and_children_deny',
2 => 'parents_allow',
3 => 'parents_deny',
4 => 'children_allow',
5 => 'children_deny',
6 => 'no_parents_and_children',
7 => 'parents_allow_with_parent',
8 => 'parents_deny_with_parent',
9 => 'children_allow_with_child',
10 => 'children_deny_with_child'
]
],
'parents_deny' => [
'name' => 'parents_deny',
'label' => 'Type 4',
'icon' => 'pagebuilder-type-icon',
'component' => 'Path/to/component',
'form' => 'pagebuilder_type_form',
'menu_section' => 'menu_section',
'sortOrder' => '4',
'translate' => 'label',
'allowed_parents' => [],
],
'children_allow' => [
'name' => 'children_allow',
'label' => 'Type 5',
'icon' => 'pagebuilder-type-icon',
'component' => 'Path/to/component',
'form' => 'pagebuilder_type_form',
'menu_section' => 'menu_section',
'sortOrder' => '5',
'translate' => 'label',
'allowed_parents' => [
0 => 'parents_and_children_allow',
1 => 'children_allow',
2 => 'children_allow_with_child'
]
],
'children_deny' => [
'name' => 'children_deny',
'label' => 'Type 6',
'icon' => 'pagebuilder-type-icon',
'component' => 'Path/to/component',
'form' => 'pagebuilder_type_form',
'menu_section' => 'menu_section',
'sortOrder' => '6',
'translate' => 'label',
'allowed_parents' => [
0 => 'parents_and_children_allow',
1 => 'children_allow',
2 => 'children_allow_with_child'
]
],
'no_parents_and_children' => [
'name' => 'no_parents_and_children',
'label' => 'Type 7',
'icon' => 'pagebuilder-type-icon',
'component' => 'Path/to/component',
'form' => 'pagebuilder_type_form',
'menu_section' => 'menu_section',
'sortOrder' => '7',
'translate' => 'label',
'allowed_parents' => [
0 => 'parents_and_children_allow',
1 => 'children_allow',
2 => 'children_deny_with_child'
]
],
'parents_allow_with_parent' => [
'name' => 'parents_allow_with_parent',
'label' => 'Type 8',
'icon' => 'pagebuilder-type-icon',
'component' => 'Path/to/component',
'form' => 'pagebuilder_type_form',
'menu_section' => 'menu_section',
'sortOrder' => '8',
'translate' => 'label',
'allowed_parents' => [
0 => 'parents_and_children_allow',
1 => 'parents_and_children_deny',
2 => 'parents_allow',
3 => 'parents_deny',
4 => 'children_allow',
5 => 'children_deny',
6 => 'no_parents_and_children',
7 => 'parents_deny_with_parent',
8 => 'children_allow_with_child',
9 => 'children_deny_with_child',
10 => 'stage'
]
],
'parents_deny_with_parent' => [
'name' => 'parents_deny_with_parent',
'label' => 'Type 9',
'icon' => 'pagebuilder-type-icon',
'component' => 'Path/to/component',
'form' => 'pagebuilder_type_form',
'menu_section' => 'menu_section',
'sortOrder' => '9',
'translate' => 'label',
'allowed_parents' => [
0 => 'stage'
]
],
'children_allow_with_child' => [
'name' => 'children_allow_with_child',
'label' => 'Type 10',
'icon' => 'pagebuilder-type-icon',
'component' => 'Path/to/component',
'form' => 'pagebuilder_type_form',
'menu_section' => 'menu_section',
'sortOrder' => '10',
'translate' => 'label',
'allowed_parents' => [
0 => 'parents_and_children_allow',
1 => 'children_allow',
2 => 'children_allow_with_child'
]
],
'children_deny_with_child' => [
'name' => 'children_deny_with_child',
'label' => 'Type 11',
'icon' => 'pagebuilder-type-icon',
'component' => 'Path/to/component',
'form' => 'pagebuilder_type_form',
'menu_section' => 'menu_section',
'sortOrder' => '11',
'translate' => 'label',
'allowed_parents' => [
0 => 'parents_and_children_allow',
1 => 'children_allow',
2 => 'children_allow_with_child'
]
]
]
];
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/content_type.xsd">
<type name="no_parents_and_children" translate="label" sortOrder="7" label="Type 7" icon="pagebuilder-type-icon" component="Path/to/component" form="pagebuilder_type_form" menu_section="menu_section">
</type>
</config>
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/content_type.xsd">
<type name="parents_allow" translate="label" sortOrder="3" label="Type 3" icon="pagebuilder-type-icon" component="Path/to/component" form="pagebuilder_type_form" menu_section="menu_section">
<parents default_policy="allow"/>
</type>
</config>
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/content_type.xsd">
<type name="parents_allow_with_parent" translate="label" sortOrder="8" label="Type 8" icon="pagebuilder-type-icon" component="Path/to/component" form="pagebuilder_type_form" menu_section="menu_section">
<parents default_policy="allow">
<parent name="stage" policy="allow"/>
<parent name="parents_allow_with_parent" policy="deny"/>
</parents>
</type>
</config>
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/content_type.xsd">
<type name="parents_and_children_allow" translate="label" sortOrder="1" label="Type 1" icon="pagebuilder-type-icon" component="Path/to/component" form="pagebuilder_type_form" menu_section="menu_section">
<parents default_policy="allow"/>
<children default_policy="allow"/>
</type>
</config>
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/content_type.xsd">
<type name="parents_and_children_deny" translate="label" sortOrder="2" label="Type 2" icon="pagebuilder-type-icon" component="Path/to/component" form="pagebuilder_type_form" menu_section="menu_section">
<parents default_policy="deny"/>
<children default_policy="deny"/>
</type>
</config>
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/content_type.xsd">
<type name="parents_deny" translate="label" sortOrder="4" label="Type 4" icon="pagebuilder-type-icon" component="Path/to/component" form="pagebuilder_type_form" menu_section="menu_section">
<parents default_policy="deny"/>
</type>
</config>
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/content_type.xsd">
<type name="parents_deny_with_parent" translate="label" sortOrder="9" label="Type 9" icon="pagebuilder-type-icon" component="Path/to/component" form="pagebuilder_type_form" menu_section="menu_section">
<parents default_policy="deny">
<parent name="stage" policy="allow"/>
<parent name="parents_deny_with_parent" policy="deny"/>
</parents>
</type>
</config>
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
/** @var $block \Magento\Cms\Model\Block */
$block = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Cms\Model\Block::class);
$block->setTitle(
'CMS Block Title'
)->setIdentifier(
'block_with_script'
)->setContent(
'<h1>Fixture Block Title</h1>
<a href="{{store url=""}}">store url</a>
<p>Config value: "{{config path="web/unsecure/base_url"}}".</p>
<p>Custom variable: "{{customvar code="variable_code"}}".</p>
<script>alert("hello")</script>
'
)->setIsActive(
1
)->setStores(
[
\Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(
\Magento\Store\Model\StoreManagerInterface::class
)->getStore()->getId()
]
)->save();
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/menu_section.xsd">
<menu_section name="menu_section1" translate="label" sortOrder="1" label="Menu Section 1"/>
<menu_section name="menu_section2" translate="label" sortOrder="2" label="Menu Section 2"/>
</config>
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/menu_section.xsd">
<menu_section name="menu_section2" translate="label" sortOrder="3" label="Menu Section 2 Label"/>
<menu_section name="menu_section3" translate="label" sortOrder="2" label="Menu Section 3"/>
</config>
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/menu_section.xsd">
<menu_section name="menu_section3" label="Menu Section 3 Label"/>
</config>
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
return [
'menu_sections' => [
'menu_section1' => [
'label' => 'Menu Section 1',
'sortOrder' => '1',
'name' => 'menu_section1',
'translate' => 'label'
],
'menu_section2' => [
'label' => 'Menu Section 2 Label',
'sortOrder' => '3',
'name' => 'menu_section2',
'translate' => 'label'
],
'menu_section3' => [
'label' => 'Menu Section 3 Label',
'sortOrder' => '2',
'name' => 'menu_section3',
'translate' => 'label'
]
]
];
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/content_type.xsd">
<type name="type1" translate="label" sortOrder="1" label="Type 1" icon="pagebuilder-type1-icon" component="Path/to/component" master_component="Path/to/master/component" form="pagebuilder_type1_form" menu_section="menu_section1">
</type>
</config>
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/content_type.xsd">
<type name="type1" translate="label" label="Type 1 Label" icon="pagebuilder-type1-custom-icon" component="Path/to/component" preview_component="Path/to/preview/component" form="pagebuilder_type1_custom_form" menu_section="menu_section2">
<appearances>
<appearance name="default" default="true" preview_template="Path/to/preview/template" master_template="Path/to/render/template" reader="Path/to/reader">
<elements>
<element name="main">
<style name="style_converter" source="converter" converter="Path/to/converter"/>
<style name="style_no_converter" source="no_converter"/>
<attribute name="name" source="data-content-type"/>
<css name="css_classes"/>
</element>
</elements>
</appearance>
</appearances>
<additional_data>
<item name="config1" xsi:type="array">
<item name="settingWithTypeString" xsi:type="string">string</item>
<item name="settingWithTypeBooleanTrue" xsi:type="boolean">true</item>
<item name="settingWithTypeBooleanFalse" xsi:type="boolean">false</item>
<item name="settingWithTypeInteger" xsi:type="number">20</item>
<item name="settingWithTypeNull" xsi:type="null"/>
<item name="settingWithTypeNumber2" xsi:type="number">-90</item>
<item name="settingWithTypeObject" xsi:type="object">Magento\TestModulePageBuilderExtensionPoints\Model\Config\ContentType\AdditionalData\Provider\TestData</item>
</item>
<item name="config2" xsi:type="array">
<item name="arrayConfig" xsi:type="array">
<item name="settingWithTypeString" xsi:type="string">string</item>
<item name="settingWithTypeBooleanTrue" xsi:type="boolean">true</item>
<item name="settingWithTypeBooleanFalse" xsi:type="boolean">false</item>
<item name="settingWithTypeInteger" xsi:type="number">20</item>
<item name="settingWithTypeNull" xsi:type="null"/>
<item name="settingWithTypeNumber2" xsi:type="number">-90</item>
<item name="settingWithTypeObject" xsi:type="object">Magento\TestModulePageBuilderExtensionPoints\Model\Config\ContentType\AdditionalData\Provider\TestData</item>
</item>
</item>
<item name="settingWithTypeString" xsi:type="string">string</item>
<item name="settingWithTypeBooleanTrue" xsi:type="boolean">true</item>
<item name="settingWithTypeBooleanFalse" xsi:type="boolean">false</item>
<item name="settingWithTypeInteger" xsi:type="number">20</item>
<item name="settingWithTypeNull" xsi:type="null"/>
<item name="settingWithTypeNumber2" xsi:type="number">-90</item>
<item name="settingWithTypeObject" xsi:type="object">Magento\TestModulePageBuilderExtensionPoints\Model\Config\ContentType\AdditionalData\Provider\TestData</item>
</additional_data>
</type>
</config>
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/content_type.xsd">
<type name="type2" translate="label" label="Type 2" icon="pagebuilder-type2-icon" component="Path/to/component" preview_component="Path/to/preview/component" form="pagebuilder_type2_form" menu_section="menu_section1">
<is_system>true</is_system>
<parents default_policy="deny">
<parent name="stage" policy="allow"/>
</parents>
<children default_policy="allow"/>
<appearances>
<appearance name="default" default="true" preview_template="Path/to/preview/template" master_template="Path/to/render/template" reader="Path/to/reader">
<data name="data1">value1</data>
<data name="data2">value2</data>
<elements>
<element name="first_element">
<style name="style_no_converter" source="no_converter"/>
<style name="style_attributes_change" source="attributes_change" converter="Path/to/converter" preview_converter="Path/to/preview/converter" persistence_mode="read"/>
<style name="style_attributes_add" source="attributes_add"/>
<style name="original_complex" reader="Path/to/reader"/>
<style name="complex_style_attributes_change" converter="Path/to/converter" preview_converter="Path/to/preview/converter" reader="Path/to/reader" persistence_mode="read"/>
<style name="complex_style_attributes_add" reader="Path/to/reader"/>
<static_style source="original_static" value="original_value"/>
<static_style source="static_style_attributes_change" value="value"/>
<attribute name="name" source="data-content-type"/>
<attribute name="attribute_change" source="data-change" converter="Path/to/converter" preview_converter="Path/to/preview/converter" persistence_mode="read"/>
<attribute name="attribute_add" source="attribute_add"/>
<attribute name="original_complex_attribute" reader="Path/to/reader"/>
<attribute name="complex_attribute_change" converter="Path/to/converter" preview_converter="Path/to/preview/converter" reader="Path/to/reader" persistence_mode="read"/>
<attribute name="complex_attribute_add" reader="Path/to/reader"/>
<static_attribute source="original_static" value="original_value"/>
<static_attribute source="static_attribute_change" value="value"/>
<tag name="tag" converter="Path/to/converter"/>
<html name="html"/>
<css name="css_classes">
<filter>
<class source="class-name"/>
</filter>
</css>
</element>
<element name="second_element">
<style name="style_no_converter" source="no_converter"/>
<attribute name="name" source="data-content-type"/>
<css name="css_classes"/>
</element>
</elements>
<converters>
<converter name="converter1" component="Path/to/converter">
<config>
<item name="item1" value="value1"/>
<item name="change_value" value="value2"/>
</config>
</converter>
<converter name="converter2" component="Path/to/converter">
<config>
<item name="item1" value="value1"/>
</config>
</converter>
</converters>
<form>Path/to/form</form>
</appearance>
<appearance name="appearance1" default="false" preview_template="Path/to/preview/template" master_template="Path/to/render/template" reader="Path/to/reader">
<elements>
<element name="main">
<style name="style_converter" source="converter" converter="Path/to/converter"/>
<style name="style_no_converter" source="no_converter"/>
<attribute name="name" source="data-content-type"/>
<css name="css_classes"/>
</element>
</elements>
<form>Path/to/form</form>
</appearance>
<appearance name="appearance2" default="false" preview_template="Path/to/preview/template" master_template="Path/to/render/template" reader="Path/to/reader"/>
<appearance name="appearance3" default="false" preview_template="Path/to/preview/template" master_template="Path/to/render/template"/>
</appearances>
</type>
</config>
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/content_type.xsd">
<type name="type2" translate="label" sortOrder="2" label="Type 2 Label" icon="pagebuilder-type2-custom-icon" component="Path/to/custom/component" preview_component="Path/to/preview/custom/component" form="pagebuilder_type2_custom_form" menu_section="menu_section2">
<is_system>false</is_system>
<parents>
<parent name="stage" policy="deny"/>
<parent name="type1" policy="allow"/>
</parents>
<children default_policy="deny">
<child name="type3" policy="allow"/>
</children>
<appearances>
<appearance name="default" default="true" preview_template="Path/to/preview/custom/template" master_template="Path/to/render/custom/template" reader="Path/to/custom/reader">
<data name="data1">custom_value</data>
<data name="data3">value3</data>
<elements>
<element name="first_element">
<style name="style_attributes_change" source="custom_name" converter="Path/to/custom/converter" preview_converter="Path/to/preview/custom/converter" persistence_mode="write"/>
<style name="style_attributes_add" source="attributes_add" converter="Path/to/custom/converter" preview_converter="Path/to/preview/custom/converter" persistence_mode="write"/>
<style name="new_style" source="new-style"/>
<style name="complex_style_attributes_change" converter="Path/to/custom/converter" preview_converter="Path/to/preview/custom/converter" reader="Path/to/custom/reader" persistence_mode="write"/>
<style name="complex_style_attributes_add" converter="Path/to/custom/converter" preview_converter="Path/to/preview/custom/converter" reader="Path/to/custom/reader" persistence_mode="write"/>
<style name="new_complex" reader="Path/to/reader"/>
<static_style source="static_style_attributes_change" value="custom_value"/>
<static_style source="new_static" value="new-value"/>
<attribute name="attribute_change" source="data-custom" converter="Path/to/custom/converter" preview_converter="Path/to/preview/custom/converter" persistence_mode="write"/>
<attribute name="attribute_add" source="attribute_add" converter="Path/to/custom/converter" preview_converter="Path/to/preview/custom/converter" persistence_mode="write"/>
<attribute name="new_attribute" source="data-new" converter="Path/to/custom/converter" preview_converter="Path/to/preview/custom/converter" persistence_mode="write"/>
<attribute name="complex_attribute_change" converter="Path/to/custom/converter" preview_converter="Path/to/preview/custom/converter" reader="Path/to/custom/reader" persistence_mode="write"/>
<attribute name="complex_attribute_add" converter="Path/to/custom/converter" preview_converter="Path/to/preview/custom/converter" reader="Path/to/custom/reader" persistence_mode="write"/>
<attribute name="new_complex_attribute" reader="Path/to/reader"/>
<static_attribute source="static_attribute_change" value="custom_value"/>
<static_attribute source="new_static" value="new-value"/>
<tag name="tag" converter="Path/to/custom/converter"/>
<html name="html"/>
<css name="css_classes">
<filter>
<class source="new-class"/>
</filter>
</css>
</element>
<element name="third_element">
<style name="style_no_converter" source="no_converter"/>
<attribute name="name" source="data-content-type"/>
<css name="css_classes"/>
</element>
</elements>
<converters>
<converter name="converter1" component="Path/to/custom/converter">
<config>
<item name="change_value" value="custom_value"/>
<item name="new_value" value="value3"/>
</config>
</converter>
<converter name="converter3" component="Path/to/custom/converter">
<config>
<item name="item1" value="value1"/>
</config>
</converter>
</converters>
<form>Path/to/custom/form</form>
</appearance>
<appearance name="appearance4" preview_template="Path/to/preview/template" master_template="Path/to/render/template" reader="Path/to/reader">
<elements>
<element name="main">
<style name="style_converter" source="converter" converter="Path/to/converter"/>
<style name="style_no_converter" source="no_converter"/>
<attribute name="name" source="data-content-type"/>
<css name="css_classes"/>
</element>
</elements>
<form>Path/to/custom/form</form>
</appearance>
</appearances>
</type>
</config>
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/content_type.xsd">
<type name="type3" translate="label" sortOrder="3" label="Type 3" icon="pagebuilder-type3-icon" component="Path/to/custom/component" form="pagebuilder_type3_form" menu_section="menu_section1">
<parents default_policy="deny">
<parent name="stage" policy="allow"/>
</parents>
<children default_policy="allow"/>
<appearances>
<appearance name="default" default="true" preview_template="Path/to/preview/template" master_template="Path/to/render/template" reader="Path/to/reader">
<elements>
<element name="main">
<style name="style_converter" source="converter" converter="Path/to/converter"/>
<style name="style_no_converter" source="no_converter"/>
<attribute name="name" source="data-content-type"/>
<css name="css_classes"/>
</element>
</elements>
</appearance>
</appearances>
</type>
</config>
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/content_type.xsd">
<type name="type3" label="Custom Type 3"/>
</config>
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
return [
'types' => [
'type3' => [
'sortOrder' => '3',
'label' => 'Custom Type 3',
'icon' => 'pagebuilder-type3-icon',
'component' => 'Path/to/custom/component',
'form' => 'pagebuilder_type3_form',
'menu_section' => 'menu_section1',
'allowed_parents' => [
0 => 'stage'
],
'appearances' => [
'default' => [
'preview_template' => 'Path/to/preview/template',
'master_template' => 'Path/to/render/template',
'elements' => [
'main' => [
'style' => [
0 =>[
'var' => 'style_converter',
'name' => 'converter',
'converter' => 'Path/to/converter',
'preview_converter' => null,
'persistence_mode' => 'readwrite',
'reader' => 'Magento_PageBuilder/js/property/style-property-reader'
],
1 => [
'var' => 'style_no_converter',
'name' => 'no_converter',
'converter' => null,
'preview_converter' => null,
'persistence_mode' => 'readwrite',
'reader' => 'Magento_PageBuilder/js/property/style-property-reader'
]
],
'attributes' => [
0 => [
'var' => 'name',
'name' => 'data-content-type',
'converter' => null,
'persistence_mode' => 'readwrite',
'preview_converter' => null,
'reader' => 'Magento_PageBuilder/js/property/attribute-reader'
]
],
'tag' => [],
'html' => [],
'css' => [
'var' => 'css_classes',
'filter' => [],
],
],
],
'converters' => [],
'default' => 'true',
'reader' => 'Path/to/reader'
]
],
'name' => 'type3',
'translate' => 'label'
]
]
];
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
$this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
/** @var \Magento\Variable\Model\ResourceModel\Variable $variableResource */
$variableResource = $this->objectManager->get(\Magento\Variable\Model\ResourceModel\Variable::class);
/** @var \Magento\Variable\Model\Variable $variable */
$variable = $this->objectManager->get(\Magento\Variable\Model\Variable::class);
$variable->setData([
'code' => 'xssVariable',
'name' => 'xssVariable',
'html_value' => '<img src=x onerror="alert(0)">',
'plain_value' => '<img src=x onerror="alert(0)">',
]);
$variableResource->save($variable);
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
/* @var \Magento\PageBuilder\Model\Template $template */
$template = $objectManager->create(\Magento\PageBuilder\Model\Template::class);
$template->setName('Test Template')
->setPreviewImage('preview-image.jpg')
->setCreatedFor('any')
->setTemplate('<div data-content-type"row"></div>');
/* @var \Magento\PageBuilder\Model\TemplateRepository $templateRepository */
$templateRepository = $objectManager->create(\Magento\PageBuilder\Model\TemplateRepository::class);
$templateRepository->save($template);
<div data-content-type="html" data-decoded="true">&amp;<img src="http://example.com"><iframe width="560" height="315" src="https://www.youtube.com/embed/owsfdh4gxyc" frameborder="0" allowfullscreen></iframe></div>
\ No newline at end of file
<div data-content-type="html" data-decoded="true">&amp;<img src="http://example.com"><iframe width="560" height="315" src="https://www.youtube.com/embed/owsfdh4gxyc" frameborder="0" allowfullscreen></iframe></div>
\ No newline at end of file
<div data-content-type="html" class="&gt;'&gt;&quot;&gt;&lt;img src=x onerror=alert(0)&gt;" data-decoded="true">
Nothing to see here
</div>
\ No newline at end of file
<div data-content-type="html" class="&gt;'&gt;&quot;&gt;&lt;img src=x onerror=alert(0)&gt;">
Nothing to see here
</div>
\ No newline at end of file
<div data-content-type="html" data-appearance="default" data-element="main" style="border-style: none; border-width: 1px; border-radius: 0px; margin: 0px; padding: 0px;" data-decoded="true"><div id="fb-root"></div>
<script async defer src="https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v3.2&appId=1&autoLogAppEvents=1"></script>
<div class="fb-page" data-href="https://www.facebook.com/facebook" data-tabs="timeline" data-small-header="false"
data-adapt-container-width="true" data-hide-cover="false" data-show-facepile="true"><blockquote
cite="https://www.facebook.com/facebook" class="fb-xfbml-parse-ignore"><a
href="https://www.facebook.com/facebook">Facebook</a></blockquote></div>
</div>
\ No newline at end of file
<div data-content-type="html" data-appearance="default" data-element="main"
style="border-style: none; border-width: 1px; border-radius: 0px; margin: 0px; padding: 0px;">&lt;div id="fb-root"&gt;&lt;/div&gt;
&lt;script async defer src="https://connect.facebook.net/en_US/sdk.js#xfbml=1&amp;version=v3.2&amp;appId=1&amp;autoLogAppEvents=1"&gt;&lt;/script&gt;
&lt;div class="fb-page" data-href="https://www.facebook.com/facebook" data-tabs="timeline" data-small-header="false"
data-adapt-container-width="true" data-hide-cover="false" data-show-facepile="true"&gt;&lt;blockquote
cite="https://www.facebook.com/facebook" class="fb-xfbml-parse-ignore"&gt;&lt;a
href="https://www.facebook.com/facebook"&gt;Facebook&lt;/a&gt;&lt;/blockquote&gt;&lt;/div&gt;
</div>
\ No newline at end of file
<div data-content-type="html" data-decoded="true">
<img src="http://example.com">
<iframe width="560" height="315" src="https://www.youtube.com/embed/owsfdh4gxyc" frameborder="0" allowfullscreen></iframe>
</div>
\ No newline at end of file
<div data-content-type="html">
&lt;img src=&quot;http://example.com&quot;&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/owsfdh4gxyc&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;
</div>
\ No newline at end of file
<div data-content-type="html" data-decoded="true">
<img src="http://example.com">
<iframe width="560" height="315" src="https://www.youtube.com/embed/owsfdh4gxyc" frameborder="0" allowfullscreen></iframe>
<div data-content-type="html">
<img src="http://example.com">
<iframe width="560" height="315" src="https://www.youtube.com/embed/owsfdh4gxyc" frameborder="0" allowfullscreen></iframe>
&amp;
</div>
</div>
\ No newline at end of file
<div data-content-type="html">
&lt;img src=&quot;http://example.com&quot;&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/owsfdh4gxyc&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;
{{block class="Magento\Framework\View\Element\Template" template="Magento_TestModulePageBuilderExtensionPoints::html_content_type.phtml"}}
</div>
\ No newline at end of file
<div data-content-type="html" data-decoded="true">
<img src="http://example.com">
<iframe width="560" height="315" src="https://www.youtube.com/embed/owsfdh4gxyc" frameborder="0" allowfullscreen></iframe>
<div>
<img src="http://example.com/block">
</div>
</div>
\ No newline at end of file
<div data-content-type="html">
&lt;img src=&quot;http://example.com&quot;&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/owsfdh4gxyc&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;
{{block class="Magento\Framework\View\Element\Template" template="Magento_TestModulePageBuilderExtensionPoints::static_html.phtml"}}
</div>
\ No newline at end of file
<div data-content-type="html" data-decoded="true">
<img src="http://example.com">
<iframe width="560" height="315" src="https://www.youtube.com/embed/owsfdh4gxyc" frameborder="0" allowfullscreen></iframe>
Hello world
</div>
\ No newline at end of file
<div data-content-type="html">
&lt;img src=&quot;http://example.com&quot;&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/owsfdh4gxyc&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;
{{block class="Magento\Framework\View\Element\Template" template="Magento_TestModulePageBuilderExtensionPoints::static_text.phtml"}}
</div>
\ No newline at end of file
<div data-content-type="text">
Hello there
<div data-content-type="html" data-decoded="true">
<img src="http://example.com">
<iframe width="560" height="315" src="https://www.youtube.com/embed/owsfdh4gxyc" frameborder="0" allowfullscreen></iframe>
&amp;
</div>
</div>
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment