Files
piratepoet/mailpoet/lib/EmailEditor/Engine/Templates/Templates.php
Rostislav Wolny 1fab8a31f3 Ensure the template theme is always an object.
Sending it as an empty array was causing that deepmerge in emailCss hook was returning null.
It was happening for the general-email template which has no theme file
[MAILPOET-5641]
2024-05-22 11:36:09 +02:00

382 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php declare(strict_types = 1);
namespace MailPoet\EmailEditor\Engine\Templates;
use MailPoet\DI\ContainerWrapper;
use MailPoet\EmailEditor\Engine\EmailStylesSchema;
use MailPoet\EmailEditor\Engine\ThemeController;
use MailPoet\Validator\Builder;
use WP_Block_Template;
use WP_Error;
// phpcs:disable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
class Templates {
const MAILPOET_EMAIL_META_THEME_TYPE = 'mailpoet_email_theme';
const MAILPOET_TEMPLATE_EMPTY_THEME = ['version' => 2]; // The version 2 is important to merge themes correctly
private string $templateDirectory;
private string $pluginSlug;
private string $postType;
private array $themeJson = [];
public function __construct() {
$this->templateDirectory = dirname(__FILE__) . DIRECTORY_SEPARATOR;
$this->pluginSlug = 'mailpoet/mailpoet';
$this->postType = 'mailpoet_email';
}
public function initialize(): void {
// Since we cannot currently disable blocks in the editor for specific templates, disable templates when viewing site editor.
// @see https://github.com/WordPress/gutenberg/issues/41062
if (strstr(wp_unslash($_SERVER['REQUEST_URI'] ?? ''), 'site-editor.php') === false) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
add_filter('pre_get_block_file_template', [$this, 'getBlockFileTemplate'], 10, 3);
add_filter('get_block_templates', [$this, 'addBlockTemplates'], 10, 3);
add_filter('theme_templates', [$this, 'addThemeTemplates'], 10, 4); // Needed when saving post template association
add_filter('get_block_template', [$this, 'addBlockTemplateDetails'], 10, 1);
add_filter('rest_pre_insert_wp_template', [$this, 'trimPostContent'], 10, 2);
// Register custom post meta for mailpoet_email_theme
$this->registerTemplateThemeFields();
// Rest field for compiled CSS used in template preview
register_rest_field(
'wp_template',
'email_theme_css',
[
'get_callback' => [$this, 'getEmailThemePreviewCss'],
'update_callback' => null,
'schema' => Builder::string()->toArray(),
]
);
}
}
/**
* Trims post content when saving a template.
* We add empty space on client to force saving the content.
*/
public function trimPostContent($changes, \WP_REST_Request $request) {
if (strpos($request->get_route(), '/wp/v2/templates/mailpoet/mailpoet') === 0 && !empty($changes->post_content)) {
$changes->post_content = trim($changes->post_content);
}
return $changes;
}
public function getBlockTemplate($templateId) {
$templates = $this->getBlockTemplates();
return $templates[$templateId] ?? null;
}
public function getBlockTheme($templateId, $templateWpId = null) {
// First check if there is a user updated theme saved
if ($templateWpId) {
$theme = get_post_meta($templateWpId, self::MAILPOET_EMAIL_META_THEME_TYPE, true);
if (is_array($theme) && isset($theme['styles'])) {
return $theme;
}
}
// If there is no user edited theme, look for default template themes in files
$template_name_parts = explode('//', $templateId);
if (count($template_name_parts) < 2) {
return false;
}
list( $templatePrefix, $templateSlug ) = $template_name_parts;
if ($this->pluginSlug !== $templatePrefix) {
return false;
}
if (!isset($this->themeJson[$templateSlug])) {
$templatePath = $templateSlug . '.json';
$jsonFile = $this->templateDirectory . $templatePath;
if (file_exists($jsonFile)) {
$this->themeJson[$templateSlug] = json_decode((string)file_get_contents($jsonFile), true);
}
}
return $this->themeJson[$templateSlug] ?? self::MAILPOET_TEMPLATE_EMPTY_THEME;
}
public function getBlockFileTemplate($return, $templateId, $template_type) {
$template_name_parts = explode('//', $templateId);
if (count($template_name_parts) < 2) {
return $return;
}
list( $templatePrefix, $templateSlug ) = $template_name_parts;
if ($this->pluginSlug !== $templatePrefix) {
return $return;
}
$templatePath = $templateSlug . '.html';
if (!is_readable($this->templateDirectory . $templatePath)) {
return $return;
}
return $this->getBlockTemplateFromFile($templatePath);
}
public function addThemeTemplates($templates, $theme, $post, $post_type) {
if ($post_type && $post_type !== $this->postType) {
return $templates;
}
foreach ($this->getBlockTemplates() as $blockTemplate) {
$templates[$blockTemplate->slug] = $blockTemplate;
}
return $templates;
}
public function addBlockTemplates($query_result, $query, $template_type) {
if ('wp_template' !== $template_type) {
return $query_result;
}
$post_type = isset($query['post_type']) ? $query['post_type'] : '';
if ($post_type && $post_type !== $this->postType) {
return $query_result;
}
foreach ($this->getBlockTemplates() as $blockTemplate) {
$fits_slug_query = !isset($query['slug__in']) || in_array($blockTemplate->slug, $query['slug__in'], true);
$fits_area_query = !isset($query['area']) || ( property_exists($blockTemplate, 'area') && $blockTemplate->area === $query['area'] );
$should_include = $fits_slug_query && $fits_area_query;
if ($should_include) {
$query_result[] = $blockTemplate;
}
}
return $query_result;
}
/**
* Add details to templates in editor.
*
* @param WP_Block_Template $block_template Block template object.
* @return WP_Block_Template
*/
public function addBlockTemplateDetails($block_template) {
if (!$block_template) {
return $block_template;
}
if (empty($block_template->title) || $block_template->title === $block_template->slug) {
$block_template->title = $this->getBlockTemplateTitle($block_template->slug);
}
if (empty($block_template->description) || $block_template->description === $block_template->slug) {
$block_template->title = $this->getBlockTemplateDescription($block_template->slug);
}
return $block_template;
}
/**
* Gets block templates indexed by ID.
*/
public function getBlockTemplates() {
$file_templates = [
$this->getBlockTemplateFromFile('email-general.html'),
$this->getBlockTemplateFromFile('awesome-one.html'),
$this->getBlockTemplateFromFile('awesome-two.html'),
$this->getBlockTemplateFromFile('email-computing-mag.html'),
];
$custom_templates = $this->getCustomBlockTemplates();
$custom_template_ids = wp_list_pluck($custom_templates, 'id');
return array_column(
array_merge(
$custom_templates,
array_filter(
$file_templates,
function($blockTemplate) use ($custom_template_ids) {
return !in_array($blockTemplate->id, $custom_template_ids, true);
}
),
),
null,
'id'
);
}
private function getCustomBlockTemplates($slugs = [], $template_type = 'wp_template') {
$check_query_args = [
'post_type' => $template_type,
'posts_per_page' => -1,
'no_found_rows' => true,
'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
[
'taxonomy' => 'wp_theme',
'field' => 'name',
'terms' => [ $this->pluginSlug, get_stylesheet() ],
],
],
];
if (is_array($slugs) && count($slugs) > 0) {
$check_query_args['post_name__in'] = $slugs;
}
$check_query = new \WP_Query($check_query_args);
$custom_templates = $check_query->posts;
return array_map(
function($custom_template) {
return $this->buildBlockTemplateFromPost($custom_template);
},
$custom_templates
);
}
public function getBlockTemplateFromFile(string $template) {
$templateObject = $this->createNewBlockTemplateObject($template);
return $this->buildBlockTemplateFromFile($templateObject);
}
/**
* Generates CSS for preview of email theme
* They are applied in the preview BLockPreview in template selection
*/
public function getEmailThemePreviewCss($template): string {
$themeController = ContainerWrapper::getInstance()->get(ThemeController::class);
$editorTheme = clone $themeController->getTheme();
$templateTheme = $this->getBlockTheme($template['id'], $template['wp_id']);
if (is_array($templateTheme)) {
$editorTheme->merge(new \WP_Theme_JSON($templateTheme, 'custom'));
}
$additionalCSS = file_get_contents($this->templateDirectory . 'preview.css');
return $editorTheme->get_stylesheet() . $additionalCSS;
}
private function registerTemplateThemeFields(): void {
register_post_meta(
'wp_template',
self::MAILPOET_EMAIL_META_THEME_TYPE,
[
'show_in_rest' => [
'schema' => (new EmailStylesSchema())->getSchema(),
],
'single' => true,
'type' => 'object',
'default' => self::MAILPOET_TEMPLATE_EMPTY_THEME,
]
);
register_rest_field('wp_template', self::MAILPOET_EMAIL_META_THEME_TYPE, [
'get_callback' => function($object) {
return $this->getBlockTheme($object['id'], $object['wp_id']);
},
'update_callback' => function($value, $template) {
return update_post_meta($template->wp_id, self::MAILPOET_EMAIL_META_THEME_TYPE, $value);
},
'schema' => (new EmailStylesSchema())->getSchema(),
]);
}
private function createNewBlockTemplateObject(string $template) {
$template_slug = $this->getBlockTemplateSlugFromPath($template);
return (object)[
'slug' => $this->getBlockTemplateSlugFromPath($template),
'id' => $this->pluginSlug . '//' . $template_slug,
'path' => $this->templateDirectory . $template,
'type' => 'wp_template',
'theme' => $this->pluginSlug,
'source' => 'plugin',
'post_types' => [
$this->postType,
],
];
}
private function buildBlockTemplateFromPost($post) {
$terms = get_the_terms($post, 'wp_theme');
if (is_wp_error($terms)) {
return $terms;
}
if (!$terms) {
return new WP_Error('template_missing_theme', 'No theme is defined for this template.');
}
$theme = $terms[0]->name;
$template = new WP_Block_Template();
$template->wp_id = $post->ID;
$template->id = $theme . '//' . $post->post_name;
$template->theme = $theme;
$template->content = $post->post_content ? $post->post_content : '<p>empty</p>' ;
$template->slug = $post->post_name;
$template->source = 'custom';
$template->type = $post->post_type;
$template->description = $post->post_excerpt;
$template->title = $post->post_title;
$template->status = $post->post_status;
$template->has_theme_file = true;
$template->is_custom = true;
$template->post_types = [];
if ('wp_template_part' === $post->post_type) {
$type_terms = get_the_terms($post, 'wp_template_part_area');
if (!is_wp_error($type_terms) && false !== $type_terms) {
$template->area = $type_terms[0]->name;
}
}
if ($this->pluginSlug === $theme) {
$template->origin = 'plugin';
}
return $template;
}
private function buildBlockTemplateFromFile($templateObject): WP_Block_Template {
$template = new WP_Block_Template();
$template->id = $templateObject->id;
$template->theme = $templateObject->theme;
$template->content = (string)file_get_contents($templateObject->path);
$template->source = $templateObject->source;
$template->slug = $templateObject->slug;
$template->type = $templateObject->type;
$template->title = $this->getBlockTemplateTitle($templateObject->slug);
$template->description = $this->getBlockTemplateDescription($templateObject->slug);
$template->status = 'publish';
$template->has_theme_file = true;
$template->origin = $templateObject->source;
$template->post_types = $templateObject->post_types;
$template->is_custom = false; // Templates are only custom if they are loaded from the DB.
$template->area = 'uncategorized';
return $template;
}
private static function getBlockTemplateSlugFromPath($path) {
return basename($path, '.html');
}
private function getBlockTemplateTitle($template_slug) {
switch ($template_slug) {
case 'email-general':
return 'General Email';
default:
// Human friendly title converted from the slug.
return ucwords(preg_replace('/[\-_]/', ' ', $template_slug));
}
}
private function getBlockTemplateDescription($template_slug) {
switch ($template_slug) {
case 'email-general':
return 'A general template for emails.';
default:
return 'A template for emails.';
}
}
}