AbstractAutomationEmbed.php
5 months ago
Automation.php
2 years ago
AutomationAnalytics.php
2 months ago
AutomationEditor.php
2 months ago
AutomationFlowEmbed.php
6 months ago
AutomationPreviewEmbed.php
2 months ago
AutomationTemplates.php
2 years ago
CustomFields.php
2 months ago
DynamicSegments.php
2 months ago
ExperimentalFeatures.php
3 years ago
FormEditor.php
1 week ago
Forms.php
2 months ago
Help.php
2 months ago
Homepage.php
2 years ago
Landingpage.php
2 years ago
Logs.php
1 month ago
NewsletterEditor.php
2 months ago
Newsletters.php
2 months ago
Settings.php
5 months ago
StaticSegments.php
2 months ago
Subscribers.php
1 week ago
SubscribersExport.php
3 years ago
SubscribersImport.php
2 months ago
Tags.php
2 months ago
Upgrade.php
1 year ago
WelcomeWizard.php
2 months ago
WooCommerceSetup.php
2 months ago
index.php
3 years ago
FormEditor.php
428 lines
| 1 | <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing |
| 2 | |
| 3 | namespace MailPoet\AdminPages\Pages; |
| 4 | |
| 5 | if (!defined('ABSPATH')) exit; |
| 6 | |
| 7 | |
| 8 | use MailPoet\AdminPages\AssetsController; |
| 9 | use MailPoet\AdminPages\PageRenderer; |
| 10 | use MailPoet\API\JSON\ResponseBuilders\CustomFieldsResponseBuilder; |
| 11 | use MailPoet\Config\Localizer; |
| 12 | use MailPoet\CustomFields\CustomFieldsRepository; |
| 13 | use MailPoet\Entities\FormEntity; |
| 14 | use MailPoet\Entities\SegmentEntity; |
| 15 | use MailPoet\Form\Block; |
| 16 | use MailPoet\Form\FormsRepository; |
| 17 | use MailPoet\Form\Renderer as FormRenderer; |
| 18 | use MailPoet\Form\Templates\TemplateRepository; |
| 19 | use MailPoet\Form\Templates\Templates\Template10BelowPages; |
| 20 | use MailPoet\Form\Templates\Templates\Template10FixedBar; |
| 21 | use MailPoet\Form\Templates\Templates\Template10Popup; |
| 22 | use MailPoet\Form\Templates\Templates\Template10SlideIn; |
| 23 | use MailPoet\Form\Templates\Templates\Template10Widget; |
| 24 | use MailPoet\Form\Templates\Templates\Template11BelowPages; |
| 25 | use MailPoet\Form\Templates\Templates\Template11FixedBar; |
| 26 | use MailPoet\Form\Templates\Templates\Template11Popup; |
| 27 | use MailPoet\Form\Templates\Templates\Template11SlideIn; |
| 28 | use MailPoet\Form\Templates\Templates\Template11Widget; |
| 29 | use MailPoet\Form\Templates\Templates\Template12BelowPages; |
| 30 | use MailPoet\Form\Templates\Templates\Template12FixedBar; |
| 31 | use MailPoet\Form\Templates\Templates\Template12Popup; |
| 32 | use MailPoet\Form\Templates\Templates\Template12SlideIn; |
| 33 | use MailPoet\Form\Templates\Templates\Template12Widget; |
| 34 | use MailPoet\Form\Templates\Templates\Template13BelowPages; |
| 35 | use MailPoet\Form\Templates\Templates\Template13FixedBar; |
| 36 | use MailPoet\Form\Templates\Templates\Template13Popup; |
| 37 | use MailPoet\Form\Templates\Templates\Template13SlideIn; |
| 38 | use MailPoet\Form\Templates\Templates\Template13Widget; |
| 39 | use MailPoet\Form\Templates\Templates\Template14BelowPages; |
| 40 | use MailPoet\Form\Templates\Templates\Template14FixedBar; |
| 41 | use MailPoet\Form\Templates\Templates\Template14Popup; |
| 42 | use MailPoet\Form\Templates\Templates\Template14SlideIn; |
| 43 | use MailPoet\Form\Templates\Templates\Template14Widget; |
| 44 | use MailPoet\Form\Templates\Templates\Template17BelowPages; |
| 45 | use MailPoet\Form\Templates\Templates\Template17FixedBar; |
| 46 | use MailPoet\Form\Templates\Templates\Template17Popup; |
| 47 | use MailPoet\Form\Templates\Templates\Template17SlideIn; |
| 48 | use MailPoet\Form\Templates\Templates\Template17Widget; |
| 49 | use MailPoet\Form\Templates\Templates\Template18BelowPages; |
| 50 | use MailPoet\Form\Templates\Templates\Template18FixedBar; |
| 51 | use MailPoet\Form\Templates\Templates\Template18Popup; |
| 52 | use MailPoet\Form\Templates\Templates\Template18SlideIn; |
| 53 | use MailPoet\Form\Templates\Templates\Template18Widget; |
| 54 | use MailPoet\Form\Templates\Templates\Template1BelowPages; |
| 55 | use MailPoet\Form\Templates\Templates\Template1FixedBar; |
| 56 | use MailPoet\Form\Templates\Templates\Template1Popup; |
| 57 | use MailPoet\Form\Templates\Templates\Template1SlideIn; |
| 58 | use MailPoet\Form\Templates\Templates\Template1Widget; |
| 59 | use MailPoet\Form\Templates\Templates\Template3BelowPages; |
| 60 | use MailPoet\Form\Templates\Templates\Template3FixedBar; |
| 61 | use MailPoet\Form\Templates\Templates\Template3Popup; |
| 62 | use MailPoet\Form\Templates\Templates\Template3SlideIn; |
| 63 | use MailPoet\Form\Templates\Templates\Template3Widget; |
| 64 | use MailPoet\Form\Templates\Templates\Template4BelowPages; |
| 65 | use MailPoet\Form\Templates\Templates\Template4FixedBar; |
| 66 | use MailPoet\Form\Templates\Templates\Template4Popup; |
| 67 | use MailPoet\Form\Templates\Templates\Template4SlideIn; |
| 68 | use MailPoet\Form\Templates\Templates\Template4Widget; |
| 69 | use MailPoet\Form\Templates\Templates\Template6BelowPages; |
| 70 | use MailPoet\Form\Templates\Templates\Template6FixedBar; |
| 71 | use MailPoet\Form\Templates\Templates\Template6Popup; |
| 72 | use MailPoet\Form\Templates\Templates\Template6SlideIn; |
| 73 | use MailPoet\Form\Templates\Templates\Template6Widget; |
| 74 | use MailPoet\Form\Templates\Templates\Template7BelowPages; |
| 75 | use MailPoet\Form\Templates\Templates\Template7FixedBar; |
| 76 | use MailPoet\Form\Templates\Templates\Template7Popup; |
| 77 | use MailPoet\Form\Templates\Templates\Template7SlideIn; |
| 78 | use MailPoet\Form\Templates\Templates\Template7Widget; |
| 79 | use MailPoet\Form\Util\CustomFonts; |
| 80 | use MailPoet\Form\Util\Export; |
| 81 | use MailPoet\NotFoundException; |
| 82 | use MailPoet\Router\Endpoints\FormPreview; |
| 83 | use MailPoet\Router\Router; |
| 84 | use MailPoet\Segments\SegmentsSimpleListRepository; |
| 85 | use MailPoet\Settings\Pages; |
| 86 | use MailPoet\Settings\UserFlagsController; |
| 87 | use MailPoet\WP\AutocompletePostListLoader as WPPostListLoader; |
| 88 | use MailPoet\WP\Functions as WPFunctions; |
| 89 | |
| 90 | class FormEditor { |
| 91 | /** @var PageRenderer */ |
| 92 | private $pageRenderer; |
| 93 | |
| 94 | /** @var CustomFieldsRepository */ |
| 95 | private $customFieldsRepository; |
| 96 | |
| 97 | /** @var CustomFieldsResponseBuilder */ |
| 98 | private $customFieldsResponseBuilder; |
| 99 | |
| 100 | /** @var FormRenderer */ |
| 101 | private $formRenderer; |
| 102 | |
| 103 | /** @var Block\Date */ |
| 104 | private $dateBlock; |
| 105 | |
| 106 | /** @var WPFunctions */ |
| 107 | private $wp; |
| 108 | |
| 109 | /** @var Localizer */ |
| 110 | private $localizer; |
| 111 | |
| 112 | /** @var TemplateRepository */ |
| 113 | private $templatesRepository; |
| 114 | |
| 115 | /** @var UserFlagsController */ |
| 116 | private $userFlags; |
| 117 | |
| 118 | /** @var WPPostListLoader */ |
| 119 | private $wpPostListLoader; |
| 120 | |
| 121 | /** @var SegmentsSimpleListRepository */ |
| 122 | private $segmentsListRepository; |
| 123 | |
| 124 | /** @var FormsRepository */ |
| 125 | private $formsRepository; |
| 126 | |
| 127 | private $activeTemplates = [ |
| 128 | FormEntity::DISPLAY_TYPE_POPUP => [ |
| 129 | Template1Popup::ID, |
| 130 | Template3Popup::ID, |
| 131 | Template4Popup::ID, |
| 132 | Template6Popup::ID, |
| 133 | Template7Popup::ID, |
| 134 | Template10Popup::ID, |
| 135 | Template11Popup::ID, |
| 136 | Template12Popup::ID, |
| 137 | Template13Popup::ID, |
| 138 | Template14Popup::ID, |
| 139 | Template17Popup::ID, |
| 140 | Template18Popup::ID, |
| 141 | ], |
| 142 | FormEntity::DISPLAY_TYPE_SLIDE_IN => [ |
| 143 | Template1SlideIn::ID, |
| 144 | Template3SlideIn::ID, |
| 145 | Template4SlideIn::ID, |
| 146 | Template6SlideIn::ID, |
| 147 | Template7SlideIn::ID, |
| 148 | Template10SlideIn::ID, |
| 149 | Template11SlideIn::ID, |
| 150 | Template12SlideIn::ID, |
| 151 | Template13SlideIn::ID, |
| 152 | Template14SlideIn::ID, |
| 153 | Template17SlideIn::ID, |
| 154 | Template18SlideIn::ID, |
| 155 | ], |
| 156 | FormEntity::DISPLAY_TYPE_FIXED_BAR => [ |
| 157 | Template1FixedBar::ID, |
| 158 | Template3FixedBar::ID, |
| 159 | Template4FixedBar::ID, |
| 160 | Template6FixedBar::ID, |
| 161 | Template7FixedBar::ID, |
| 162 | Template10FixedBar::ID, |
| 163 | Template11FixedBar::ID, |
| 164 | Template12FixedBar::ID, |
| 165 | Template13FixedBar::ID, |
| 166 | Template14FixedBar::ID, |
| 167 | Template17FixedBar::ID, |
| 168 | Template18FixedBar::ID, |
| 169 | ], |
| 170 | FormEntity::DISPLAY_TYPE_BELOW_POST => [ |
| 171 | Template1BelowPages::ID, |
| 172 | Template3BelowPages::ID, |
| 173 | Template4BelowPages::ID, |
| 174 | Template6BelowPages::ID, |
| 175 | Template7BelowPages::ID, |
| 176 | Template10BelowPages::ID, |
| 177 | Template11BelowPages::ID, |
| 178 | Template12BelowPages::ID, |
| 179 | Template13BelowPages::ID, |
| 180 | Template14BelowPages::ID, |
| 181 | Template17BelowPages::ID, |
| 182 | Template18BelowPages::ID, |
| 183 | ], |
| 184 | FormEntity::DISPLAY_TYPE_OTHERS => [ |
| 185 | Template1Widget::ID, |
| 186 | Template3Widget::ID, |
| 187 | Template4Widget::ID, |
| 188 | Template6Widget::ID, |
| 189 | Template7Widget::ID, |
| 190 | Template10Widget::ID, |
| 191 | Template11Widget::ID, |
| 192 | Template12Widget::ID, |
| 193 | Template13Widget::ID, |
| 194 | Template14Widget::ID, |
| 195 | Template17Widget::ID, |
| 196 | Template18Widget::ID, |
| 197 | ], |
| 198 | ]; |
| 199 | |
| 200 | /** @var AssetsController */ |
| 201 | private $assetsController; |
| 202 | |
| 203 | public function __construct( |
| 204 | AssetsController $assetsController, |
| 205 | PageRenderer $pageRenderer, |
| 206 | CustomFieldsRepository $customFieldsRepository, |
| 207 | CustomFieldsResponseBuilder $customFieldsResponseBuilder, |
| 208 | FormRenderer $formRenderer, |
| 209 | Block\Date $dateBlock, |
| 210 | WPFunctions $wp, |
| 211 | Localizer $localizer, |
| 212 | UserFlagsController $userFlags, |
| 213 | WPPostListLoader $wpPostListLoader, |
| 214 | TemplateRepository $templateRepository, |
| 215 | FormsRepository $formsRepository, |
| 216 | SegmentsSimpleListRepository $segmentsListRepository |
| 217 | ) { |
| 218 | $this->assetsController = $assetsController; |
| 219 | $this->pageRenderer = $pageRenderer; |
| 220 | $this->customFieldsRepository = $customFieldsRepository; |
| 221 | $this->customFieldsResponseBuilder = $customFieldsResponseBuilder; |
| 222 | $this->formRenderer = $formRenderer; |
| 223 | $this->dateBlock = $dateBlock; |
| 224 | $this->wp = $wp; |
| 225 | $this->localizer = $localizer; |
| 226 | $this->templatesRepository = $templateRepository; |
| 227 | $this->userFlags = $userFlags; |
| 228 | $this->wpPostListLoader = $wpPostListLoader; |
| 229 | $this->segmentsListRepository = $segmentsListRepository; |
| 230 | $this->formsRepository = $formsRepository; |
| 231 | } |
| 232 | |
| 233 | public function render() { |
| 234 | if (!isset($_GET['id']) && !isset($_GET['action']) && !isset($_GET['template_id'])) { |
| 235 | $this->renderTemplateSelection(); |
| 236 | return; |
| 237 | } |
| 238 | if (is_string($_GET['template_id'] ?? null)) { |
| 239 | $template = $this->templatesRepository->getFormTemplate(sanitize_text_field(wp_unslash($_GET['template_id']))); |
| 240 | $form = $template->toFormEntity(); |
| 241 | } else { |
| 242 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- is_numeric guard plus int cast is the sanitization |
| 243 | $idParam = $_GET['id'] ?? null; |
| 244 | $form = $this->getFormData(is_numeric($idParam) ? (int)$idParam : 0); |
| 245 | } |
| 246 | $customFields = $this->customFieldsRepository->findAllActive(); |
| 247 | if (!$form instanceof FormEntity) { |
| 248 | throw new NotFoundException('Form does not exist'); |
| 249 | } |
| 250 | $dateTypes = $this->dateBlock->getDateTypes(); |
| 251 | $data = [ |
| 252 | 'form' => $form->toArray(), |
| 253 | 'form_exports' => [ |
| 254 | 'php' => Export::get('php'), |
| 255 | 'iframe' => Export::get('iframe'), |
| 256 | 'shortcode' => Export::get('shortcode'), |
| 257 | ], |
| 258 | 'segments' => $this->segmentsListRepository->getListWithSubscribedSubscribersCounts([SegmentEntity::TYPE_DEFAULT]), |
| 259 | 'styles' => $this->formRenderer->getCustomStyles($form), |
| 260 | 'date_types' => array_map(function ($label, $value) { |
| 261 | return [ |
| 262 | 'label' => $label, |
| 263 | 'value' => $value, |
| 264 | ]; |
| 265 | }, $dateTypes, array_keys($dateTypes)), |
| 266 | 'date_formats' => $this->dateBlock->getDateFormats(), |
| 267 | 'month_names' => $this->dateBlock->getMonthNames(), |
| 268 | 'custom_fields' => $this->customFieldsResponseBuilder->buildBatch($customFields), |
| 269 | 'editor_tutorial_seen' => $this->userFlags->get('form_editor_tutorial_seen'), |
| 270 | 'preview_page_url' => $this->getPreviewPageUrl(), |
| 271 | 'custom_fonts' => CustomFonts::FONTS, |
| 272 | 'editor_color_palette' => $this->getEditorColorPalette(), |
| 273 | 'translations' => $this->getGutenbergScriptsTranslations(), |
| 274 | 'posts' => $this->wpPostListLoader->getPosts(), |
| 275 | 'pages' => $this->wpPostListLoader->getPages(), |
| 276 | 'categories' => $this->wpPostListLoader->getCategories(), |
| 277 | 'tags' => $this->wpPostListLoader->getTags(), |
| 278 | 'products' => $this->wpPostListLoader->getProducts(), |
| 279 | 'product_categories' => $this->wpPostListLoader->getWooCommerceCategories(), |
| 280 | 'product_tags' => $this->wpPostListLoader->getWooCommerceTags(), |
| 281 | 'is_administrator' => $this->wp->currentUserCan('administrator'), |
| 282 | 'theme_support_widgets' => $this->wp->wpGetThemeSupport('widgets'), |
| 283 | 'theme_support_fse' => $this->wp->wpGetTheme()->is_block_theme(), |
| 284 | ]; |
| 285 | $this->wp->wpEnqueueMedia(); |
| 286 | $this->assetsController->setupFormEditorDependencies(); |
| 287 | $this->pageRenderer->displayPage('form/editor.html', $data); |
| 288 | } |
| 289 | |
| 290 | private function getEditorColorPalette(): array { |
| 291 | $palette = $this->normalizeColorPalette( |
| 292 | $this->wp->wpGetGlobalSettings(['color', 'palette', 'theme']) |
| 293 | ); |
| 294 | if ($palette) { |
| 295 | return $palette; |
| 296 | } |
| 297 | return $this->normalizeColorPalette( |
| 298 | $this->wp->wpGetThemeSupport('editor-color-palette') |
| 299 | ); |
| 300 | } |
| 301 | |
| 302 | private function normalizeColorPalette($palette): array { |
| 303 | if (!is_array($palette)) { |
| 304 | return []; |
| 305 | } |
| 306 | if ( |
| 307 | isset($palette[0]) |
| 308 | && is_array($palette[0]) |
| 309 | && !isset($palette[0]['color']) |
| 310 | ) { |
| 311 | $palette = $palette[0]; |
| 312 | } |
| 313 | |
| 314 | $normalizedPalette = []; |
| 315 | foreach ($palette as $color) { |
| 316 | if (!is_array($color)) { |
| 317 | continue; |
| 318 | } |
| 319 | $name = isset($color['name']) && is_scalar($color['name']) |
| 320 | ? $this->wp->sanitizeTextField((string)$color['name']) |
| 321 | : ''; |
| 322 | $slug = isset($color['slug']) && is_scalar($color['slug']) |
| 323 | ? $this->wp->sanitizeKey((string)$color['slug']) |
| 324 | : ''; |
| 325 | $colorValue = isset($color['color']) && is_scalar($color['color']) |
| 326 | ? $this->wp->sanitizeTextField((string)$color['color']) |
| 327 | : ''; |
| 328 | if (!$name || !$slug || !$colorValue) { |
| 329 | continue; |
| 330 | } |
| 331 | $normalizedPalette[] = [ |
| 332 | 'name' => $name, |
| 333 | 'slug' => $slug, |
| 334 | 'color' => $colorValue, |
| 335 | ]; |
| 336 | } |
| 337 | return $normalizedPalette; |
| 338 | } |
| 339 | |
| 340 | public function renderTemplateSelection() { |
| 341 | $templatesData = []; |
| 342 | foreach ($this->activeTemplates as $formType => $templateIds) { |
| 343 | $templateForms = $this->templatesRepository->getFormTemplates($this->activeTemplates[$formType]); |
| 344 | $templatesData[$formType] = []; |
| 345 | foreach ($templateForms as $templateId => $form) { |
| 346 | $templatesData[$formType][] = [ |
| 347 | 'id' => $templateId, |
| 348 | 'name' => $form->getName(), |
| 349 | 'thumbnail' => $form->getThumbnailUrl(), |
| 350 | ]; |
| 351 | } |
| 352 | } |
| 353 | $data = [ |
| 354 | 'templates' => $templatesData, |
| 355 | ]; |
| 356 | $this->assetsController->setupFormEditorDependencies(); |
| 357 | $this->pageRenderer->displayPage('form/template_selection.html', $data); |
| 358 | } |
| 359 | |
| 360 | private function getPreviewPageUrl() { |
| 361 | $mailpoetPage = Pages::getMailPoetPage(Pages::PAGE_SUBSCRIPTIONS); |
| 362 | if (!$mailpoetPage) { |
| 363 | return null; |
| 364 | } |
| 365 | $url = $this->wp->getPermalink($mailpoetPage); |
| 366 | $params = [ |
| 367 | Router::NAME, |
| 368 | 'endpoint=' . FormPreview::ENDPOINT, |
| 369 | 'action=' . FormPreview::ACTION_VIEW, |
| 370 | ]; |
| 371 | $url .= (parse_url($url, PHP_URL_QUERY) ? '&' : '?') . join('&', $params); |
| 372 | return $url; |
| 373 | } |
| 374 | |
| 375 | /** |
| 376 | * JS Translations are distributed and loaded per script. We can't use wp_set_script_translations |
| 377 | * because translation filename is determined based on script filename and path. |
| 378 | * This function loads JSON files with Gutenberg script's translations distributed within WordPress. |
| 379 | * Implemented based on load_script_textdomain function |
| 380 | * @see https://developer.wordpress.org/reference/functions/load_script_textdomain/ |
| 381 | * @return string[] |
| 382 | */ |
| 383 | private function getGutenbergScriptsTranslations() { |
| 384 | $locale = $this->localizer->locale(); |
| 385 | if (!$locale) { |
| 386 | return []; |
| 387 | } |
| 388 | // List of scripts - relative path to translations directory (default: wp-content/languages) |
| 389 | $translationsToLoad = [ |
| 390 | 'wp-includes/js/dist/blocks.js', |
| 391 | 'wp-includes/js/dist/components.js', |
| 392 | 'wp-includes/js/dist/block-editor.js', |
| 393 | 'wp-includes/js/dist/block-library.js', |
| 394 | 'wp-includes/js/dist/editor.js', |
| 395 | 'wp-includes/js/dist/media-utils.js', |
| 396 | 'wp-includes/js/dist/format-library.js', |
| 397 | 'wp-includes/js/dist/edit-post.js', |
| 398 | ]; |
| 399 | |
| 400 | $translations = []; |
| 401 | foreach ($translationsToLoad as $translation) { |
| 402 | $file = WP_LANG_DIR . '/' . $locale . '-' . md5($translation) . '.json'; |
| 403 | if (!file_exists($file)) { |
| 404 | continue; |
| 405 | } |
| 406 | $translationsData = file_get_contents($file); |
| 407 | if ($translationsData) { |
| 408 | $translations[] = $translationsData; |
| 409 | } |
| 410 | } |
| 411 | return $translations; |
| 412 | } |
| 413 | |
| 414 | private function getFormData(int $id): ?FormEntity { |
| 415 | $form = $this->formsRepository->findOneById($id); |
| 416 | if (!$form instanceof FormEntity) { |
| 417 | return null; |
| 418 | } |
| 419 | $form->setStyles($this->formRenderer->getCustomStyles($form)); |
| 420 | // Use empty settings in case they are corrupted or missing |
| 421 | if (!is_array($form->getSettings())) { |
| 422 | $initialFormTemplate = $this->templatesRepository->getFormTemplate(TemplateRepository::INITIAL_FORM_TEMPLATE); |
| 423 | $form->setSettings($initialFormTemplate->getSettings()); |
| 424 | } |
| 425 | return $form; |
| 426 | } |
| 427 | } |
| 428 |