Changeset 3730982
- Timestamp:
- 10/06/2026 02:12:29 PM (3 weeks ago)
- Location:
- easynewsletter
- Files:
-
- 22 added
- 38 edited
- 1 copied
-
tags/4.2.0 (copied) (copied from easynewsletter/trunk )
-
tags/4.2.0/README.txt (modified) (2 diffs)
-
tags/4.2.0/easynewsletter.php (modified) (1 diff)
-
tags/4.2.0/src/core/databaseConnector.php (modified) (3 diffs)
-
tags/4.2.0/src/core/generateEmailHtml.php (modified) (18 diffs)
-
tags/4.2.0/src/core/menuPage.php (modified) (3 diffs)
-
tags/4.2.0/src/core/newsletterBlockLayout.php (added)
-
tags/4.2.0/src/core/newsletterCSSLength.php (added)
-
tags/4.2.0/src/core/newsletterPageTemplate.php (modified) (1 diff)
-
tags/4.2.0/src/core/newsletterPostType.php (modified) (9 diffs)
-
tags/4.2.0/src/core/newsletterStyleInliner.php (added)
-
tags/4.2.0/src/core/newsletterStyleSettings.php (added)
-
tags/4.2.0/src/core/newsletterThemeStyles.php (added)
-
tags/4.2.0/src/core/outlookPreview.php (modified) (4 diffs)
-
tags/4.2.0/src/core/registration/block (added)
-
tags/4.2.0/src/core/registration/block/block.json (added)
-
tags/4.2.0/src/core/registration/block/editor.js (added)
-
tags/4.2.0/src/core/registration/registration.php (modified) (5 diffs)
-
tags/4.2.0/src/core/registration/registrationBlock.php (added)
-
tags/4.2.0/src/core/registration/registrationForm.php (modified) (4 diffs)
-
tags/4.2.0/src/core/registration/registrationFormContent.php (modified) (1 diff)
-
tags/4.2.0/src/core/resources/admin.css (modified) (1 diff)
-
tags/4.2.0/src/core/resources/newslettersPostTypeAdmin.js (modified) (1 diff)
-
tags/4.2.0/src/core/resources/newslettersPostTypeOverview.js (modified) (1 diff)
-
tags/4.2.0/src/core/resources/registrationForm.css (added)
-
tags/4.2.0/src/core/resources/settingsPage.js (modified) (1 diff)
-
tags/4.2.0/src/core/resources/signupForm.js (added)
-
tags/4.2.0/src/core/settingsPageContent.php (modified) (7 diffs)
-
tags/4.2.0/vendor-prefixed/autoload.php (modified) (1 diff)
-
tags/4.2.0/vendor-prefixed/composer/autoload_real.php (modified) (2 diffs)
-
tags/4.2.0/vendor-prefixed/composer/autoload_static.php (modified) (2 diffs)
-
trunk/README.txt (modified) (2 diffs)
-
trunk/easynewsletter.php (modified) (1 diff)
-
trunk/src/core/databaseConnector.php (modified) (3 diffs)
-
trunk/src/core/generateEmailHtml.php (modified) (18 diffs)
-
trunk/src/core/menuPage.php (modified) (3 diffs)
-
trunk/src/core/newsletterBlockLayout.php (added)
-
trunk/src/core/newsletterCSSLength.php (added)
-
trunk/src/core/newsletterPageTemplate.php (modified) (1 diff)
-
trunk/src/core/newsletterPostType.php (modified) (9 diffs)
-
trunk/src/core/newsletterStyleInliner.php (added)
-
trunk/src/core/newsletterStyleSettings.php (added)
-
trunk/src/core/newsletterThemeStyles.php (added)
-
trunk/src/core/outlookPreview.php (modified) (4 diffs)
-
trunk/src/core/registration/block (added)
-
trunk/src/core/registration/block/block.json (added)
-
trunk/src/core/registration/block/editor.js (added)
-
trunk/src/core/registration/registration.php (modified) (5 diffs)
-
trunk/src/core/registration/registrationBlock.php (added)
-
trunk/src/core/registration/registrationForm.php (modified) (4 diffs)
-
trunk/src/core/registration/registrationFormContent.php (modified) (1 diff)
-
trunk/src/core/resources/admin.css (modified) (1 diff)
-
trunk/src/core/resources/newslettersPostTypeAdmin.js (modified) (1 diff)
-
trunk/src/core/resources/newslettersPostTypeOverview.js (modified) (1 diff)
-
trunk/src/core/resources/registrationForm.css (added)
-
trunk/src/core/resources/settingsPage.js (modified) (1 diff)
-
trunk/src/core/resources/signupForm.js (added)
-
trunk/src/core/settingsPageContent.php (modified) (7 diffs)
-
trunk/vendor-prefixed/autoload.php (modified) (1 diff)
-
trunk/vendor-prefixed/composer/autoload_real.php (modified) (2 diffs)
-
trunk/vendor-prefixed/composer/autoload_static.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
easynewsletter/tags/4.2.0/README.txt
r3710879 r3730982 4 4 Requires at least: 6.0 5 5 Tested up to: 7.0 6 Stable tag: 4. 1.46 Stable tag: 4.2.0 7 7 Requires PHP: 8.0 8 8 License: GPLv3 or later … … 72 72 == Changelog == 73 73 74 = 4.2.0 = 75 Fix - Automatic image sizing in responsive emails. 76 New - Added fluid block layouts and signup forms. 77 New - Introduced theme-based newsletter styling pipeline. 78 New - Added inline registration form layout. 79 New - Configurable signup checkbox text. 80 74 81 = 4.1.4 = 75 82 New - Added newsletter email rendering pipeline. -
easynewsletter/tags/4.2.0/easynewsletter.php
r3710879 r3730982 5 5 Plugin URI: https://www.farnlabs.de/ 6 6 Description: Provides a Newsletter functionality with complete WordPress integration. 7 Version: 4. 1.47 Version: 4.2.0 8 8 Author: FARN – digital brand design 9 9 Author URI: https://www.farn.de/ -
easynewsletter/tags/4.2.0/src/core/databaseConnector.php
r3710879 r3730982 31 31 'intervalInSeconds' => '300', 32 32 'signupFormFields' => '', // will be set as a serialized array('en_eMailAddress') in the constructor. 33 'signupCheckboxText' => '', // Empty uses the translated default checkbox label. 33 34 'activeNewsletter' => '', 34 35 'activeNewsletterID' => '', … … 42 43 'subscriberCategory' => '', // will be set as a serialized array('default') in the constructor. 43 44 'subscriberRole' => '', // will be set as a serialized array('default') in the constructor. 45 'newsletterUseThemeStyles' => 'false', // Upgrades always opt out. 46 'newsletterCSSFile' => '', 47 'newsletterExcludedStyles' => '[]', 44 48 'newsletterCSS' => '', 45 49 'newsletterContentWidth' => '800', 50 'newsletterWideWidth' => '1100', 51 'newsletterSideSpacing' => '0', // Preserve existing installations. 46 52 'addedUserRoleKey' => 'en_user_subscriber', 47 53 "registrationPageID" => "", … … 125 131 $results = array_column($results,0); 126 132 133 $freshInstallation = empty($results); 127 134 foreach ($this->defaultSettings as $key => $value) { 135 if ($key === 'newsletterUseThemeStyles' && $freshInstallation) { $value = 'true'; } 136 if ($key === 'newsletterSideSpacing' && $freshInstallation) { $value = '20'; } 128 137 if(!in_array($key, $results)){ 129 138 $this->wpdb->insert($this->tableName, -
easynewsletter/tags/4.2.0/src/core/generateEmailHtml.php
r3710879 r3730982 3 3 4 4 require_once __DIR__ . '/resources/vendor/cssin/CSSIN.php'; 5 require_once __DIR__ . '/newsletterThemeStyles.php'; 6 require_once __DIR__ . '/newsletterStyleInliner.php'; 7 require_once __DIR__ . '/newsletterBlockLayout.php'; 5 8 6 9 use CSSIN; … … 10 13 class generateEmailHtml { 11 14 public static function generate($postID) : string{ 15 $extendedStyles = newsletterThemeStyles::extended(); 16 $baseCSS = file_get_contents(__DIR__ . '/resources/newsletterBaseStyle.css'); 17 if ($extendedStyles) { 18 // Let the theme's inherited font size reach the email wrapper. 19 $baseCSS = str_replace(['font-size: 16px!important;', 'line-height: 1.3;'], '', $baseCSS); 20 } 21 $additionalCSS = $extendedStyles ? newsletterThemeStyles::css((int)$postID) : ''; 12 22 $contentWidth = self::contentWidth(databaseConnector::instance()->getSettingFromDB("newsletterContentWidth")); 13 23 $enl_mailcontent_head = '<!DOCTYPE html><html lang="de"> … … 20 30 <style type="text/css"> 21 31 '.file_get_contents( __DIR__ . "/resources/newsletterBlocklibraryStyle.css" ).' 22 '.file_get_contents( __DIR__ . "/resources/newsletterBaseStyle.css" ).' 32 '.$baseCSS.' 33 '.$additionalCSS.' 23 34 '.databaseConnector::instance()->getSettingFromDB("newsletterCSS").' 24 35 </style> … … 65 76 $enl_full_mailcontent = $enl_mailcontent_head . $enl_mailcontent_body . $enl_mailcontent_foot; 66 77 78 if ($extendedStyles) { 79 // Normalize before variable resolution too: its legacy parser 80 // must see ordinary root declarations outside conditional rules. 81 $enl_full_mailcontent = preg_replace_callback('~(<style\b[^>]*>)(.*?)(</style>)~is', static function ($match) { 82 return $match[1] . newsletterStyleInliner::emailCSS($match[2]) . $match[3]; 83 }, $enl_full_mailcontent); 84 } 85 67 86 /* Use CSSIN to inline all styles */ 68 87 $cssin = new CSSIN(); 69 $enl_full_mailcontent_inlined = $cssin->inlineCSS( self::prepare($enl_full_mailcontent) ); 88 $enl_full_mailcontent_inlined = $extendedStyles 89 ? newsletterStyleInliner::inline(self::prepare($enl_full_mailcontent, false), $contentWidth) 90 : $cssin->inlineCSS(self::prepare($enl_full_mailcontent)); 70 91 71 92 //return $enl_full_mailcontent; 72 return self::transform($enl_full_mailcontent_inlined, $contentWidth );93 return self::transform($enl_full_mailcontent_inlined, $contentWidth, $extendedStyles, (int)(databaseConnector::instance()->getSettingFromDB('newsletterWideWidth') ?: 1100), self::sideSpacing(databaseConnector::instance()->getSettingFromDB('newsletterSideSpacing'))); 73 94 } catch (\Throwable $e) { 74 95 Log::log("An error occurred while processing the mail content: " . $e->getMessage()); … … 77 98 } 78 99 100 public static function sideSpacing($value): int { 101 $valid = filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0, 'max_range' => 200]]); 102 return $valid === false ? 0 : $valid; 103 } 104 79 105 private int $layoutWidth = 800; 106 private float $rootFont = 16; 80 107 private ?\DOMElement $layoutRoot = null; 81 108 public static function contentWidth($value): int { … … 99 126 private static function length(string $value, float $reference, float $font = 16): ?float { 100 127 $value = self::plain($value); 128 if (preg_match('/^(calc|clamp|min|max)\(/i', $value)) { 129 return newsletterCSSLength::pixels($value, $reference, $font); 130 } 101 131 if (!preg_match('/^(\d+(?:\.\d+)?)(px|pt|%|em|rem)?$/i', $value, $match)) { return null; } 102 132 $number = (float)$match[1]; … … 203 233 204 234 /** Remember author intent before global image CSS is inlined. */ 205 public static function prepare(string $html ): string {235 public static function prepare(string $html, bool $convertColumns = true): string { 206 236 if (trim($html) === '') { return $html; } 207 237 $dom = self::load($html); 208 238 self::resolveDocumentVariables($dom); 239 foreach ((new \DOMXPath($dom))->query('//div[contains(concat(" ",normalize-space(@class)," ")," wp-block-column ")]') as $column) { 240 $author = self::styles($column->getAttribute('style')); 241 if (isset($author['flex-basis'])) { $column->setAttribute('data-en-author-basis', $author['flex-basis']); } 242 } 243 foreach ((new \DOMXPath($dom))->query('//div[contains(concat(" ",normalize-space(@class)," ")," wp-block-group ")]') as $group) { 244 $padding = array_filter(self::styles($group->getAttribute('style')), static function ($name) { return str_starts_with($name, 'padding'); }, ARRAY_FILTER_USE_KEY); 245 if ($padding) { $group->setAttribute('data-en-author-padding', json_encode($padding)); } 246 } 247 if ($convertColumns) { self::prepareColumns($dom); } 248 foreach ($dom->getElementsByTagName('img') as $image) { 249 $styles = self::styles($image->getAttribute('style')); 250 $intent = []; 251 foreach (['width', 'height', 'aspect-ratio', 'object-fit', 'object-position'] as $key) { 252 if (isset($styles[$key])) { $intent[$key] = $styles[$key]; } 253 } 254 // Older Gutenberg versions put intentional dimensions in attributes. 255 $parent = $image->parentNode; 256 while ($parent instanceof \DOMElement && in_array($parent->tagName, ['a', 'figure', 'div'], true)) { 257 if (preg_match('/\bis-resized\b/', $parent->getAttribute('class'))) { 258 foreach (['width', 'height'] as $key) { 259 if (!isset($intent[$key]) && preg_match('/^[1-9]\d*$/', $image->getAttribute($key))) { 260 $intent[$key] = $image->getAttribute($key) . 'px'; 261 } 262 } 263 break; 264 } 265 $parent = $parent->parentNode; 266 } 267 $image->setAttribute('data-en-image-intent', json_encode($intent)); 268 } 269 return $dom->saveHTML(); 270 } 271 272 private static function prepareColumns(\DOMDocument $dom): void { 209 273 // Work from inside out so nested column groups keep their own row/gap. 210 274 $groups = (new \DOMXPath($dom))->query('//div[contains(concat(" ", normalize-space(@class), " "), " wp-block-columns ")]'); 211 275 foreach (array_reverse(iterator_to_array($groups)) as $group) { 212 276 $groupStyles = self::styles($group->getAttribute('style')); 213 $gap = preg_split('/\s+/', self::plain($groupStyles['gap'] ?? '40px'));277 $gap = array_values(array_filter(newsletterStyleInliner::split(self::plain($groupStyles['gap'] ?? '40px'), ' '), 'strlen')); 214 278 $columnGap = $groupStyles['column-gap'] ?? ($gap[1] ?? $gap[0]); 215 279 $rowGap = $groupStyles['row-gap'] ?? $gap[0]; … … 239 303 foreach ($column->attributes as $attribute) { $cell->setAttribute($attribute->name, $attribute->value); } 240 304 $css = self::styles($cell->getAttribute('style')); 241 $cell->setAttribute('data-en-column-basis', $css['flex-basis'] ?? $css['width'] ?? ''); 305 $cell->setAttribute('data-en-column-basis', $cell->getAttribute('data-en-author-basis') ?: ($css['flex-basis'] ?? $css['width'] ?? '')); 306 $cell->setAttribute('data-en-column-grow', $cell->hasAttribute('data-en-author-basis') ? '0' : ($css['flex-grow'] ?? '1')); 307 $cell->removeAttribute('data-en-author-basis'); 242 308 unset($css['flex-basis']); 309 $alignment = $groupStyles['align-items'] ?? 'top'; 310 foreach (['top', 'center', 'bottom'] as $position) { 311 if (preg_match('/\bis-vertically-aligned-' . $position . '\b/', $group->getAttribute('class'))) { $alignment = $position; } 312 } 313 // A per-column choice takes precedence over the group choice. 314 $alignment = $css['align-self'] ?? $alignment; 315 foreach (['top', 'center', 'bottom'] as $position) { 316 if (preg_match('/\bis-vertically-aligned-' . $position . '\b/', $column->getAttribute('class'))) { $alignment = $position; } 317 } 318 $alignment = match ($alignment) { 'center' => 'middle', 'bottom', 'flex-end', 'end' => 'bottom', default => 'top' }; 319 $css['vertical-align'] = $alignment; 320 $cell->setAttribute('valign', $alignment); 243 321 $cell->setAttribute('style', \CSSIN::arrayToStyle($css)); 244 322 while ($column->firstChild) { $cell->appendChild($column->firstChild); } … … 247 325 $group->parentNode->replaceChild($table, $group); 248 326 } 249 foreach ($dom->getElementsByTagName('img') as $image) { 250 $styles = self::styles($image->getAttribute('style')); 251 $intent = []; 252 foreach (['width', 'height', 'aspect-ratio', 'object-fit', 'object-position'] as $key) { 253 if (isset($styles[$key])) { $intent[$key] = $styles[$key]; } 254 } 255 // Older Gutenberg versions put intentional dimensions in attributes. 256 $parent = $image->parentNode; 257 while ($parent instanceof \DOMElement && in_array($parent->tagName, ['a', 'figure', 'div'], true)) { 258 if (preg_match('/\bis-resized\b/', $parent->getAttribute('class'))) { 259 foreach (['width', 'height'] as $key) { 260 if (!isset($intent[$key]) && preg_match('/^[1-9]\d*$/', $image->getAttribute($key))) { 261 $intent[$key] = $image->getAttribute($key) . 'px'; 262 } 263 } 264 break; 265 } 266 $parent = $parent->parentNode; 267 } 268 $image->setAttribute('data-en-image-intent', json_encode($intent)); 269 } 270 return $dom->saveHTML(); 271 } 272 273 public static function transform(string $html, int $width): string { 327 } 328 329 public static function transform(string $html, int $width, bool $convertColumns = false, ?int $wideWidth = null, int $sideSpacing = 0): string { 274 330 if (trim($html) === '') { return $html; } 275 331 $dom = self::load($html); 332 $fluidSections = $wideWidth !== null && newsletterBlockLayout::apply($dom, self::contentWidth($width), $wideWidth >= 200 && $wideWidth <= 2000 ? $wideWidth : 1100, self::sideSpacing($sideSpacing)); 333 if ($convertColumns) { self::prepareColumns($dom); } 276 334 self::wrapColumnPadding($dom); 277 335 self::centerImages($dom); … … 282 340 } 283 341 $instance = new self(); 284 $instance->layoutWidth = self::contentWidth($width); 342 $instance->layoutWidth = $fluidSections ? max(self::contentWidth($width), $wideWidth >= 200 && $wideWidth <= 2000 ? $wideWidth : 1100) : self::contentWidth($width); 343 $rootStyles = self::styles($dom->documentElement->getAttribute('style')); 344 $instance->rootFont = self::length($rootStyles['font-size'] ?? '', 16, 16) ?? 16; 285 345 $instance->layoutRoot = (new \DOMXPath($dom))->query('//table[contains(concat(" ", normalize-space(@class), " "), " enl-wrapper ")]')->item(0); 286 if ($instance->layoutRoot) { 346 if ($fluidSections && $instance->layoutRoot) { 347 $instance->layoutWidth = (int)$instance->layoutRoot->getAttribute('data-en-reference-width'); 348 $instance->layoutRoot->removeAttribute('data-en-reference-width'); 349 } 350 if ($instance->layoutRoot && !$fluidSections) { 287 351 $rootStyles = self::styles($instance->layoutRoot->getAttribute('style')); 288 352 $instance->layoutWidth = (int)round(min($instance->layoutWidth, … … 298 362 } 299 363 $instance->walk($dom->documentElement, $instance->layoutWidth); 300 $instance->outlookFrame(); 364 if (!$fluidSections) { $instance->outlookFrame(); } 365 foreach ((new \DOMXPath($dom))->query('//*[@data-en-author-padding or @data-en-fluid-sections]') as $node) { 366 $node->removeAttribute('data-en-author-padding'); $node->removeAttribute('data-en-fluid-sections'); 367 } 301 368 return $dom->saveHTML(); 302 369 } … … 340 407 341 408 private static function paddingPixels(array $styles, float $reference, float $font): string { 342 $parts = preg_split('/\s+/', self::plain($styles['padding'] ?? '0'));409 $parts = array_values(array_filter(newsletterStyleInliner::split(self::plain($styles['padding'] ?? '0'), ' '), 'strlen')); 343 410 $values = [$parts[0], $parts[1] ?? $parts[0], $parts[2] ?? $parts[0], $parts[3] ?? $parts[1] ?? $parts[0]]; 344 411 foreach (['top', 'right', 'bottom', 'left'] as $index => $side) { … … 443 510 } 444 511 $styles = self::styles($node->getAttribute('style')); 512 foreach ($styles as $name => &$value) { 513 if (!str_starts_with($name, '--') && !in_array($name, ['font-family', 'content'], true)) { 514 $value = newsletterCSSLength::resolveRem($value, $node->tagName === 'html' && $name === 'font-size' ? 16 : $this->rootFont); 515 } 516 } 517 unset($value); 445 518 if ($node->tagName === 'table' && preg_match('/\bwp-block-columns\b/', $node->getAttribute('class'))) { 446 519 $styles['display'] = 'table'; … … 448 521 } 449 522 $font = self::length($styles['font-size'] ?? '', $font, $font) ?? $font; 523 foreach ($styles as $property => $value) { 524 if (!preg_match('/^(?:padding|margin)(?:-(?:top|right|bottom|left))?$|^(?:font-size|width|height|min-width|max-width|min-height|max-height)$/', $property) 525 || !preg_match('/(?:calc|clamp|min|max)\(/i', $value)) { continue; } 526 $parts = array_values(array_filter(newsletterStyleInliner::split(self::plain($value), ' '), 'strlen')); 527 foreach ($parts as &$part) { 528 $pixels = newsletterCSSLength::pixels($part, $available, $font, $this->layoutWidth); 529 if ($pixels !== null) { $part = round($pixels, 3) . 'px'; } 530 } 531 unset($part); 532 $styles[$property] = implode(' ', $parts); 533 } 450 534 if (in_array($node->tagName, ['td', 'th'], true)) { 451 535 $paddingStyles = $styles; 536 if ($node->hasAttribute('data-en-outlook-padding')) { 537 $paddingStyles = json_decode($node->getAttribute('data-en-outlook-padding'), true) ?: []; 538 $node->removeAttribute('data-en-outlook-padding'); 539 } 452 540 if (strpos(' ' . $node->getAttribute('class') . ' ', ' en-button-cell ') !== false) { 453 541 $link = $node->getElementsByTagName('a')->item(0); … … 486 574 $width = $allocated ?? min($available, self::length($styles['width'] ?? $node->getAttribute('width'), $available, $font) ?? $available); 487 575 $width = min($width, self::length($styles['max-width'] ?? '', $available, $font) ?? $width); 488 $padding = preg_split('/\s+/', self::plain($styles['padding'] ?? '0'));576 $padding = array_values(array_filter(newsletterStyleInliner::split(self::plain($styles['padding'] ?? '0'), ' '), 'strlen')); 489 577 $left = $styles['padding-left'] ?? ($padding[3] ?? $padding[1] ?? $padding[0]); 490 578 $right = $styles['padding-right'] ?? ($padding[1] ?? $padding[0]); … … 545 633 $gap = $count > 1 ? min($gap, max(0, ($width - $count) / ($count - 1))) : 0; 546 634 $contentWidth = $width - $gap * ($count - 1); 547 $weights = []; $ used = 0; $auto= 0;635 $weights = []; $growth = []; $used = 0; 548 636 foreach ($columns as $column) { 549 637 $basis = self::length($column->getAttribute('data-en-column-basis'), $contentWidth, $font); 550 $weights[] = $basis; 551 if ($basis === null) { $auto++; } else { $used += $basis; } 552 } 553 foreach ($weights as &$weight) { $weight = $weight ?? max(1, $contentWidth - $used) / max(1, $auto); } 554 unset($weight); 638 $weights[] = $basis ?? 0; 639 $growth[] = max(0, (float)$column->getAttribute('data-en-column-grow')); 640 $used += $basis ?? 0; 641 } 642 $free = max(0, $contentWidth - $used); 643 $totalGrowth = array_sum($growth); 644 if ($totalGrowth > 0) { 645 foreach ($weights as $index => &$weight) { $weight += $free * $growth[$index] / $totalGrowth; } 646 unset($weight); 647 } 555 648 $sum = array_sum($weights); 556 649 if ($sum <= 0) { $weights = array_fill(0, $count, 1); $sum = $count; } … … 568 661 $cellWidth = $contentWidth * $fraction; 569 662 $cell->removeAttribute('data-en-column-basis'); 663 $cell->removeAttribute('data-en-column-grow'); 570 664 // Numeric desktop fallback plus proportional widths for narrow clients. 571 665 // Gutters scale with the row when columns stay side by side. … … 624 718 // Keep tighter author caps (e.g. width:100%; max-width:200px). 625 719 $styles['max-width'] = $styles['max-width'] ?? '100%'; 720 if (!$fixed && strpos($widthCSS, '%') === false) { 721 // Automatic images must shrink through width itself: Thunderbird can 722 // override max-width with its message stylesheet. Keep the computed 723 // desktop size as a cap so small intrinsic images are not enlarged. 724 $cap = self::plain($styles['max-width']); 725 $styles['width'] = preg_match('/^(\d+(?:\.\d+)?)%$/', $cap, $match) 726 ? min(100, (float)$match[1]) . '%' : '100%'; 727 $styles['max-width'] = $width . 'px'; 728 } 626 729 $styles['height'] = 'auto'; 627 730 unset($styles['object-fit'], $styles['object-position'], $styles['aspect-ratio']); -
easynewsletter/tags/4.2.0/src/core/menuPage.php
r3696327 r3730982 54 54 'easyNewsletter', 55 55 [$this, "menuPageContent"]); 56 add_submenu_page( 57 'easyNewsletter', 58 __('Overview', 'easynewsletter'), 59 __('Overview', 'easynewsletter'), 60 'manage_options', 61 'easyNewsletter', 62 [$this, 'menuPageContent'], 63 0 64 ); 56 65 } 57 66 … … 118 127 public function settingsPageContent(): void{ 119 128 //add settingsPage.js script to this site 120 wp_enqueue_script( "en_settingsPageJS", plugins_url( 'resources/settingsPage.js', __FILE__ ) );129 wp_enqueue_script( "en_settingsPageJS", plugins_url( 'resources/settingsPage.js', __FILE__ ), ['jquery'], filemtime(__DIR__ . '/resources/settingsPage.js'), true ); 121 130 122 131 include ('settingsPageContent.php'); … … 135 144 // styles for menu icon 136 145 wp_enqueue_style("en_menuIconCSS", plugins_url('resources/menuIcon.css', __FILE__ )); 137 wp_enqueue_style("en_admin", plugins_url('resources/admin.css', __FILE__ ));146 wp_enqueue_style("en_admin", plugins_url('resources/admin.css', __FILE__), [], filemtime(__DIR__ . '/resources/admin.css')); 138 147 } 139 148 -
easynewsletter/tags/4.2.0/src/core/newsletterPageTemplate.php
r3710879 r3730982 47 47 </nav> 48 48 </header> 49 <iframe title="<?php echo $outlook ? 'Outlook-Simulation' : 'Standard-Vorschau'; ?>" sandbox="allow- popups allow-popups-to-escape-sandbox" srcdoc="<?php echo esc_attr($html); ?>"></iframe>49 <iframe title="<?php echo $outlook ? 'Outlook-Simulation' : 'Standard-Vorschau'; ?>" sandbox="allow-same-origin allow-popups allow-popups-to-escape-sandbox" srcdoc="<?php echo esc_attr($html); ?>"></iframe> 50 50 </body> 51 51 </html> -
easynewsletter/tags/4.2.0/src/core/newsletterPostType.php
r3710879 r3730982 28 28 */ 29 29 public function init(): void { 30 newsletterThemeStyles::init(); 30 31 add_action('init', array($this, 'createNewsletterPostType')); 31 32 add_filter( 'manage_en_newsletters_posts_columns', array( $this, "addBackendNewsletterColumns" ) ); … … 37 38 add_action( 'admin_enqueue_scripts', array($this, 'enqueue_script'), 11 ); 38 39 add_filter('block_editor_settings_all', [$this, 'newsletterEditorStyles'], 20, 2); 40 add_filter('post_row_actions', [$this, 'newsletterRowActions'], PHP_INT_MAX, 2); 41 add_filter('page_row_actions', [$this, 'newsletterRowActions'], PHP_INT_MAX, 2); 42 foreach (['manage_en_newsletters_posts_columns', 'manage_edit-en_newsletters_columns', 'manage_edit-en_newsletters_sortable_columns'] as $hook) { 43 add_filter($hook, [$this, 'removeNewsletterSEOColumns'], PHP_INT_MAX); 44 } 39 45 40 46 add_action( 'wp_ajax_en_sendNewsletterTestMail', array( $this,'en_sendNewsletterTestMail')); … … 61 67 add_action('admin_enqueue_scripts', [$this, "add_script_to_overview"]); 62 68 add_action( 'wp_ajax_en_copyNewsletter', array( $this,'en_copyNewsletter')); 63 add_action( 'wp_ajax_nopriv_en_copyNewsletter', array( $this,'en_copyNewsletter'));64 69 65 70 metaBoxes::addAllMetaBoxes(); … … 87 92 'labels' => array( 88 93 'name' => __( 'Newsletter' ), 89 'singular_name' => __( 'Newsletter' ) 94 'singular_name' => __( 'Newsletter' ), 95 'add_new' => __('Neuer Newsletter', 'easynewsletter'), 96 'add_new_item' => __('Neuer Newsletter', 'easynewsletter') 90 97 ), 91 98 'public' => true, … … 268 275 if( 'en_newsletters' == $post_type ) { 269 276 $resources_url = EASY_NEWSLETTER_URL . 'src/core/resources/'; 270 wp_enqueue_script( 'newsletters-admin-edit-script', $resources_url . 'newslettersPostTypeAdmin.js', [ 'jquery', 'wp-i18n'] );277 wp_enqueue_script( 'newsletters-admin-edit-script', $resources_url . 'newslettersPostTypeAdmin.js', [ 'jquery', 'wp-i18n'], filemtime(__DIR__ . '/resources/newslettersPostTypeAdmin.js'), true); 271 278 wp_set_script_translations("newsletters-admin-edit-script",'easynewsletter',plugin_dir_path(__FILE__) . 'resources/language'); 272 279 … … 294 301 return $settings; 295 302 } 303 $db = databaseConnector::instance(); 304 $contentWidth = generateEmailHtml::contentWidth($db->getSettingFromDB('newsletterContentWidth')); 305 $wideWidth = filter_var($db->getSettingFromDB('newsletterWideWidth'), FILTER_VALIDATE_INT, 306 ['options' => ['min_range' => 200, 'max_range' => 2000]]) ?: 1100; 307 $wideWidth = max($contentWidth, $wideWidth); 308 // Gutenberg uses these settings for inherited Group layouts and controls. 309 $settings['__experimentalFeatures']['layout']['contentSize'] = $contentWidth . 'px'; 310 $settings['__experimentalFeatures']['layout']['wideSize'] = $wideWidth . 'px'; 296 311 $settings['styles'][] = ['css' => file_get_contents(__DIR__ . '/resources/newsletterBaseStyle.css')]; 312 if (newsletterThemeStyles::enabled()) { 313 // Theme fonts may be enqueued only on the frontend. Include their 314 // absolute @font-face sources in the editor canvas as well. 315 $themeCSS = newsletterStyleInliner::emailCSS(newsletterThemeStyles::css((int)$context->post->ID)); 316 [, $fonts] = \CSSIN::splitFontFace($themeCSS); 317 if ($fonts !== '') { $settings['styles'][] = ['css' => $fonts]; } 318 } 319 // Override theme-generated canvas widths without touching other post 320 // types, nested columns or explicit per-group content-size choices. 321 $settings['styles'][] = ['css' => " 322 .editor-styles-wrapper { 323 --wp--style--global--content-size: {$contentWidth}px !important; 324 --wp--style--global--wide-size: {$wideWidth}px !important; 325 } 326 .editor-styles-wrapper .is-root-container > .wp-block:not([data-align=wide]):not([data-align=full]):not(.alignwide):not(.alignfull), 327 .editor-styles-wrapper .editor-post-title { 328 max-width: {$contentWidth}px; 329 margin-left: auto; 330 margin-right: auto; 331 } 332 .editor-styles-wrapper .is-root-container > .wp-block[data-align=wide], 333 .editor-styles-wrapper .is-root-container > .wp-block.alignwide { 334 max-width: {$wideWidth}px; 335 margin-left: auto; 336 margin-right: auto; 337 } 338 .editor-styles-wrapper .is-root-container > .wp-block[data-align=full], 339 .editor-styles-wrapper .is-root-container > .wp-block.alignfull { 340 max-width: none; 341 } 342 "]; 297 343 $customCSS = databaseConnector::instance()->getSettingFromDB('newsletterCSS'); 298 344 if (is_string($customCSS) && trim($customCSS) !== '') { … … 305 351 function remove_editor_styles_for_post_type(): void { 306 352 global $post_type; 307 if( 'en_newsletters' == $post_type ) {353 if( 'en_newsletters' == $post_type && !newsletterThemeStyles::enabled() ) { 308 354 remove_theme_support('editor-styles'); 309 355 } … … 419 465 420 466 $postID = sanitize_key($_POST["post_id"]); 421 $subject = get_post_meta($postID,"en_subject", true);467 $subject = "[Test] " . get_post_meta($postID,"en_subject", true); 422 468 $userID = get_current_user_id(); 423 469 … … 508 554 $posttype = $_GET["post_type"] ?? null; 509 555 if ('edit.php' === $hook && $posttype == "en_newsletters") { 510 wp_enqueue_script( 'newsletters-overview-script', EASY_NEWSLETTER_URL . 'src/core/resources/newslettersPostTypeOverview.js'); 511 } 512 } 513 514 public function en_copyNewsletter(): void { 515 check_ajax_referer( 'secure_nonce_name', 'security' ); 516 try { 517 $post = get_post($_POST["post_id"]); 518 519 //make a duplicate from the post 520 $new_post_id = wp_insert_post( array( 521 'post_title' => $post->post_title . ' (Copy)', 522 'post_content' => $post->post_content, 523 'post_status' => $post->post_status, 524 'post_type' => $post->post_type, 525 'post_author' => $post->post_author, 526 'post_parent' => $post->post_parent, 527 'post_excerpt' => $post->post_excerpt, 528 'post_date' => $post->post_date, 529 'post_date_gmt' => $post->post_date_gmt, 530 'menu_order' => $post->menu_order, 531 'comment_status' => $post->comment_status, 532 'ping_status' => $post->ping_status, 533 'post_password' => $post->post_password, 534 'to_ping' => $post->to_ping, 535 'pinged' => $post->pinged, 536 'post_content_filtered' => $post->post_content_filtered, 537 'post_category' => $post->post_category, 538 'tags_input' => $post->tags_input, 539 'tax_input' => $post->tax_input, 540 'meta_input' => $post->meta_input, 541 ) ); 542 543 //Copy custom Meta keys to the new post 544 $custom_keys = get_post_custom_keys($_POST["post_id"]); 545 foreach ( $custom_keys as $key ) { 546 $value = get_post_meta( $_POST["post_id"], $key, true ); 547 update_post_meta( $new_post_id, $key, $value ); 548 } 549 550 551 } catch (\Error | Exception $e){ 552 Log::logError($e->getMessage()); 553 } 554 } 556 wp_enqueue_script( 'newsletters-overview-script', EASY_NEWSLETTER_URL . 'src/core/resources/newslettersPostTypeOverview.js', ['jquery'], filemtime(__DIR__ . '/resources/newslettersPostTypeOverview.js')); 557 } 558 } 559 560 /** Registered only on newsletter column hooks, including Screen Options. */ 561 public function removeNewsletterSEOColumns(array $columns): array { 562 foreach (array_keys($columns) as $key) { 563 if ($key === 'tsf-seo-bar-wrap' || str_starts_with($key, 'wpseo-')) { 564 unset($columns[$key]); 565 } 566 } 567 return $columns; 568 } 569 570 /** Keep one predictable copy workflow in the newsletter list only. */ 571 public function newsletterRowActions(array $actions, $post): array { 572 if (($post->post_type ?? '') !== 'en_newsletters') { return $actions; } 573 // WordPress uses these action keys as the surrounding span classes. 574 // Duplicate Page; Copy & Delete Everything (copy-delete-posts); 575 // Yoast Duplicate Post; Duplicate Page and Post; WP Duplicate Page; Post Duplicator. 576 foreach (['duplicate', 'cdp_copy', 'clone', 'edit_as_new_draft', 'rewrite', 'dpp', 'njt_duplicate_page', 'duplicate_post', 'w3tc_flush_post'] as $key) { 577 unset($actions[$key]); 578 } 579 return $actions; 580 } 581 582 public function en_copyNewsletter(): void { 583 check_ajax_referer('secure_nonce_name', 'security'); 584 $postID = absint($_POST['post_id'] ?? 0); 585 $post = get_post($postID); 586 $type = get_post_type_object('en_newsletters'); 587 if (!$post || $post->post_type !== 'en_newsletters' 588 || !current_user_can('edit_post', $postID) 589 || !$type || !current_user_can($type->cap->create_posts)) { 590 wp_send_json_error(['message' => __('Newsletter kann nicht kopiert werden.', 'easynewsletter')], 403); 591 return; 592 } 593 $newID = self::duplicateNewsletter($post); 594 if (is_wp_error($newID)) { 595 wp_send_json_error(['message' => $newID->get_error_message()], 500); 596 return; 597 } 598 wp_send_json_success(['post_id' => $newID]); 599 } 600 601 /** Copy editorial content, never the original publication or delivery state. */ 602 public static function duplicateNewsletter(\WP_Post $post) { 603 $newID = wp_insert_post(wp_slash([ 604 'post_title' => $post->post_title . ' (Copy)', 605 'post_content' => $post->post_content, 606 'post_excerpt' => $post->post_excerpt, 607 'post_status' => 'draft', 608 'post_type' => 'en_newsletters', 609 'post_author' => get_current_user_id(), 610 'post_parent' => $post->post_parent, 611 'post_date' => current_time('mysql'), 612 // A floating draft date lets WordPress use publication time later, 613 // unless the editor explicitly chooses a publication date. 614 'post_date_gmt' => '0000-00-00 00:00:00', 615 'comment_status' => $post->comment_status, 616 'ping_status' => $post->ping_status, 617 'post_password' => $post->post_password, 618 ]), true); 619 if (is_wp_error($newID)) { return $newID; } 620 621 $excluded = ['_edit_lock', '_edit_last', '_wp_old_slug', '_wp_old_date', 622 '_wp_trash_meta_status', '_wp_trash_meta_time', '_wp_desired_post_slug', 623 'en_scheduleLock', 'lockStatus', 'en_alreadySend', 'en_archiveContent']; 624 foreach (get_post_meta($post->ID) as $key => $values) { 625 if (in_array($key, $excluded, true)) { continue; } 626 foreach ($values as $value) { 627 add_post_meta($newID, $key, wp_slash(maybe_unserialize($value))); 628 } 629 } 630 foreach (get_object_taxonomies($post->post_type) as $taxonomy) { 631 $terms = wp_get_object_terms($post->ID, $taxonomy, ['fields' => 'ids']); 632 if (!is_wp_error($terms)) { wp_set_object_terms($newID, $terms, $taxonomy); } 633 } 634 return $newID; 635 } 555 636 } -
easynewsletter/tags/4.2.0/src/core/outlookPreview.php
r3710879 r3730982 3 3 4 4 require_once __DIR__ . '/resources/vendor/cssin/CSSIN.php'; 5 require_once __DIR__ . '/newsletterCSSLength.php'; 5 6 6 7 /** Preview-only approximation of Classic Outlook for Windows, after CSSIN. */ … … 32 33 $node->parentNode->removeChild($node); 33 34 } 35 $rootFont = 16.0; 34 36 foreach ($xpath->query('//*') as $node) { 35 37 $parser = new \csstidy(); … … 42 44 } 43 45 } 46 if (strtolower($node->tagName) === 'html' && isset($styles['font-size'])) { 47 $rootFont = newsletterCSSLength::pixels($styles['font-size'], 16, 16) ?? 16; 48 } 49 foreach ($styles as $name => &$value) { 50 if (!str_starts_with($name, '--') && !in_array($name, ['font-family', 'content'], true)) { 51 $value = newsletterCSSLength::resolveRem($value, $node->tagName === 'html' && $name === 'font-size' ? 16 : $rootFont); 52 } 53 } 54 unset($value); 44 55 unset($styles['max-width']); 45 56 if (isset($styles['mso-padding-alt'])) { … … 53 64 $plainProperty = preg_replace('/^-(webkit|moz|ms|o)-/', '', strtolower($property)); 54 65 if (preg_match('/^border-.*radius(?:-.*)?$/', $plainProperty) 55 || in_array($plainProperty, ['box-shadow', 'text-shadow', 'gap', 'row-gap', 'column-gap', 'order' ], true)66 || in_array($plainProperty, ['box-shadow', 'text-shadow', 'gap', 'row-gap', 'column-gap', 'order', 'transform', 'transform-origin', 'transform-style', 'rotate', 'translate', 'scale', 'perspective', 'perspective-origin', 'backface-visibility'], true) 56 67 || preg_match('/^(flex|grid|align|justify|place)(-|$)/', $plainProperty)) { 57 68 unset($styles[$property]); -
easynewsletter/tags/4.2.0/src/core/registration/registration.php
r3710879 r3730982 51 51 */ 52 52 public function init(): void { 53 add_action('init', [registrationBlock::class, 'register']); 53 54 add_shortcode('easyNewsletter', array($this, 'registrationFormFilter')); 54 55 add_shortcode('easyNewsletterUnsubscribeForm', array($this, 'easyNewsletterUnsubscribeForm')); 56 add_action('wp_enqueue_scripts', static function () { 57 wp_enqueue_style('en-registration-form', plugins_url('../resources/registrationForm.css', __FILE__), array(), '1.0.0'); 58 }); 55 59 } 56 60 … … 188 192 public function registrationFormFilter($attributes): string|bool { 189 193 194 return $this->renderForm($attributes); 195 } 196 197 public function renderForm($attributes, bool $preview = false): string { 198 static $instance = 0; 199 $formInstance = ++$instance; 200 $idSuffix = $formInstance === 1 ? '' : '_' . $formInstance; 201 if (!$preview) { 202 wp_enqueue_script('en-signup-form', plugins_url('../resources/signupForm.js', __FILE__), [], (string) filemtime(__DIR__ . '/../resources/signupForm.js'), true); 203 } 190 204 $attributes = shortcode_atts(array( 191 205 'jump' => false, 192 206 'category' => 'default', 193 207 'redirect' => false, 208 'layout' => '', 194 209 'registration_success_page_id' => 0 195 210 ), $attributes); … … 197 212 $availableSubscriberCategories = databaseConnector::instance()->getSettingFromDB("subscriberCategory"); 198 213 $availableSubscriberCategories = unserialize($availableSubscriberCategories); 199 if (!in_array($attributes['category'], $availableSubscriberCategories)){214 if (!in_array($attributes['category'], (array) $availableSubscriberCategories, true)){ 200 215 $attributes['category'] = 'default'; 201 216 } … … 212 227 $token = sanitize_text_field($_GET['token'] ?? ""); 213 228 214 if ( $attributes['jump'] == "true" || $shouldRedirect){229 if (!$preview && (wp_validate_boolean($attributes['jump']) || $shouldRedirect)){ 215 230 wp_enqueue_script( 216 231 'custom-registration-script', // Handle … … 223 238 224 239 ob_start(); 225 if ( empty($_GET) ||240 if ($preview || empty($_GET) || 226 241 !empty($confirmed) && 227 242 !empty($unsubscribed) && -
easynewsletter/tags/4.2.0/src/core/registration/registrationForm.php
r3710879 r3730982 6 6 use Farn\EasyNewsletter\core\subscriberHandler; 7 7 8 if (! empty(sanitize_text_field( $_REQUEST['submit'] ?? ""))){8 if (!$preview && !empty($_POST['submit']) && (isset($_POST['en_form_instance']) ? (string) $_POST['en_form_instance'] === (string) $formInstance : $formInstance === 1)){ 9 9 10 10 $registrationSuccessPageContent = get_the_content("", false, $registrationSuccessPageID); … … 14 14 : ''; 15 15 16 if ( ( $_POST['exitInput'] != "") || ($_POST['human_check']!= date('Y-m-d')) ){16 if ( (($_POST['exitInput'] ?? '') != "") || (($_POST['human_check'] ?? '') != date('Y-m-d')) ){ 17 17 echo '<div id="en_registrationJumpContainer"' . $registrationSuccessRedirectAttribute . '>'; 18 18 echo $registrationSuccessPageContent; … … 23 23 $metaFields = array(); 24 24 25 foreach ($_ REQUEST as $field => $input){25 foreach ($_POST as $field => $input){ 26 26 if ($field == "en_subscriberCategory"){ 27 $metaFields[$field] = [ sanitize_text_field($input)];28 } else if ($field != 'submit' ){27 $metaFields[$field] = [$attributes['category']]; 28 } else if ($field != 'submit' && $field != 'en_form_instance' && is_scalar($input)){ 29 29 $metaFields[$field] = sanitize_text_field($input); 30 30 } … … 52 52 ob_end_clean(); 53 53 54 $content = str_replace("{{registrationForm}}", $formContent , $content); 54 // A block-level form cannot live inside the paragraph used by older templates. 55 // Remove only the paragraph wrapping our placeholder, preserving surrounding copy. 56 $content = preg_replace('/<p\b[^>]*>\s*\{\{registrationForm\}\}\s*<\/p>/i', '{{registrationForm}}', $content); 57 // Avoid automatic paragraph insertion at template-only whitespace between tags. 58 $formContent = preg_replace('/>\s+</', '><', trim($formContent)); 59 $content = str_replace("{{registrationForm}}", $formContent, $content); 55 60 56 61 //No escape because we want to get the raw content -
easynewsletter/tags/4.2.0/src/core/registration/registrationFormContent.php
r3495745 r3730982 1 1 <?php 2 3 2 namespace Farn\EasyNewsletter\core\registration; 4 5 3 use Farn\EasyNewsletter\core\databaseConnector; 6 4 5 $signupCheckboxText = wp_kses_post(databaseConnector::instance()->getSettingFromDB('signupCheckboxText')); 6 if (trim($signupCheckboxText) === '') { 7 $signupCheckboxText = esc_html__('I am a human being', 'easynewsletter'); 8 } 9 $inlineLayout = ($attributes['layout'] ?? '') === 'inline'; 10 $submitButton = '<input type="submit" class="button button-primary" name="submit" id="submit' . esc_attr($idSuffix) . '" value="' . esc_attr__('Subscribe', 'easynewsletter') . '">'; 11 $inlineSubmitRendered = false; 12 $signupFormFields = (array) maybe_unserialize(databaseConnector::instance()->getSettingFromDB('signupFormFields')); 7 13 ?> 8 9 14 <div class="en_signup_wrapper"> 10 <form method="post" name="en_signup_form" id="en_signup_form" class="en_signup_form"> 11 <?php 12 // get values from database/settings-page 13 14 15 $category = isset($attributes) ? $attributes[ 'category'] : "default"; 16 17 $signupFormFields = databaseConnector::instance()->getSettingFromDB('signupFormFields'); 18 $signupFormFields = unserialize($signupFormFields); 19 if($signupFormFields !== false){ 20 foreach ($signupFormFields as $signupFormField){ 21 echo match ($signupFormField){ 22 "en_eMailAddress" => "<label for='" . esc_attr($signupFormField) . "'>" . __('E-Mail','easynewsletter'), 23 "en_salutation" => "<label for='" . esc_attr($signupFormField) . "'>" . __('Salutation','easynewsletter'), 24 "en_gender" => "<label for='" . esc_attr($signupFormField) . "'>" . __('Gender','easynewsletter'), 25 "en_firstName" => "<label for='" . esc_attr($signupFormField) . "'>" . __('First Name', 'easynewsletter'), 26 "en_lastName" => "<label for='" . esc_attr($signupFormField) . "'>" . __('Last Name','easynewsletter'), 27 "en_telephoneNumber" => "<label for='" . esc_attr($signupFormField) . "'>" . __('Telephone Number', 'easynewsletter'), 28 "en_eMailAddressValidation" => "<label for='" . esc_attr($signupFormField) . "'>" . __('Validate E-Mail', 'easynewsletter'), 29 "en_securityQuestion" => "", 30 default => "<label for='" . esc_attr($signupFormField) . "'>".$signupFormField 31 }; 32 33 echo match ( $signupFormField ) { 34 'en_eMailAddress', "en_eMailAddressValidation"=> "<input type='email' name='" . esc_attr($signupFormField) . "' id='" . esc_attr($signupFormField) . "' value='' required class='en_inputCheck' oninput='en_validationCheck()'>", 35 'telephoneNumber'=> "<input type=' tel' name='" . esc_attr($signupFormField) . "' id='" . esc_attr($signupFormField) . "' value='' required class='en_inputCheck'>", 36 "en_securityQuestion" => "", 37 default => "<input type='text' name='" . esc_attr($signupFormField) . "' id='" . esc_attr($signupFormField) . "' value='' required>", 38 }; 39 echo "</label>"; 40 } 41 echo "<input style='display: none' name='exitInput' id='exitInput'>"; 42 echo "<input type='hidden' name='en_subscriberCategory' value='". $category ."'>"; 43 } 44 ?> 45 <br> 46 <div id="en_human_check_div"><input type="checkbox" name="human_check" id="human_check" value=""><label for="human_check"><?php _e("I am a human being", 'easynewsletter')?></label></div> 47 <input type="submit" class="button button-primary" name="submit" id="submit" value="<?php esc_html_e ("Subscribe", 'easynewsletter')?>"> 48 49 50 <script> 51 document.getElementById('human_check').addEventListener('click', function() { 52 if (this.checked) { 53 const currentDate = new Date().toISOString().slice(0, 10); 54 this.value = currentDate; 55 } else { 56 this.value = ''; 57 } 58 }); 59 </script> 60 61 62 </form> 15 <form method="post" name="en_signup_form<?php echo esc_attr($idSuffix); ?>" id="en_signup_form<?php echo esc_attr($idSuffix); ?>" class="en_signup_form"> 16 <?php if ($preview) { echo '<fieldset disabled style="border:0;padding:0;margin:0;min-width:0">'; } ?> 17 <?php foreach ($signupFormFields as $field): 18 if (!is_string($field) || $field === 'en_securityQuestion') { continue; } 19 $inlineEmail = $inlineLayout && $field === 'en_eMailAddress'; 20 if ($inlineEmail) { echo '<div class="en_signup_inline">'; } 21 $type = in_array($field, ['en_eMailAddress', 'en_eMailAddressValidation'], true) ? 'email' : ($field === 'en_telephoneNumber' ? 'tel' : 'text'); 22 echo '<label for="' . esc_attr($field . $idSuffix) . '">' . esc_html(registrationBlock::fieldLabel($field)); 23 echo '<input type="' . $type . '" name="' . esc_attr($field) . '" id="' . esc_attr($field . $idSuffix) . '" value="" required class="en_inputCheck"></label>'; 24 if ($inlineEmail) { echo $submitButton . '</div>'; $inlineSubmitRendered = true; } 25 endforeach; ?> 26 <input style="display:none" name="exitInput" id="exitInput<?php echo esc_attr($idSuffix); ?>" tabindex="-1" autocomplete="off"> 27 <input type="hidden" name="en_subscriberCategory" value="<?php echo esc_attr($attributes['category']); ?>"> 28 <input type="hidden" name="en_form_instance" value="<?php echo esc_attr($formInstance); ?>"> 29 <?php if (!$inlineLayout) { echo '<br>'; } ?> 30 <div class="en_human_check_div" id="en_human_check_div<?php echo esc_attr($idSuffix); ?>"><input type="checkbox" name="human_check" id="human_check<?php echo esc_attr($idSuffix); ?>" value="" required><label for="human_check<?php echo esc_attr($idSuffix); ?>"><?php echo $signupCheckboxText; ?></label></div> 31 <?php if (!$inlineSubmitRendered) { echo $submitButton; } ?> 32 <?php if ($preview) { echo '</fieldset>'; } ?> 33 </form> 63 34 </div> 64 65 <?php66 if (in_array("en_eMailAddressValidation", $signupFormFields)) {67 wp_add_inline_script("en_registrationValidation", "68 const submitButton = document.getElementById('submit')69 let emailElement = document.getElementById('en_eMailAddress')70 let emailValidationElement = document.getElementById('en_eMailAddressValidation')71 72 submitButton.setAttribute('disabled', 'disabled')73 74 function en_validationCheck(){75 if (emailElement.value === emailValidationElement.value){76 submitButton.removeAttribute('disabled')77 emailValidationElement.style.color = 'black'78 } else {79 submitButton.setAttribute('disabled', 'disabled')80 emailValidationElement.style.color = 'red'81 }82 }");83 } -
easynewsletter/tags/4.2.0/src/core/resources/admin.css
r3495745 r3730982 7 7 float: right; 8 8 } 9 /* Settings subsections: plain links beneath the main plugin tabs. */ 10 .en-settings-navigation { 11 display: flex; 12 gap: 12px; 13 margin-bottom: 25px; 14 } 15 .en-settings-navigation a { text-decoration: none; } 16 .en-settings-navigation a[aria-current="page"] { color: #1d2327; font-weight: 600; } 17 .en-settings-navigation a:focus-visible { outline: 2px solid #2271b1; outline-offset: 4px; } 18 #easyNewsletterSettings [data-en-settings-panel][hidden] { display: none; } -
easynewsletter/tags/4.2.0/src/core/resources/newslettersPostTypeAdmin.js
r3696327 r3730982 3 3 const { ajaxUrl, nonce, sendingInProgress } = easyNewsletterData; 4 4 5 if (typeof __ === "undefined") {5 jQuery(document).ready(function ($) { 6 6 const { __ } = wp.i18n; 7 } 8 9 jQuery(document).ready(function ($) { 10 // add send-buttons and basic popup to html 11 setTimeout(function () { 12 // wrapper 13 $(".editor-header").after("<div class='en_sendButtonsWrapper'></div>"); 14 // sendNewsletter-button or stopSendingNewsletter-button depending on sending state 15 if (sendingInProgress === true || sendingInProgress === "true") { 16 // Newsletter is currently being sent 17 $(".en_sendButtonsWrapper").append('<button class="components-button editor-post-switch-to-draft is-tertiary en_stopSendingNewsletter dashicons-before dashicons-no">' + wp.i18n.__("Stop Sending", "easynewsletter") + "</button>"); 18 } else { 19 $(".en_sendButtonsWrapper").append("<button class='components-button editor-post-switch-to-draft is-tertiary en_wantToSendNewsletter dashicons-before dashicons-email-alt'>" + __("Send Newsletter Now", "easynewsletter") + "</button>"); 20 } 21 // sendTestMail-button 22 $(".en_sendButtonsWrapper").append("<button class='components-button editor-post-switch-to-draft is-tertiary en_TestmailButton dashicons-before dashicons-admin-tools'>" + __("Send Test Mail", "easynewsletter") + "</button>"); 23 // newsletterPopup 24 $("body").append('<div class="en_newsletterAdminPopup hidden"><div class="en_newsletterAdminPopupBackground"></div><div class="en_newsletterAdminPopupContent"></div></div>'); 25 }, 2000); 7 let toolbar = null; 8 let framePending = false; 9 10 function mountToolbar() { 11 const header = document.querySelector('.editor-header, .edit-post-header'); 12 if (!header) return; 13 if (!toolbar) { 14 toolbar = document.createElement('div'); 15 toolbar.className = 'en_sendButtonsWrapper'; 16 const sending = sendingInProgress === true || sendingInProgress === 'true'; 17 const send = document.createElement('button'); 18 send.type = 'button'; 19 send.className = 'components-button editor-post-switch-to-draft is-tertiary dashicons-before ' + 20 (sending ? 'en_stopSendingNewsletter dashicons-no' : 'en_wantToSendNewsletter dashicons-email-alt'); 21 send.textContent = sending ? __('Stop Sending', 'easynewsletter') : __('Send Newsletter Now', 'easynewsletter'); 22 const test = document.createElement('button'); 23 test.type = 'button'; 24 test.className = 'components-button editor-post-switch-to-draft is-tertiary en_TestmailButton dashicons-before dashicons-admin-tools'; 25 test.textContent = __('Send Test Mail', 'easynewsletter'); 26 toolbar.append(send, test); 27 } 28 if (header.nextElementSibling !== toolbar) { 29 header.after(toolbar); 30 // Also fires after Gutenberg replaces the header. Reuse our node so 31 // extension buttons and their current state survive a remount. 32 $(document).trigger('en:toolbar-ready', [toolbar]); 33 } 34 if (!document.querySelector('.en_newsletterAdminPopup')) { 35 $('body').append('<div class="en_newsletterAdminPopup hidden"><div class="en_newsletterAdminPopupBackground"></div><div class="en_newsletterAdminPopupContent"></div></div>'); 36 } 37 } 38 const toolbarObserver = new MutationObserver(function () { 39 if (framePending) return; 40 framePending = true; 41 requestAnimationFrame(function () { 42 framePending = false; 43 mountToolbar(); 44 }); 45 }); 46 toolbarObserver.observe(document.body, { childList: true, subtree: true }); 47 mountToolbar(); 48 window.addEventListener('pagehide', () => toolbarObserver.disconnect(), { once: true }); 26 49 27 50 // click on newsletter popup background - hide popup -
easynewsletter/tags/4.2.0/src/core/resources/newslettersPostTypeOverview.js
r3495745 r3730982 3 3 let id = $(element).parents(":eq(1)").attr("id"); 4 4 id = id.toString().replace("post-",""); 5 $(element).append("<span class=' copy'> | <a href='#' class='en_copy' data-post-id='"+id+"'>Copy</a></span>");5 $(element).append("<span class='en_row_action_copy'> | <a href='#' class='en_copy' data-post-id='"+id+"'>Duplizieren</a></span>"); 6 6 }) 7 7 -
easynewsletter/tags/4.2.0/src/core/resources/settingsPage.js
r3495745 r3730982 1 jQuery(document).ready(function ($) { 2 $("#easyNewsletterSettings").on('keydown', function(event) { 3 if (event.keyCode === 13){ 1 jQuery(function ($) { 2 const form = document.getElementById('easyNewsletterSettings'); 3 if (!form) return; 4 const links = Array.from(document.querySelectorAll('[data-en-settings-link]')); 5 const panels = Array.from(document.querySelectorAll('[data-en-settings-panel]')); 6 const selection = form.elements.en_settings_section; 7 const valid = new Set(links.map(link => link.dataset.enSettingsLink)); 8 function showSection(id) { 9 if (!valid.has(id)) id = 'general'; 10 panels.forEach(panel => { panel.hidden = panel.dataset.enSettingsPanel !== id; }); 11 links.forEach(link => { 12 if (link.dataset.enSettingsLink === id) link.setAttribute('aria-current', 'page'); 13 else link.removeAttribute('aria-current'); 14 }); 15 selection.value = id; 16 } 17 function fromHash() { 18 const match = location.hash.match(/^#en-settings-(general|signup|design)$/); 19 return match ? match[1] : selection.value; 20 } 21 links.forEach(link => link.addEventListener('click', event => { 22 event.preventDefault(); 23 const id = link.dataset.enSettingsLink; 24 showSection(id); 25 history.replaceState(null, '', link.getAttribute('href')); 26 })); 27 window.addEventListener('hashchange', () => showSection(fromHash())); 28 // Hidden fields stay enabled: switching sections never discards edits and 29 // saving submits the complete settings form, including unchecked checkboxes. 30 form.addEventListener('invalid', event => { 31 const panel = event.target.closest('[data-en-settings-panel]'); 32 if (panel) showSection(panel.dataset.enSettingsPanel); 33 }, true); 34 showSection(fromHash()); 35 $(form).on('keydown', function (event) { 36 if (event.key === 'Enter' && $(event.target).is('input:not([type=submit]):not([type=button])')) { 4 37 event.preventDefault(); 5 $("#en_submitSaveButton").click();38 document.getElementById('en_submitSaveButton').click(); 6 39 } 7 }) 8 }) 40 }); 41 }); -
easynewsletter/tags/4.2.0/src/core/settingsPageContent.php
r3710879 r3730982 14 14 } 15 15 16 $settingsValueMap['newsletterUseThemeStyles'] = isset($_POST['newsletterUseThemeStyles']) && $_POST['newsletterUseThemeStyles'] === 'true' ? 'true' : 'false'; 17 $excludedStyles = array_values(array_filter((array)($_POST['newsletterExcludedStyles'] ?? []), 'is_string')); 18 $settingsValueMap['newsletterExcludedStyles'] = wp_json_encode(array_map('sanitize_text_field', wp_unslash($excludedStyles))); 19 $settingsValueMap['newsletterCSSFile'] = esc_url_raw(wp_unslash((string)($_POST['newsletterCSSFile'] ?? '')), ['http', 'https']); 20 // Root-relative paths are convenient for CSS kept with the theme. 21 if (isset($_POST['newsletterCSSFile']) && str_starts_with(trim((string)$_POST['newsletterCSSFile']), '/')) { 22 $settingsValueMap['newsletterCSSFile'] = esc_url_raw(newsletterThemeStyles::absoluteURL(trim(wp_unslash($_POST['newsletterCSSFile'])), home_url('/'))); 23 } 24 update_option('en_style_revision', wp_generate_uuid4(), false); 25 delete_option('en_style_file_warnings'); 26 unset($settingsValueMap['en_settings_section']); 16 27 unset($settingsValueMap['page']); 17 28 unset($settingsValueMap['submit']); … … 39 50 } 40 51 continue; 52 } else if ($setting == "signupCheckboxText"){ 53 $value = wp_kses_post(wp_unslash($value)); 41 54 } else if ($setting == "newsletterContentWidth"){ 42 55 $value = (string)generateEmailHtml::contentWidth($value); 56 } else if ($setting == "newsletterSideSpacing"){ 57 $value = (string)generateEmailHtml::sideSpacing($value); 58 } else if ($setting == "newsletterWideWidth"){ 59 $value = (string)(filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => 200, 'max_range' => 2000]]) ?: 1100); 43 60 } else if ($setting == "newsletterCSS"){ 44 61 // Prevent escaping quotes … … 157 174 } 158 175 ?> 159 <h1 class="wp-heading-inline"><?php esc_html_e("Easy Newsletter Settings", "easynewsletter");?></h1> 160 <hr class="wp-header-end"> 176 <?php 177 $settingsSections = [ 178 'general' => ['label' => __('Allgemein', 'easynewsletter'), 'title' => __('Allgemeine Einstellungen', 'easynewsletter')], 179 'signup' => ['label' => __('Anmeldung', 'easynewsletter'), 'title' => __('Anmeldung', 'easynewsletter')], 180 'design' => ['label' => __('Design', 'easynewsletter'), 'title' => __('Newsletter-Design', 'easynewsletter')], 181 ]; 182 $settingSection = static function ($setting) { 183 if (in_array($setting, ['newsletterCSS', 'newsletterContentWidth', 'newsletterWideWidth', 'newsletterSideSpacing', 'newsletterUseThemeStyles', 'newsletterCSSFile', 'newsletterExcludedStyles'], true)) { return 'design'; } 184 if (in_array($setting, ['signupFormFields', 'signupCheckboxText', 'subscriberMode', 'subscriberCategory', 'subscriberRole', 'addedUserRoleKey', 'standardActivationPost', 'standardUnsubscribePost', 'standardWelcomePost', 'registrationPageID', 'registrationSuccessPageID', 'confirmationSuccessPageID', 'confirmationDeniedPageID', 'unsubscribedPageID', 'registrationFormPageID', 'unsubscribeFormPageID', 'unsubscribedConfirmedPageID'], true)) { return 'signup'; } 185 return 'general'; 186 }; 187 $selectedSection = sanitize_key($_POST['en_settings_section'] ?? 'general'); 188 if (!isset($settingsSections[$selectedSection])) { $selectedSection = 'general'; } 189 ?> 190 <nav class="en-settings-navigation" aria-label="<?php esc_attr_e('Einstellungsbereiche', 'easynewsletter'); ?>"> 191 <?php foreach ($settingsSections as $sectionID => $section): ?> 192 <a href="#en-settings-<?php echo esc_attr($sectionID); ?>" data-en-settings-link="<?php echo esc_attr($sectionID); ?>"><?php echo esc_html($section['label']); ?></a> 193 <?php endforeach; ?> 194 </nav> 161 195 <form method="post" name="easyNewsletterSettings" id="easyNewsletterSettings"> 196 <input type="hidden" name="en_settings_section" value="<?php echo esc_attr($selectedSection); ?>"> 197 <?php 198 $allSettings = (array)databaseConnector::instance()->getAllSettings(); 199 $designSettings = []; 200 foreach (['newsletterCSS', 'newsletterContentWidth', 'newsletterWideWidth', 'newsletterSideSpacing'] as $designKey) { 201 if (array_key_exists($designKey, $allSettings)) { 202 $designSettings[$designKey] = $allSettings[$designKey]; 203 unset($allSettings[$designKey]); 204 } 205 } 206 $allSettings = array_merge($allSettings, $designSettings); 207 ?> 208 <?php foreach ($settingsSections as $sectionID => $section): ?> 209 <section id="en-settings-<?php echo esc_attr($sectionID); ?>" data-en-settings-panel="<?php echo esc_attr($sectionID); ?>" aria-labelledby="en-settings-heading-<?php echo esc_attr($sectionID); ?>"> 210 <h1 id="en-settings-heading-<?php echo esc_attr($sectionID); ?>"><?php echo esc_html($section['title']); ?></h1> 162 211 <table class="form-table"> 163 212 <tbody> 164 213 <?php 165 $allSettings = databaseConnector::instance()->getAllSettings();166 214 foreach ($allSettings as $setting => $value){ 215 if ($settingSection($setting) !== $sectionID) { continue; } 216 217 if (in_array($setting, ['newsletterUseThemeStyles', 'newsletterCSSFile', 'newsletterExcludedStyles'], true)) { continue; } 218 if ($setting === 'newsletterCSS') { 219 require_once __DIR__ . '/newsletterStyleSettings.php'; 220 newsletterStyleSettings::render(); 221 } 167 222 168 223 //TODO Maybe put them somewhere to be seen in a hidden settings menu or something. … … 195 250 'intervalInSeconds' => "<th scope='row'>".__("Interval in seconds ", 'easynewsletter')."</th>", 196 251 'signupFormFields' => "<th scope='row'>".__("Signup form fields ", 'easynewsletter')."</th>", 252 'signupCheckboxText' => "<th scope='row'>".esc_html__("Text der Pflicht-Checkbox im Anmeldeformular", 'easynewsletter')."</th>", 197 253 'activeNewsletter' => "<th scope='row'>".__("Currently active newsletter ", 'easynewsletter')."</th>", 198 254 'activeNewsletterID' => "<th scope='row'>".__("Currently active newsletter ID ", 'easynewsletter')."</th>", … … 204 260 'subscriberCategory' => "<th scope='row'>".__("Available subscriber category ", 'easynewsletter')."</th>", 205 261 'subscriberRole' => "<th scope='row'>".__("Available subscriber roles ", 'easynewsletter')."</th>", 206 'newsletterContentWidth' => "<th scope='row'>".__("Newsletter-Inhaltsbreite (px)", 'easynewsletter')."</th>", 207 'newsletterCSS' => "<th scope='row'>".__("Custom Newsletter CSS ", 'easynewsletter')."</th>", 262 'newsletterContentWidth' => "<th scope='row'>".__("Standard-Breite (px)", 'easynewsletter')."</th>", 263 'newsletterSideSpacing' => "<th scope='row'>".__("Seitlicher Inhaltsabstand (px)", 'easynewsletter')."</th>", 264 'newsletterWideWidth' => "<th scope='row'>".__("Weite Breite (px)", 'easynewsletter')."</th>", 265 'newsletterCSS' => "<th scope='row'>".__("Benutzerdefiniertes CSS", 'easynewsletter')."</th>", 208 266 'addedUserRoleKey' => "<th scope='row'>".__("New user role key ", 'easynewsletter')."</th>", 209 267 'registrationPageID' => "<th scope='row'>".__("Registration page id ", 'easynewsletter')."</th>", … … 219 277 'senderName' => "<input type='text' name='" . esc_attr($setting) . "' id='" . esc_attr($setting) . "' value='" .stripslashes(esc_attr($value)) . "'>", 220 278 'maxEmailPerInterval', 'intervalInSeconds' => "<input type='number' name='" . esc_attr($setting) . "' id='" . esc_attr($setting) . "' value='" . esc_attr($value) . "'>", 221 'newsletterContentWidth' => "<input type='number' min='200' max='2000' step='1' name='newsletterContentWidth' id='newsletterContentWidth' value='" . esc_attr($value) . "'><p class='description'>" . esc_html__("Maximale Desktop-Inhaltsbreite und Grundlage für die Bildgrößenberechnung. Auf schmalen Bildschirmen bleibt der Newsletter flexibel. Standard: 800 px.", 'easynewsletter') . "</p>", 279 'newsletterContentWidth' => "<input type='number' min='200' max='2000' step='1' name='newsletterContentWidth' id='newsletterContentWidth' value='" . esc_attr($value) . "'>", 280 'newsletterSideSpacing' => "<input type='number' min='0' max='200' step='1' name='newsletterSideSpacing' id='newsletterSideSpacing' value='" . esc_attr($value) . "'>", 281 'newsletterWideWidth' => "<input type='number' min='200' max='2000' step='1' name='newsletterWideWidth' id='newsletterWideWidth' value='" . esc_attr($value) . "'>", 222 282 'signupFormFields' => createCheckboxes($value), 283 'signupCheckboxText' => "<textarea name='signupCheckboxText' id='signupCheckboxText' rows='4' cols='50' aria-describedby='signupCheckboxTextDescription'>" . esc_textarea($value) . "</textarea><p class='description' id='signupCheckboxTextDescription'>" . esc_html__("Eigener Text, z. B. zur Bestätigung der Datenschutzhinweise. Sicheres HTML wie Links und Hervorhebungen ist erlaubt. Leer lassen für den Standardtext „Ich bin ein Mensch“. Die bestehende Spam-Prüfung bleibt aktiv.", 'easynewsletter') . "</p>", 223 284 'subscriberMode' => generateModeSelection(), 224 285 'sendingInProgress', 'activeNewsletter', 'activeNewsletterID' => "<input type='text' name='" . $setting . "' id='" . $setting . "' value='" . $value . "' disabled>", 225 286 'subscriberCategory' => generateTargetGroupsCategory(), 226 287 'subscriberRole' => generateTargetGroupsRoles(), 227 'newsletterCSS' => "<textarea name='" . esc_attr($setting) . "' id='" . esc_attr($setting) . "'rows='4' cols='50' form='easyNewsletterSettings'>". $value."</textarea>",288 'newsletterCSS' => "<textarea name='" . esc_attr($setting) . "' id='" . esc_attr($setting) . "'rows='4' cols='50' form='easyNewsletterSettings'>".esc_textarea($value)."</textarea>", 228 289 default => "<input type='text' name='" . esc_attr($setting) . "' id='" . esc_attr($setting) . "' value='" . esc_attr($value) . "'>", 229 290 }; … … 232 293 </tbody> 233 294 </table> 295 </section> 296 <?php endforeach; ?> 234 297 <?php echo "<input type='hidden' name='wp_en_nonce' value='".wp_create_nonce()."'>"?> 235 298 <input type="submit" id="en_submitSaveButton" class="button button-primary" name="submit" value="<?php _e("Save", 'easynewsletter')?>"> 236 299 300 <div data-en-settings-panel="general"> 237 301 <h3><?php _e("Press this Button to stop the Newsletter sending Process", 'easynewsletter')?></h3> 238 302 <input type="submit" class="button button-primary" style="background-color: red; border-color: red" name="stopNewsletterSending" value="<?php _e("Stop Newsletter Sending Process", 'easynewsletter')?>"> 303 </div> 239 304 </form> 240 305 <br> 241 306 </div> 242 -
easynewsletter/tags/4.2.0/vendor-prefixed/autoload.php
r3710879 r3730982 20 20 require_once __DIR__ . '/composer/autoload_real.php'; 21 21 22 return ComposerAutoloaderInit 9130e49187f279f2bf1d421ae64cfb1b::getLoader();22 return ComposerAutoloaderInit2a106f58d8aeb22de715011744b3955b::getLoader(); -
easynewsletter/tags/4.2.0/vendor-prefixed/composer/autoload_real.php
r3710879 r3730982 3 3 // autoload_real.php @generated by Composer 4 4 5 class ComposerAutoloaderInit 9130e49187f279f2bf1d421ae64cfb1b5 class ComposerAutoloaderInit2a106f58d8aeb22de715011744b3955b 6 6 { 7 7 private static $loader; … … 23 23 } 24 24 25 spl_autoload_register(array('ComposerAutoloaderInit 9130e49187f279f2bf1d421ae64cfb1b', 'loadClassLoader'), true, true);25 spl_autoload_register(array('ComposerAutoloaderInit2a106f58d8aeb22de715011744b3955b', 'loadClassLoader'), true, true); 26 26 self::$loader = $loader = new \Farn\EasyNewsletter\Composer\Autoload\ClassLoader(\dirname(__DIR__)); 27 spl_autoload_unregister(array('ComposerAutoloaderInit 9130e49187f279f2bf1d421ae64cfb1b', 'loadClassLoader'));27 spl_autoload_unregister(array('ComposerAutoloaderInit2a106f58d8aeb22de715011744b3955b', 'loadClassLoader')); 28 28 29 29 require __DIR__ . '/autoload_static.php'; 30 call_user_func(\Farn\EasyNewsletter\Composer\Autoload\ComposerStaticInit 9130e49187f279f2bf1d421ae64cfb1b::getInitializer($loader));30 call_user_func(\Farn\EasyNewsletter\Composer\Autoload\ComposerStaticInit2a106f58d8aeb22de715011744b3955b::getInitializer($loader)); 31 31 32 32 $loader->setClassMapAuthoritative(true); -
easynewsletter/tags/4.2.0/vendor-prefixed/composer/autoload_static.php
r3710879 r3730982 5 5 namespace Farn\EasyNewsletter\Composer\Autoload; 6 6 7 class ComposerStaticInit 9130e49187f279f2bf1d421ae64cfb1b7 class ComposerStaticInit2a106f58d8aeb22de715011744b3955b 8 8 { 9 9 public static $prefixLengthsPsr4 = array ( … … 33 33 { 34 34 return \Closure::bind(function () use ($loader) { 35 $loader->prefixLengthsPsr4 = ComposerStaticInit 9130e49187f279f2bf1d421ae64cfb1b::$prefixLengthsPsr4;36 $loader->prefixDirsPsr4 = ComposerStaticInit 9130e49187f279f2bf1d421ae64cfb1b::$prefixDirsPsr4;37 $loader->classMap = ComposerStaticInit 9130e49187f279f2bf1d421ae64cfb1b::$classMap;35 $loader->prefixLengthsPsr4 = ComposerStaticInit2a106f58d8aeb22de715011744b3955b::$prefixLengthsPsr4; 36 $loader->prefixDirsPsr4 = ComposerStaticInit2a106f58d8aeb22de715011744b3955b::$prefixDirsPsr4; 37 $loader->classMap = ComposerStaticInit2a106f58d8aeb22de715011744b3955b::$classMap; 38 38 39 39 }, null, ClassLoader::class); -
easynewsletter/trunk/README.txt
r3710879 r3730982 4 4 Requires at least: 6.0 5 5 Tested up to: 7.0 6 Stable tag: 4. 1.46 Stable tag: 4.2.0 7 7 Requires PHP: 8.0 8 8 License: GPLv3 or later … … 72 72 == Changelog == 73 73 74 = 4.2.0 = 75 Fix - Automatic image sizing in responsive emails. 76 New - Added fluid block layouts and signup forms. 77 New - Introduced theme-based newsletter styling pipeline. 78 New - Added inline registration form layout. 79 New - Configurable signup checkbox text. 80 74 81 = 4.1.4 = 75 82 New - Added newsletter email rendering pipeline. -
easynewsletter/trunk/easynewsletter.php
r3710879 r3730982 5 5 Plugin URI: https://www.farnlabs.de/ 6 6 Description: Provides a Newsletter functionality with complete WordPress integration. 7 Version: 4. 1.47 Version: 4.2.0 8 8 Author: FARN – digital brand design 9 9 Author URI: https://www.farn.de/ -
easynewsletter/trunk/src/core/databaseConnector.php
r3710879 r3730982 31 31 'intervalInSeconds' => '300', 32 32 'signupFormFields' => '', // will be set as a serialized array('en_eMailAddress') in the constructor. 33 'signupCheckboxText' => '', // Empty uses the translated default checkbox label. 33 34 'activeNewsletter' => '', 34 35 'activeNewsletterID' => '', … … 42 43 'subscriberCategory' => '', // will be set as a serialized array('default') in the constructor. 43 44 'subscriberRole' => '', // will be set as a serialized array('default') in the constructor. 45 'newsletterUseThemeStyles' => 'false', // Upgrades always opt out. 46 'newsletterCSSFile' => '', 47 'newsletterExcludedStyles' => '[]', 44 48 'newsletterCSS' => '', 45 49 'newsletterContentWidth' => '800', 50 'newsletterWideWidth' => '1100', 51 'newsletterSideSpacing' => '0', // Preserve existing installations. 46 52 'addedUserRoleKey' => 'en_user_subscriber', 47 53 "registrationPageID" => "", … … 125 131 $results = array_column($results,0); 126 132 133 $freshInstallation = empty($results); 127 134 foreach ($this->defaultSettings as $key => $value) { 135 if ($key === 'newsletterUseThemeStyles' && $freshInstallation) { $value = 'true'; } 136 if ($key === 'newsletterSideSpacing' && $freshInstallation) { $value = '20'; } 128 137 if(!in_array($key, $results)){ 129 138 $this->wpdb->insert($this->tableName, -
easynewsletter/trunk/src/core/generateEmailHtml.php
r3710879 r3730982 3 3 4 4 require_once __DIR__ . '/resources/vendor/cssin/CSSIN.php'; 5 require_once __DIR__ . '/newsletterThemeStyles.php'; 6 require_once __DIR__ . '/newsletterStyleInliner.php'; 7 require_once __DIR__ . '/newsletterBlockLayout.php'; 5 8 6 9 use CSSIN; … … 10 13 class generateEmailHtml { 11 14 public static function generate($postID) : string{ 15 $extendedStyles = newsletterThemeStyles::extended(); 16 $baseCSS = file_get_contents(__DIR__ . '/resources/newsletterBaseStyle.css'); 17 if ($extendedStyles) { 18 // Let the theme's inherited font size reach the email wrapper. 19 $baseCSS = str_replace(['font-size: 16px!important;', 'line-height: 1.3;'], '', $baseCSS); 20 } 21 $additionalCSS = $extendedStyles ? newsletterThemeStyles::css((int)$postID) : ''; 12 22 $contentWidth = self::contentWidth(databaseConnector::instance()->getSettingFromDB("newsletterContentWidth")); 13 23 $enl_mailcontent_head = '<!DOCTYPE html><html lang="de"> … … 20 30 <style type="text/css"> 21 31 '.file_get_contents( __DIR__ . "/resources/newsletterBlocklibraryStyle.css" ).' 22 '.file_get_contents( __DIR__ . "/resources/newsletterBaseStyle.css" ).' 32 '.$baseCSS.' 33 '.$additionalCSS.' 23 34 '.databaseConnector::instance()->getSettingFromDB("newsletterCSS").' 24 35 </style> … … 65 76 $enl_full_mailcontent = $enl_mailcontent_head . $enl_mailcontent_body . $enl_mailcontent_foot; 66 77 78 if ($extendedStyles) { 79 // Normalize before variable resolution too: its legacy parser 80 // must see ordinary root declarations outside conditional rules. 81 $enl_full_mailcontent = preg_replace_callback('~(<style\b[^>]*>)(.*?)(</style>)~is', static function ($match) { 82 return $match[1] . newsletterStyleInliner::emailCSS($match[2]) . $match[3]; 83 }, $enl_full_mailcontent); 84 } 85 67 86 /* Use CSSIN to inline all styles */ 68 87 $cssin = new CSSIN(); 69 $enl_full_mailcontent_inlined = $cssin->inlineCSS( self::prepare($enl_full_mailcontent) ); 88 $enl_full_mailcontent_inlined = $extendedStyles 89 ? newsletterStyleInliner::inline(self::prepare($enl_full_mailcontent, false), $contentWidth) 90 : $cssin->inlineCSS(self::prepare($enl_full_mailcontent)); 70 91 71 92 //return $enl_full_mailcontent; 72 return self::transform($enl_full_mailcontent_inlined, $contentWidth );93 return self::transform($enl_full_mailcontent_inlined, $contentWidth, $extendedStyles, (int)(databaseConnector::instance()->getSettingFromDB('newsletterWideWidth') ?: 1100), self::sideSpacing(databaseConnector::instance()->getSettingFromDB('newsletterSideSpacing'))); 73 94 } catch (\Throwable $e) { 74 95 Log::log("An error occurred while processing the mail content: " . $e->getMessage()); … … 77 98 } 78 99 100 public static function sideSpacing($value): int { 101 $valid = filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0, 'max_range' => 200]]); 102 return $valid === false ? 0 : $valid; 103 } 104 79 105 private int $layoutWidth = 800; 106 private float $rootFont = 16; 80 107 private ?\DOMElement $layoutRoot = null; 81 108 public static function contentWidth($value): int { … … 99 126 private static function length(string $value, float $reference, float $font = 16): ?float { 100 127 $value = self::plain($value); 128 if (preg_match('/^(calc|clamp|min|max)\(/i', $value)) { 129 return newsletterCSSLength::pixels($value, $reference, $font); 130 } 101 131 if (!preg_match('/^(\d+(?:\.\d+)?)(px|pt|%|em|rem)?$/i', $value, $match)) { return null; } 102 132 $number = (float)$match[1]; … … 203 233 204 234 /** Remember author intent before global image CSS is inlined. */ 205 public static function prepare(string $html ): string {235 public static function prepare(string $html, bool $convertColumns = true): string { 206 236 if (trim($html) === '') { return $html; } 207 237 $dom = self::load($html); 208 238 self::resolveDocumentVariables($dom); 239 foreach ((new \DOMXPath($dom))->query('//div[contains(concat(" ",normalize-space(@class)," ")," wp-block-column ")]') as $column) { 240 $author = self::styles($column->getAttribute('style')); 241 if (isset($author['flex-basis'])) { $column->setAttribute('data-en-author-basis', $author['flex-basis']); } 242 } 243 foreach ((new \DOMXPath($dom))->query('//div[contains(concat(" ",normalize-space(@class)," ")," wp-block-group ")]') as $group) { 244 $padding = array_filter(self::styles($group->getAttribute('style')), static function ($name) { return str_starts_with($name, 'padding'); }, ARRAY_FILTER_USE_KEY); 245 if ($padding) { $group->setAttribute('data-en-author-padding', json_encode($padding)); } 246 } 247 if ($convertColumns) { self::prepareColumns($dom); } 248 foreach ($dom->getElementsByTagName('img') as $image) { 249 $styles = self::styles($image->getAttribute('style')); 250 $intent = []; 251 foreach (['width', 'height', 'aspect-ratio', 'object-fit', 'object-position'] as $key) { 252 if (isset($styles[$key])) { $intent[$key] = $styles[$key]; } 253 } 254 // Older Gutenberg versions put intentional dimensions in attributes. 255 $parent = $image->parentNode; 256 while ($parent instanceof \DOMElement && in_array($parent->tagName, ['a', 'figure', 'div'], true)) { 257 if (preg_match('/\bis-resized\b/', $parent->getAttribute('class'))) { 258 foreach (['width', 'height'] as $key) { 259 if (!isset($intent[$key]) && preg_match('/^[1-9]\d*$/', $image->getAttribute($key))) { 260 $intent[$key] = $image->getAttribute($key) . 'px'; 261 } 262 } 263 break; 264 } 265 $parent = $parent->parentNode; 266 } 267 $image->setAttribute('data-en-image-intent', json_encode($intent)); 268 } 269 return $dom->saveHTML(); 270 } 271 272 private static function prepareColumns(\DOMDocument $dom): void { 209 273 // Work from inside out so nested column groups keep their own row/gap. 210 274 $groups = (new \DOMXPath($dom))->query('//div[contains(concat(" ", normalize-space(@class), " "), " wp-block-columns ")]'); 211 275 foreach (array_reverse(iterator_to_array($groups)) as $group) { 212 276 $groupStyles = self::styles($group->getAttribute('style')); 213 $gap = preg_split('/\s+/', self::plain($groupStyles['gap'] ?? '40px'));277 $gap = array_values(array_filter(newsletterStyleInliner::split(self::plain($groupStyles['gap'] ?? '40px'), ' '), 'strlen')); 214 278 $columnGap = $groupStyles['column-gap'] ?? ($gap[1] ?? $gap[0]); 215 279 $rowGap = $groupStyles['row-gap'] ?? $gap[0]; … … 239 303 foreach ($column->attributes as $attribute) { $cell->setAttribute($attribute->name, $attribute->value); } 240 304 $css = self::styles($cell->getAttribute('style')); 241 $cell->setAttribute('data-en-column-basis', $css['flex-basis'] ?? $css['width'] ?? ''); 305 $cell->setAttribute('data-en-column-basis', $cell->getAttribute('data-en-author-basis') ?: ($css['flex-basis'] ?? $css['width'] ?? '')); 306 $cell->setAttribute('data-en-column-grow', $cell->hasAttribute('data-en-author-basis') ? '0' : ($css['flex-grow'] ?? '1')); 307 $cell->removeAttribute('data-en-author-basis'); 242 308 unset($css['flex-basis']); 309 $alignment = $groupStyles['align-items'] ?? 'top'; 310 foreach (['top', 'center', 'bottom'] as $position) { 311 if (preg_match('/\bis-vertically-aligned-' . $position . '\b/', $group->getAttribute('class'))) { $alignment = $position; } 312 } 313 // A per-column choice takes precedence over the group choice. 314 $alignment = $css['align-self'] ?? $alignment; 315 foreach (['top', 'center', 'bottom'] as $position) { 316 if (preg_match('/\bis-vertically-aligned-' . $position . '\b/', $column->getAttribute('class'))) { $alignment = $position; } 317 } 318 $alignment = match ($alignment) { 'center' => 'middle', 'bottom', 'flex-end', 'end' => 'bottom', default => 'top' }; 319 $css['vertical-align'] = $alignment; 320 $cell->setAttribute('valign', $alignment); 243 321 $cell->setAttribute('style', \CSSIN::arrayToStyle($css)); 244 322 while ($column->firstChild) { $cell->appendChild($column->firstChild); } … … 247 325 $group->parentNode->replaceChild($table, $group); 248 326 } 249 foreach ($dom->getElementsByTagName('img') as $image) { 250 $styles = self::styles($image->getAttribute('style')); 251 $intent = []; 252 foreach (['width', 'height', 'aspect-ratio', 'object-fit', 'object-position'] as $key) { 253 if (isset($styles[$key])) { $intent[$key] = $styles[$key]; } 254 } 255 // Older Gutenberg versions put intentional dimensions in attributes. 256 $parent = $image->parentNode; 257 while ($parent instanceof \DOMElement && in_array($parent->tagName, ['a', 'figure', 'div'], true)) { 258 if (preg_match('/\bis-resized\b/', $parent->getAttribute('class'))) { 259 foreach (['width', 'height'] as $key) { 260 if (!isset($intent[$key]) && preg_match('/^[1-9]\d*$/', $image->getAttribute($key))) { 261 $intent[$key] = $image->getAttribute($key) . 'px'; 262 } 263 } 264 break; 265 } 266 $parent = $parent->parentNode; 267 } 268 $image->setAttribute('data-en-image-intent', json_encode($intent)); 269 } 270 return $dom->saveHTML(); 271 } 272 273 public static function transform(string $html, int $width): string { 327 } 328 329 public static function transform(string $html, int $width, bool $convertColumns = false, ?int $wideWidth = null, int $sideSpacing = 0): string { 274 330 if (trim($html) === '') { return $html; } 275 331 $dom = self::load($html); 332 $fluidSections = $wideWidth !== null && newsletterBlockLayout::apply($dom, self::contentWidth($width), $wideWidth >= 200 && $wideWidth <= 2000 ? $wideWidth : 1100, self::sideSpacing($sideSpacing)); 333 if ($convertColumns) { self::prepareColumns($dom); } 276 334 self::wrapColumnPadding($dom); 277 335 self::centerImages($dom); … … 282 340 } 283 341 $instance = new self(); 284 $instance->layoutWidth = self::contentWidth($width); 342 $instance->layoutWidth = $fluidSections ? max(self::contentWidth($width), $wideWidth >= 200 && $wideWidth <= 2000 ? $wideWidth : 1100) : self::contentWidth($width); 343 $rootStyles = self::styles($dom->documentElement->getAttribute('style')); 344 $instance->rootFont = self::length($rootStyles['font-size'] ?? '', 16, 16) ?? 16; 285 345 $instance->layoutRoot = (new \DOMXPath($dom))->query('//table[contains(concat(" ", normalize-space(@class), " "), " enl-wrapper ")]')->item(0); 286 if ($instance->layoutRoot) { 346 if ($fluidSections && $instance->layoutRoot) { 347 $instance->layoutWidth = (int)$instance->layoutRoot->getAttribute('data-en-reference-width'); 348 $instance->layoutRoot->removeAttribute('data-en-reference-width'); 349 } 350 if ($instance->layoutRoot && !$fluidSections) { 287 351 $rootStyles = self::styles($instance->layoutRoot->getAttribute('style')); 288 352 $instance->layoutWidth = (int)round(min($instance->layoutWidth, … … 298 362 } 299 363 $instance->walk($dom->documentElement, $instance->layoutWidth); 300 $instance->outlookFrame(); 364 if (!$fluidSections) { $instance->outlookFrame(); } 365 foreach ((new \DOMXPath($dom))->query('//*[@data-en-author-padding or @data-en-fluid-sections]') as $node) { 366 $node->removeAttribute('data-en-author-padding'); $node->removeAttribute('data-en-fluid-sections'); 367 } 301 368 return $dom->saveHTML(); 302 369 } … … 340 407 341 408 private static function paddingPixels(array $styles, float $reference, float $font): string { 342 $parts = preg_split('/\s+/', self::plain($styles['padding'] ?? '0'));409 $parts = array_values(array_filter(newsletterStyleInliner::split(self::plain($styles['padding'] ?? '0'), ' '), 'strlen')); 343 410 $values = [$parts[0], $parts[1] ?? $parts[0], $parts[2] ?? $parts[0], $parts[3] ?? $parts[1] ?? $parts[0]]; 344 411 foreach (['top', 'right', 'bottom', 'left'] as $index => $side) { … … 443 510 } 444 511 $styles = self::styles($node->getAttribute('style')); 512 foreach ($styles as $name => &$value) { 513 if (!str_starts_with($name, '--') && !in_array($name, ['font-family', 'content'], true)) { 514 $value = newsletterCSSLength::resolveRem($value, $node->tagName === 'html' && $name === 'font-size' ? 16 : $this->rootFont); 515 } 516 } 517 unset($value); 445 518 if ($node->tagName === 'table' && preg_match('/\bwp-block-columns\b/', $node->getAttribute('class'))) { 446 519 $styles['display'] = 'table'; … … 448 521 } 449 522 $font = self::length($styles['font-size'] ?? '', $font, $font) ?? $font; 523 foreach ($styles as $property => $value) { 524 if (!preg_match('/^(?:padding|margin)(?:-(?:top|right|bottom|left))?$|^(?:font-size|width|height|min-width|max-width|min-height|max-height)$/', $property) 525 || !preg_match('/(?:calc|clamp|min|max)\(/i', $value)) { continue; } 526 $parts = array_values(array_filter(newsletterStyleInliner::split(self::plain($value), ' '), 'strlen')); 527 foreach ($parts as &$part) { 528 $pixels = newsletterCSSLength::pixels($part, $available, $font, $this->layoutWidth); 529 if ($pixels !== null) { $part = round($pixels, 3) . 'px'; } 530 } 531 unset($part); 532 $styles[$property] = implode(' ', $parts); 533 } 450 534 if (in_array($node->tagName, ['td', 'th'], true)) { 451 535 $paddingStyles = $styles; 536 if ($node->hasAttribute('data-en-outlook-padding')) { 537 $paddingStyles = json_decode($node->getAttribute('data-en-outlook-padding'), true) ?: []; 538 $node->removeAttribute('data-en-outlook-padding'); 539 } 452 540 if (strpos(' ' . $node->getAttribute('class') . ' ', ' en-button-cell ') !== false) { 453 541 $link = $node->getElementsByTagName('a')->item(0); … … 486 574 $width = $allocated ?? min($available, self::length($styles['width'] ?? $node->getAttribute('width'), $available, $font) ?? $available); 487 575 $width = min($width, self::length($styles['max-width'] ?? '', $available, $font) ?? $width); 488 $padding = preg_split('/\s+/', self::plain($styles['padding'] ?? '0'));576 $padding = array_values(array_filter(newsletterStyleInliner::split(self::plain($styles['padding'] ?? '0'), ' '), 'strlen')); 489 577 $left = $styles['padding-left'] ?? ($padding[3] ?? $padding[1] ?? $padding[0]); 490 578 $right = $styles['padding-right'] ?? ($padding[1] ?? $padding[0]); … … 545 633 $gap = $count > 1 ? min($gap, max(0, ($width - $count) / ($count - 1))) : 0; 546 634 $contentWidth = $width - $gap * ($count - 1); 547 $weights = []; $ used = 0; $auto= 0;635 $weights = []; $growth = []; $used = 0; 548 636 foreach ($columns as $column) { 549 637 $basis = self::length($column->getAttribute('data-en-column-basis'), $contentWidth, $font); 550 $weights[] = $basis; 551 if ($basis === null) { $auto++; } else { $used += $basis; } 552 } 553 foreach ($weights as &$weight) { $weight = $weight ?? max(1, $contentWidth - $used) / max(1, $auto); } 554 unset($weight); 638 $weights[] = $basis ?? 0; 639 $growth[] = max(0, (float)$column->getAttribute('data-en-column-grow')); 640 $used += $basis ?? 0; 641 } 642 $free = max(0, $contentWidth - $used); 643 $totalGrowth = array_sum($growth); 644 if ($totalGrowth > 0) { 645 foreach ($weights as $index => &$weight) { $weight += $free * $growth[$index] / $totalGrowth; } 646 unset($weight); 647 } 555 648 $sum = array_sum($weights); 556 649 if ($sum <= 0) { $weights = array_fill(0, $count, 1); $sum = $count; } … … 568 661 $cellWidth = $contentWidth * $fraction; 569 662 $cell->removeAttribute('data-en-column-basis'); 663 $cell->removeAttribute('data-en-column-grow'); 570 664 // Numeric desktop fallback plus proportional widths for narrow clients. 571 665 // Gutters scale with the row when columns stay side by side. … … 624 718 // Keep tighter author caps (e.g. width:100%; max-width:200px). 625 719 $styles['max-width'] = $styles['max-width'] ?? '100%'; 720 if (!$fixed && strpos($widthCSS, '%') === false) { 721 // Automatic images must shrink through width itself: Thunderbird can 722 // override max-width with its message stylesheet. Keep the computed 723 // desktop size as a cap so small intrinsic images are not enlarged. 724 $cap = self::plain($styles['max-width']); 725 $styles['width'] = preg_match('/^(\d+(?:\.\d+)?)%$/', $cap, $match) 726 ? min(100, (float)$match[1]) . '%' : '100%'; 727 $styles['max-width'] = $width . 'px'; 728 } 626 729 $styles['height'] = 'auto'; 627 730 unset($styles['object-fit'], $styles['object-position'], $styles['aspect-ratio']); -
easynewsletter/trunk/src/core/menuPage.php
r3696327 r3730982 54 54 'easyNewsletter', 55 55 [$this, "menuPageContent"]); 56 add_submenu_page( 57 'easyNewsletter', 58 __('Overview', 'easynewsletter'), 59 __('Overview', 'easynewsletter'), 60 'manage_options', 61 'easyNewsletter', 62 [$this, 'menuPageContent'], 63 0 64 ); 56 65 } 57 66 … … 118 127 public function settingsPageContent(): void{ 119 128 //add settingsPage.js script to this site 120 wp_enqueue_script( "en_settingsPageJS", plugins_url( 'resources/settingsPage.js', __FILE__ ) );129 wp_enqueue_script( "en_settingsPageJS", plugins_url( 'resources/settingsPage.js', __FILE__ ), ['jquery'], filemtime(__DIR__ . '/resources/settingsPage.js'), true ); 121 130 122 131 include ('settingsPageContent.php'); … … 135 144 // styles for menu icon 136 145 wp_enqueue_style("en_menuIconCSS", plugins_url('resources/menuIcon.css', __FILE__ )); 137 wp_enqueue_style("en_admin", plugins_url('resources/admin.css', __FILE__ ));146 wp_enqueue_style("en_admin", plugins_url('resources/admin.css', __FILE__), [], filemtime(__DIR__ . '/resources/admin.css')); 138 147 } 139 148 -
easynewsletter/trunk/src/core/newsletterPageTemplate.php
r3710879 r3730982 47 47 </nav> 48 48 </header> 49 <iframe title="<?php echo $outlook ? 'Outlook-Simulation' : 'Standard-Vorschau'; ?>" sandbox="allow- popups allow-popups-to-escape-sandbox" srcdoc="<?php echo esc_attr($html); ?>"></iframe>49 <iframe title="<?php echo $outlook ? 'Outlook-Simulation' : 'Standard-Vorschau'; ?>" sandbox="allow-same-origin allow-popups allow-popups-to-escape-sandbox" srcdoc="<?php echo esc_attr($html); ?>"></iframe> 50 50 </body> 51 51 </html> -
easynewsletter/trunk/src/core/newsletterPostType.php
r3710879 r3730982 28 28 */ 29 29 public function init(): void { 30 newsletterThemeStyles::init(); 30 31 add_action('init', array($this, 'createNewsletterPostType')); 31 32 add_filter( 'manage_en_newsletters_posts_columns', array( $this, "addBackendNewsletterColumns" ) ); … … 37 38 add_action( 'admin_enqueue_scripts', array($this, 'enqueue_script'), 11 ); 38 39 add_filter('block_editor_settings_all', [$this, 'newsletterEditorStyles'], 20, 2); 40 add_filter('post_row_actions', [$this, 'newsletterRowActions'], PHP_INT_MAX, 2); 41 add_filter('page_row_actions', [$this, 'newsletterRowActions'], PHP_INT_MAX, 2); 42 foreach (['manage_en_newsletters_posts_columns', 'manage_edit-en_newsletters_columns', 'manage_edit-en_newsletters_sortable_columns'] as $hook) { 43 add_filter($hook, [$this, 'removeNewsletterSEOColumns'], PHP_INT_MAX); 44 } 39 45 40 46 add_action( 'wp_ajax_en_sendNewsletterTestMail', array( $this,'en_sendNewsletterTestMail')); … … 61 67 add_action('admin_enqueue_scripts', [$this, "add_script_to_overview"]); 62 68 add_action( 'wp_ajax_en_copyNewsletter', array( $this,'en_copyNewsletter')); 63 add_action( 'wp_ajax_nopriv_en_copyNewsletter', array( $this,'en_copyNewsletter'));64 69 65 70 metaBoxes::addAllMetaBoxes(); … … 87 92 'labels' => array( 88 93 'name' => __( 'Newsletter' ), 89 'singular_name' => __( 'Newsletter' ) 94 'singular_name' => __( 'Newsletter' ), 95 'add_new' => __('Neuer Newsletter', 'easynewsletter'), 96 'add_new_item' => __('Neuer Newsletter', 'easynewsletter') 90 97 ), 91 98 'public' => true, … … 268 275 if( 'en_newsletters' == $post_type ) { 269 276 $resources_url = EASY_NEWSLETTER_URL . 'src/core/resources/'; 270 wp_enqueue_script( 'newsletters-admin-edit-script', $resources_url . 'newslettersPostTypeAdmin.js', [ 'jquery', 'wp-i18n'] );277 wp_enqueue_script( 'newsletters-admin-edit-script', $resources_url . 'newslettersPostTypeAdmin.js', [ 'jquery', 'wp-i18n'], filemtime(__DIR__ . '/resources/newslettersPostTypeAdmin.js'), true); 271 278 wp_set_script_translations("newsletters-admin-edit-script",'easynewsletter',plugin_dir_path(__FILE__) . 'resources/language'); 272 279 … … 294 301 return $settings; 295 302 } 303 $db = databaseConnector::instance(); 304 $contentWidth = generateEmailHtml::contentWidth($db->getSettingFromDB('newsletterContentWidth')); 305 $wideWidth = filter_var($db->getSettingFromDB('newsletterWideWidth'), FILTER_VALIDATE_INT, 306 ['options' => ['min_range' => 200, 'max_range' => 2000]]) ?: 1100; 307 $wideWidth = max($contentWidth, $wideWidth); 308 // Gutenberg uses these settings for inherited Group layouts and controls. 309 $settings['__experimentalFeatures']['layout']['contentSize'] = $contentWidth . 'px'; 310 $settings['__experimentalFeatures']['layout']['wideSize'] = $wideWidth . 'px'; 296 311 $settings['styles'][] = ['css' => file_get_contents(__DIR__ . '/resources/newsletterBaseStyle.css')]; 312 if (newsletterThemeStyles::enabled()) { 313 // Theme fonts may be enqueued only on the frontend. Include their 314 // absolute @font-face sources in the editor canvas as well. 315 $themeCSS = newsletterStyleInliner::emailCSS(newsletterThemeStyles::css((int)$context->post->ID)); 316 [, $fonts] = \CSSIN::splitFontFace($themeCSS); 317 if ($fonts !== '') { $settings['styles'][] = ['css' => $fonts]; } 318 } 319 // Override theme-generated canvas widths without touching other post 320 // types, nested columns or explicit per-group content-size choices. 321 $settings['styles'][] = ['css' => " 322 .editor-styles-wrapper { 323 --wp--style--global--content-size: {$contentWidth}px !important; 324 --wp--style--global--wide-size: {$wideWidth}px !important; 325 } 326 .editor-styles-wrapper .is-root-container > .wp-block:not([data-align=wide]):not([data-align=full]):not(.alignwide):not(.alignfull), 327 .editor-styles-wrapper .editor-post-title { 328 max-width: {$contentWidth}px; 329 margin-left: auto; 330 margin-right: auto; 331 } 332 .editor-styles-wrapper .is-root-container > .wp-block[data-align=wide], 333 .editor-styles-wrapper .is-root-container > .wp-block.alignwide { 334 max-width: {$wideWidth}px; 335 margin-left: auto; 336 margin-right: auto; 337 } 338 .editor-styles-wrapper .is-root-container > .wp-block[data-align=full], 339 .editor-styles-wrapper .is-root-container > .wp-block.alignfull { 340 max-width: none; 341 } 342 "]; 297 343 $customCSS = databaseConnector::instance()->getSettingFromDB('newsletterCSS'); 298 344 if (is_string($customCSS) && trim($customCSS) !== '') { … … 305 351 function remove_editor_styles_for_post_type(): void { 306 352 global $post_type; 307 if( 'en_newsletters' == $post_type ) {353 if( 'en_newsletters' == $post_type && !newsletterThemeStyles::enabled() ) { 308 354 remove_theme_support('editor-styles'); 309 355 } … … 419 465 420 466 $postID = sanitize_key($_POST["post_id"]); 421 $subject = get_post_meta($postID,"en_subject", true);467 $subject = "[Test] " . get_post_meta($postID,"en_subject", true); 422 468 $userID = get_current_user_id(); 423 469 … … 508 554 $posttype = $_GET["post_type"] ?? null; 509 555 if ('edit.php' === $hook && $posttype == "en_newsletters") { 510 wp_enqueue_script( 'newsletters-overview-script', EASY_NEWSLETTER_URL . 'src/core/resources/newslettersPostTypeOverview.js'); 511 } 512 } 513 514 public function en_copyNewsletter(): void { 515 check_ajax_referer( 'secure_nonce_name', 'security' ); 516 try { 517 $post = get_post($_POST["post_id"]); 518 519 //make a duplicate from the post 520 $new_post_id = wp_insert_post( array( 521 'post_title' => $post->post_title . ' (Copy)', 522 'post_content' => $post->post_content, 523 'post_status' => $post->post_status, 524 'post_type' => $post->post_type, 525 'post_author' => $post->post_author, 526 'post_parent' => $post->post_parent, 527 'post_excerpt' => $post->post_excerpt, 528 'post_date' => $post->post_date, 529 'post_date_gmt' => $post->post_date_gmt, 530 'menu_order' => $post->menu_order, 531 'comment_status' => $post->comment_status, 532 'ping_status' => $post->ping_status, 533 'post_password' => $post->post_password, 534 'to_ping' => $post->to_ping, 535 'pinged' => $post->pinged, 536 'post_content_filtered' => $post->post_content_filtered, 537 'post_category' => $post->post_category, 538 'tags_input' => $post->tags_input, 539 'tax_input' => $post->tax_input, 540 'meta_input' => $post->meta_input, 541 ) ); 542 543 //Copy custom Meta keys to the new post 544 $custom_keys = get_post_custom_keys($_POST["post_id"]); 545 foreach ( $custom_keys as $key ) { 546 $value = get_post_meta( $_POST["post_id"], $key, true ); 547 update_post_meta( $new_post_id, $key, $value ); 548 } 549 550 551 } catch (\Error | Exception $e){ 552 Log::logError($e->getMessage()); 553 } 554 } 556 wp_enqueue_script( 'newsletters-overview-script', EASY_NEWSLETTER_URL . 'src/core/resources/newslettersPostTypeOverview.js', ['jquery'], filemtime(__DIR__ . '/resources/newslettersPostTypeOverview.js')); 557 } 558 } 559 560 /** Registered only on newsletter column hooks, including Screen Options. */ 561 public function removeNewsletterSEOColumns(array $columns): array { 562 foreach (array_keys($columns) as $key) { 563 if ($key === 'tsf-seo-bar-wrap' || str_starts_with($key, 'wpseo-')) { 564 unset($columns[$key]); 565 } 566 } 567 return $columns; 568 } 569 570 /** Keep one predictable copy workflow in the newsletter list only. */ 571 public function newsletterRowActions(array $actions, $post): array { 572 if (($post->post_type ?? '') !== 'en_newsletters') { return $actions; } 573 // WordPress uses these action keys as the surrounding span classes. 574 // Duplicate Page; Copy & Delete Everything (copy-delete-posts); 575 // Yoast Duplicate Post; Duplicate Page and Post; WP Duplicate Page; Post Duplicator. 576 foreach (['duplicate', 'cdp_copy', 'clone', 'edit_as_new_draft', 'rewrite', 'dpp', 'njt_duplicate_page', 'duplicate_post', 'w3tc_flush_post'] as $key) { 577 unset($actions[$key]); 578 } 579 return $actions; 580 } 581 582 public function en_copyNewsletter(): void { 583 check_ajax_referer('secure_nonce_name', 'security'); 584 $postID = absint($_POST['post_id'] ?? 0); 585 $post = get_post($postID); 586 $type = get_post_type_object('en_newsletters'); 587 if (!$post || $post->post_type !== 'en_newsletters' 588 || !current_user_can('edit_post', $postID) 589 || !$type || !current_user_can($type->cap->create_posts)) { 590 wp_send_json_error(['message' => __('Newsletter kann nicht kopiert werden.', 'easynewsletter')], 403); 591 return; 592 } 593 $newID = self::duplicateNewsletter($post); 594 if (is_wp_error($newID)) { 595 wp_send_json_error(['message' => $newID->get_error_message()], 500); 596 return; 597 } 598 wp_send_json_success(['post_id' => $newID]); 599 } 600 601 /** Copy editorial content, never the original publication or delivery state. */ 602 public static function duplicateNewsletter(\WP_Post $post) { 603 $newID = wp_insert_post(wp_slash([ 604 'post_title' => $post->post_title . ' (Copy)', 605 'post_content' => $post->post_content, 606 'post_excerpt' => $post->post_excerpt, 607 'post_status' => 'draft', 608 'post_type' => 'en_newsletters', 609 'post_author' => get_current_user_id(), 610 'post_parent' => $post->post_parent, 611 'post_date' => current_time('mysql'), 612 // A floating draft date lets WordPress use publication time later, 613 // unless the editor explicitly chooses a publication date. 614 'post_date_gmt' => '0000-00-00 00:00:00', 615 'comment_status' => $post->comment_status, 616 'ping_status' => $post->ping_status, 617 'post_password' => $post->post_password, 618 ]), true); 619 if (is_wp_error($newID)) { return $newID; } 620 621 $excluded = ['_edit_lock', '_edit_last', '_wp_old_slug', '_wp_old_date', 622 '_wp_trash_meta_status', '_wp_trash_meta_time', '_wp_desired_post_slug', 623 'en_scheduleLock', 'lockStatus', 'en_alreadySend', 'en_archiveContent']; 624 foreach (get_post_meta($post->ID) as $key => $values) { 625 if (in_array($key, $excluded, true)) { continue; } 626 foreach ($values as $value) { 627 add_post_meta($newID, $key, wp_slash(maybe_unserialize($value))); 628 } 629 } 630 foreach (get_object_taxonomies($post->post_type) as $taxonomy) { 631 $terms = wp_get_object_terms($post->ID, $taxonomy, ['fields' => 'ids']); 632 if (!is_wp_error($terms)) { wp_set_object_terms($newID, $terms, $taxonomy); } 633 } 634 return $newID; 635 } 555 636 } -
easynewsletter/trunk/src/core/outlookPreview.php
r3710879 r3730982 3 3 4 4 require_once __DIR__ . '/resources/vendor/cssin/CSSIN.php'; 5 require_once __DIR__ . '/newsletterCSSLength.php'; 5 6 6 7 /** Preview-only approximation of Classic Outlook for Windows, after CSSIN. */ … … 32 33 $node->parentNode->removeChild($node); 33 34 } 35 $rootFont = 16.0; 34 36 foreach ($xpath->query('//*') as $node) { 35 37 $parser = new \csstidy(); … … 42 44 } 43 45 } 46 if (strtolower($node->tagName) === 'html' && isset($styles['font-size'])) { 47 $rootFont = newsletterCSSLength::pixels($styles['font-size'], 16, 16) ?? 16; 48 } 49 foreach ($styles as $name => &$value) { 50 if (!str_starts_with($name, '--') && !in_array($name, ['font-family', 'content'], true)) { 51 $value = newsletterCSSLength::resolveRem($value, $node->tagName === 'html' && $name === 'font-size' ? 16 : $rootFont); 52 } 53 } 54 unset($value); 44 55 unset($styles['max-width']); 45 56 if (isset($styles['mso-padding-alt'])) { … … 53 64 $plainProperty = preg_replace('/^-(webkit|moz|ms|o)-/', '', strtolower($property)); 54 65 if (preg_match('/^border-.*radius(?:-.*)?$/', $plainProperty) 55 || in_array($plainProperty, ['box-shadow', 'text-shadow', 'gap', 'row-gap', 'column-gap', 'order' ], true)66 || in_array($plainProperty, ['box-shadow', 'text-shadow', 'gap', 'row-gap', 'column-gap', 'order', 'transform', 'transform-origin', 'transform-style', 'rotate', 'translate', 'scale', 'perspective', 'perspective-origin', 'backface-visibility'], true) 56 67 || preg_match('/^(flex|grid|align|justify|place)(-|$)/', $plainProperty)) { 57 68 unset($styles[$property]); -
easynewsletter/trunk/src/core/registration/registration.php
r3710879 r3730982 51 51 */ 52 52 public function init(): void { 53 add_action('init', [registrationBlock::class, 'register']); 53 54 add_shortcode('easyNewsletter', array($this, 'registrationFormFilter')); 54 55 add_shortcode('easyNewsletterUnsubscribeForm', array($this, 'easyNewsletterUnsubscribeForm')); 56 add_action('wp_enqueue_scripts', static function () { 57 wp_enqueue_style('en-registration-form', plugins_url('../resources/registrationForm.css', __FILE__), array(), '1.0.0'); 58 }); 55 59 } 56 60 … … 188 192 public function registrationFormFilter($attributes): string|bool { 189 193 194 return $this->renderForm($attributes); 195 } 196 197 public function renderForm($attributes, bool $preview = false): string { 198 static $instance = 0; 199 $formInstance = ++$instance; 200 $idSuffix = $formInstance === 1 ? '' : '_' . $formInstance; 201 if (!$preview) { 202 wp_enqueue_script('en-signup-form', plugins_url('../resources/signupForm.js', __FILE__), [], (string) filemtime(__DIR__ . '/../resources/signupForm.js'), true); 203 } 190 204 $attributes = shortcode_atts(array( 191 205 'jump' => false, 192 206 'category' => 'default', 193 207 'redirect' => false, 208 'layout' => '', 194 209 'registration_success_page_id' => 0 195 210 ), $attributes); … … 197 212 $availableSubscriberCategories = databaseConnector::instance()->getSettingFromDB("subscriberCategory"); 198 213 $availableSubscriberCategories = unserialize($availableSubscriberCategories); 199 if (!in_array($attributes['category'], $availableSubscriberCategories)){214 if (!in_array($attributes['category'], (array) $availableSubscriberCategories, true)){ 200 215 $attributes['category'] = 'default'; 201 216 } … … 212 227 $token = sanitize_text_field($_GET['token'] ?? ""); 213 228 214 if ( $attributes['jump'] == "true" || $shouldRedirect){229 if (!$preview && (wp_validate_boolean($attributes['jump']) || $shouldRedirect)){ 215 230 wp_enqueue_script( 216 231 'custom-registration-script', // Handle … … 223 238 224 239 ob_start(); 225 if ( empty($_GET) ||240 if ($preview || empty($_GET) || 226 241 !empty($confirmed) && 227 242 !empty($unsubscribed) && -
easynewsletter/trunk/src/core/registration/registrationForm.php
r3710879 r3730982 6 6 use Farn\EasyNewsletter\core\subscriberHandler; 7 7 8 if (! empty(sanitize_text_field( $_REQUEST['submit'] ?? ""))){8 if (!$preview && !empty($_POST['submit']) && (isset($_POST['en_form_instance']) ? (string) $_POST['en_form_instance'] === (string) $formInstance : $formInstance === 1)){ 9 9 10 10 $registrationSuccessPageContent = get_the_content("", false, $registrationSuccessPageID); … … 14 14 : ''; 15 15 16 if ( ( $_POST['exitInput'] != "") || ($_POST['human_check']!= date('Y-m-d')) ){16 if ( (($_POST['exitInput'] ?? '') != "") || (($_POST['human_check'] ?? '') != date('Y-m-d')) ){ 17 17 echo '<div id="en_registrationJumpContainer"' . $registrationSuccessRedirectAttribute . '>'; 18 18 echo $registrationSuccessPageContent; … … 23 23 $metaFields = array(); 24 24 25 foreach ($_ REQUEST as $field => $input){25 foreach ($_POST as $field => $input){ 26 26 if ($field == "en_subscriberCategory"){ 27 $metaFields[$field] = [ sanitize_text_field($input)];28 } else if ($field != 'submit' ){27 $metaFields[$field] = [$attributes['category']]; 28 } else if ($field != 'submit' && $field != 'en_form_instance' && is_scalar($input)){ 29 29 $metaFields[$field] = sanitize_text_field($input); 30 30 } … … 52 52 ob_end_clean(); 53 53 54 $content = str_replace("{{registrationForm}}", $formContent , $content); 54 // A block-level form cannot live inside the paragraph used by older templates. 55 // Remove only the paragraph wrapping our placeholder, preserving surrounding copy. 56 $content = preg_replace('/<p\b[^>]*>\s*\{\{registrationForm\}\}\s*<\/p>/i', '{{registrationForm}}', $content); 57 // Avoid automatic paragraph insertion at template-only whitespace between tags. 58 $formContent = preg_replace('/>\s+</', '><', trim($formContent)); 59 $content = str_replace("{{registrationForm}}", $formContent, $content); 55 60 56 61 //No escape because we want to get the raw content -
easynewsletter/trunk/src/core/registration/registrationFormContent.php
r3495745 r3730982 1 1 <?php 2 3 2 namespace Farn\EasyNewsletter\core\registration; 4 5 3 use Farn\EasyNewsletter\core\databaseConnector; 6 4 5 $signupCheckboxText = wp_kses_post(databaseConnector::instance()->getSettingFromDB('signupCheckboxText')); 6 if (trim($signupCheckboxText) === '') { 7 $signupCheckboxText = esc_html__('I am a human being', 'easynewsletter'); 8 } 9 $inlineLayout = ($attributes['layout'] ?? '') === 'inline'; 10 $submitButton = '<input type="submit" class="button button-primary" name="submit" id="submit' . esc_attr($idSuffix) . '" value="' . esc_attr__('Subscribe', 'easynewsletter') . '">'; 11 $inlineSubmitRendered = false; 12 $signupFormFields = (array) maybe_unserialize(databaseConnector::instance()->getSettingFromDB('signupFormFields')); 7 13 ?> 8 9 14 <div class="en_signup_wrapper"> 10 <form method="post" name="en_signup_form" id="en_signup_form" class="en_signup_form"> 11 <?php 12 // get values from database/settings-page 13 14 15 $category = isset($attributes) ? $attributes[ 'category'] : "default"; 16 17 $signupFormFields = databaseConnector::instance()->getSettingFromDB('signupFormFields'); 18 $signupFormFields = unserialize($signupFormFields); 19 if($signupFormFields !== false){ 20 foreach ($signupFormFields as $signupFormField){ 21 echo match ($signupFormField){ 22 "en_eMailAddress" => "<label for='" . esc_attr($signupFormField) . "'>" . __('E-Mail','easynewsletter'), 23 "en_salutation" => "<label for='" . esc_attr($signupFormField) . "'>" . __('Salutation','easynewsletter'), 24 "en_gender" => "<label for='" . esc_attr($signupFormField) . "'>" . __('Gender','easynewsletter'), 25 "en_firstName" => "<label for='" . esc_attr($signupFormField) . "'>" . __('First Name', 'easynewsletter'), 26 "en_lastName" => "<label for='" . esc_attr($signupFormField) . "'>" . __('Last Name','easynewsletter'), 27 "en_telephoneNumber" => "<label for='" . esc_attr($signupFormField) . "'>" . __('Telephone Number', 'easynewsletter'), 28 "en_eMailAddressValidation" => "<label for='" . esc_attr($signupFormField) . "'>" . __('Validate E-Mail', 'easynewsletter'), 29 "en_securityQuestion" => "", 30 default => "<label for='" . esc_attr($signupFormField) . "'>".$signupFormField 31 }; 32 33 echo match ( $signupFormField ) { 34 'en_eMailAddress', "en_eMailAddressValidation"=> "<input type='email' name='" . esc_attr($signupFormField) . "' id='" . esc_attr($signupFormField) . "' value='' required class='en_inputCheck' oninput='en_validationCheck()'>", 35 'telephoneNumber'=> "<input type=' tel' name='" . esc_attr($signupFormField) . "' id='" . esc_attr($signupFormField) . "' value='' required class='en_inputCheck'>", 36 "en_securityQuestion" => "", 37 default => "<input type='text' name='" . esc_attr($signupFormField) . "' id='" . esc_attr($signupFormField) . "' value='' required>", 38 }; 39 echo "</label>"; 40 } 41 echo "<input style='display: none' name='exitInput' id='exitInput'>"; 42 echo "<input type='hidden' name='en_subscriberCategory' value='". $category ."'>"; 43 } 44 ?> 45 <br> 46 <div id="en_human_check_div"><input type="checkbox" name="human_check" id="human_check" value=""><label for="human_check"><?php _e("I am a human being", 'easynewsletter')?></label></div> 47 <input type="submit" class="button button-primary" name="submit" id="submit" value="<?php esc_html_e ("Subscribe", 'easynewsletter')?>"> 48 49 50 <script> 51 document.getElementById('human_check').addEventListener('click', function() { 52 if (this.checked) { 53 const currentDate = new Date().toISOString().slice(0, 10); 54 this.value = currentDate; 55 } else { 56 this.value = ''; 57 } 58 }); 59 </script> 60 61 62 </form> 15 <form method="post" name="en_signup_form<?php echo esc_attr($idSuffix); ?>" id="en_signup_form<?php echo esc_attr($idSuffix); ?>" class="en_signup_form"> 16 <?php if ($preview) { echo '<fieldset disabled style="border:0;padding:0;margin:0;min-width:0">'; } ?> 17 <?php foreach ($signupFormFields as $field): 18 if (!is_string($field) || $field === 'en_securityQuestion') { continue; } 19 $inlineEmail = $inlineLayout && $field === 'en_eMailAddress'; 20 if ($inlineEmail) { echo '<div class="en_signup_inline">'; } 21 $type = in_array($field, ['en_eMailAddress', 'en_eMailAddressValidation'], true) ? 'email' : ($field === 'en_telephoneNumber' ? 'tel' : 'text'); 22 echo '<label for="' . esc_attr($field . $idSuffix) . '">' . esc_html(registrationBlock::fieldLabel($field)); 23 echo '<input type="' . $type . '" name="' . esc_attr($field) . '" id="' . esc_attr($field . $idSuffix) . '" value="" required class="en_inputCheck"></label>'; 24 if ($inlineEmail) { echo $submitButton . '</div>'; $inlineSubmitRendered = true; } 25 endforeach; ?> 26 <input style="display:none" name="exitInput" id="exitInput<?php echo esc_attr($idSuffix); ?>" tabindex="-1" autocomplete="off"> 27 <input type="hidden" name="en_subscriberCategory" value="<?php echo esc_attr($attributes['category']); ?>"> 28 <input type="hidden" name="en_form_instance" value="<?php echo esc_attr($formInstance); ?>"> 29 <?php if (!$inlineLayout) { echo '<br>'; } ?> 30 <div class="en_human_check_div" id="en_human_check_div<?php echo esc_attr($idSuffix); ?>"><input type="checkbox" name="human_check" id="human_check<?php echo esc_attr($idSuffix); ?>" value="" required><label for="human_check<?php echo esc_attr($idSuffix); ?>"><?php echo $signupCheckboxText; ?></label></div> 31 <?php if (!$inlineSubmitRendered) { echo $submitButton; } ?> 32 <?php if ($preview) { echo '</fieldset>'; } ?> 33 </form> 63 34 </div> 64 65 <?php66 if (in_array("en_eMailAddressValidation", $signupFormFields)) {67 wp_add_inline_script("en_registrationValidation", "68 const submitButton = document.getElementById('submit')69 let emailElement = document.getElementById('en_eMailAddress')70 let emailValidationElement = document.getElementById('en_eMailAddressValidation')71 72 submitButton.setAttribute('disabled', 'disabled')73 74 function en_validationCheck(){75 if (emailElement.value === emailValidationElement.value){76 submitButton.removeAttribute('disabled')77 emailValidationElement.style.color = 'black'78 } else {79 submitButton.setAttribute('disabled', 'disabled')80 emailValidationElement.style.color = 'red'81 }82 }");83 } -
easynewsletter/trunk/src/core/resources/admin.css
r3495745 r3730982 7 7 float: right; 8 8 } 9 /* Settings subsections: plain links beneath the main plugin tabs. */ 10 .en-settings-navigation { 11 display: flex; 12 gap: 12px; 13 margin-bottom: 25px; 14 } 15 .en-settings-navigation a { text-decoration: none; } 16 .en-settings-navigation a[aria-current="page"] { color: #1d2327; font-weight: 600; } 17 .en-settings-navigation a:focus-visible { outline: 2px solid #2271b1; outline-offset: 4px; } 18 #easyNewsletterSettings [data-en-settings-panel][hidden] { display: none; } -
easynewsletter/trunk/src/core/resources/newslettersPostTypeAdmin.js
r3696327 r3730982 3 3 const { ajaxUrl, nonce, sendingInProgress } = easyNewsletterData; 4 4 5 if (typeof __ === "undefined") {5 jQuery(document).ready(function ($) { 6 6 const { __ } = wp.i18n; 7 } 8 9 jQuery(document).ready(function ($) { 10 // add send-buttons and basic popup to html 11 setTimeout(function () { 12 // wrapper 13 $(".editor-header").after("<div class='en_sendButtonsWrapper'></div>"); 14 // sendNewsletter-button or stopSendingNewsletter-button depending on sending state 15 if (sendingInProgress === true || sendingInProgress === "true") { 16 // Newsletter is currently being sent 17 $(".en_sendButtonsWrapper").append('<button class="components-button editor-post-switch-to-draft is-tertiary en_stopSendingNewsletter dashicons-before dashicons-no">' + wp.i18n.__("Stop Sending", "easynewsletter") + "</button>"); 18 } else { 19 $(".en_sendButtonsWrapper").append("<button class='components-button editor-post-switch-to-draft is-tertiary en_wantToSendNewsletter dashicons-before dashicons-email-alt'>" + __("Send Newsletter Now", "easynewsletter") + "</button>"); 20 } 21 // sendTestMail-button 22 $(".en_sendButtonsWrapper").append("<button class='components-button editor-post-switch-to-draft is-tertiary en_TestmailButton dashicons-before dashicons-admin-tools'>" + __("Send Test Mail", "easynewsletter") + "</button>"); 23 // newsletterPopup 24 $("body").append('<div class="en_newsletterAdminPopup hidden"><div class="en_newsletterAdminPopupBackground"></div><div class="en_newsletterAdminPopupContent"></div></div>'); 25 }, 2000); 7 let toolbar = null; 8 let framePending = false; 9 10 function mountToolbar() { 11 const header = document.querySelector('.editor-header, .edit-post-header'); 12 if (!header) return; 13 if (!toolbar) { 14 toolbar = document.createElement('div'); 15 toolbar.className = 'en_sendButtonsWrapper'; 16 const sending = sendingInProgress === true || sendingInProgress === 'true'; 17 const send = document.createElement('button'); 18 send.type = 'button'; 19 send.className = 'components-button editor-post-switch-to-draft is-tertiary dashicons-before ' + 20 (sending ? 'en_stopSendingNewsletter dashicons-no' : 'en_wantToSendNewsletter dashicons-email-alt'); 21 send.textContent = sending ? __('Stop Sending', 'easynewsletter') : __('Send Newsletter Now', 'easynewsletter'); 22 const test = document.createElement('button'); 23 test.type = 'button'; 24 test.className = 'components-button editor-post-switch-to-draft is-tertiary en_TestmailButton dashicons-before dashicons-admin-tools'; 25 test.textContent = __('Send Test Mail', 'easynewsletter'); 26 toolbar.append(send, test); 27 } 28 if (header.nextElementSibling !== toolbar) { 29 header.after(toolbar); 30 // Also fires after Gutenberg replaces the header. Reuse our node so 31 // extension buttons and their current state survive a remount. 32 $(document).trigger('en:toolbar-ready', [toolbar]); 33 } 34 if (!document.querySelector('.en_newsletterAdminPopup')) { 35 $('body').append('<div class="en_newsletterAdminPopup hidden"><div class="en_newsletterAdminPopupBackground"></div><div class="en_newsletterAdminPopupContent"></div></div>'); 36 } 37 } 38 const toolbarObserver = new MutationObserver(function () { 39 if (framePending) return; 40 framePending = true; 41 requestAnimationFrame(function () { 42 framePending = false; 43 mountToolbar(); 44 }); 45 }); 46 toolbarObserver.observe(document.body, { childList: true, subtree: true }); 47 mountToolbar(); 48 window.addEventListener('pagehide', () => toolbarObserver.disconnect(), { once: true }); 26 49 27 50 // click on newsletter popup background - hide popup -
easynewsletter/trunk/src/core/resources/newslettersPostTypeOverview.js
r3495745 r3730982 3 3 let id = $(element).parents(":eq(1)").attr("id"); 4 4 id = id.toString().replace("post-",""); 5 $(element).append("<span class=' copy'> | <a href='#' class='en_copy' data-post-id='"+id+"'>Copy</a></span>");5 $(element).append("<span class='en_row_action_copy'> | <a href='#' class='en_copy' data-post-id='"+id+"'>Duplizieren</a></span>"); 6 6 }) 7 7 -
easynewsletter/trunk/src/core/resources/settingsPage.js
r3495745 r3730982 1 jQuery(document).ready(function ($) { 2 $("#easyNewsletterSettings").on('keydown', function(event) { 3 if (event.keyCode === 13){ 1 jQuery(function ($) { 2 const form = document.getElementById('easyNewsletterSettings'); 3 if (!form) return; 4 const links = Array.from(document.querySelectorAll('[data-en-settings-link]')); 5 const panels = Array.from(document.querySelectorAll('[data-en-settings-panel]')); 6 const selection = form.elements.en_settings_section; 7 const valid = new Set(links.map(link => link.dataset.enSettingsLink)); 8 function showSection(id) { 9 if (!valid.has(id)) id = 'general'; 10 panels.forEach(panel => { panel.hidden = panel.dataset.enSettingsPanel !== id; }); 11 links.forEach(link => { 12 if (link.dataset.enSettingsLink === id) link.setAttribute('aria-current', 'page'); 13 else link.removeAttribute('aria-current'); 14 }); 15 selection.value = id; 16 } 17 function fromHash() { 18 const match = location.hash.match(/^#en-settings-(general|signup|design)$/); 19 return match ? match[1] : selection.value; 20 } 21 links.forEach(link => link.addEventListener('click', event => { 22 event.preventDefault(); 23 const id = link.dataset.enSettingsLink; 24 showSection(id); 25 history.replaceState(null, '', link.getAttribute('href')); 26 })); 27 window.addEventListener('hashchange', () => showSection(fromHash())); 28 // Hidden fields stay enabled: switching sections never discards edits and 29 // saving submits the complete settings form, including unchecked checkboxes. 30 form.addEventListener('invalid', event => { 31 const panel = event.target.closest('[data-en-settings-panel]'); 32 if (panel) showSection(panel.dataset.enSettingsPanel); 33 }, true); 34 showSection(fromHash()); 35 $(form).on('keydown', function (event) { 36 if (event.key === 'Enter' && $(event.target).is('input:not([type=submit]):not([type=button])')) { 4 37 event.preventDefault(); 5 $("#en_submitSaveButton").click();38 document.getElementById('en_submitSaveButton').click(); 6 39 } 7 }) 8 }) 40 }); 41 }); -
easynewsletter/trunk/src/core/settingsPageContent.php
r3710879 r3730982 14 14 } 15 15 16 $settingsValueMap['newsletterUseThemeStyles'] = isset($_POST['newsletterUseThemeStyles']) && $_POST['newsletterUseThemeStyles'] === 'true' ? 'true' : 'false'; 17 $excludedStyles = array_values(array_filter((array)($_POST['newsletterExcludedStyles'] ?? []), 'is_string')); 18 $settingsValueMap['newsletterExcludedStyles'] = wp_json_encode(array_map('sanitize_text_field', wp_unslash($excludedStyles))); 19 $settingsValueMap['newsletterCSSFile'] = esc_url_raw(wp_unslash((string)($_POST['newsletterCSSFile'] ?? '')), ['http', 'https']); 20 // Root-relative paths are convenient for CSS kept with the theme. 21 if (isset($_POST['newsletterCSSFile']) && str_starts_with(trim((string)$_POST['newsletterCSSFile']), '/')) { 22 $settingsValueMap['newsletterCSSFile'] = esc_url_raw(newsletterThemeStyles::absoluteURL(trim(wp_unslash($_POST['newsletterCSSFile'])), home_url('/'))); 23 } 24 update_option('en_style_revision', wp_generate_uuid4(), false); 25 delete_option('en_style_file_warnings'); 26 unset($settingsValueMap['en_settings_section']); 16 27 unset($settingsValueMap['page']); 17 28 unset($settingsValueMap['submit']); … … 39 50 } 40 51 continue; 52 } else if ($setting == "signupCheckboxText"){ 53 $value = wp_kses_post(wp_unslash($value)); 41 54 } else if ($setting == "newsletterContentWidth"){ 42 55 $value = (string)generateEmailHtml::contentWidth($value); 56 } else if ($setting == "newsletterSideSpacing"){ 57 $value = (string)generateEmailHtml::sideSpacing($value); 58 } else if ($setting == "newsletterWideWidth"){ 59 $value = (string)(filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => 200, 'max_range' => 2000]]) ?: 1100); 43 60 } else if ($setting == "newsletterCSS"){ 44 61 // Prevent escaping quotes … … 157 174 } 158 175 ?> 159 <h1 class="wp-heading-inline"><?php esc_html_e("Easy Newsletter Settings", "easynewsletter");?></h1> 160 <hr class="wp-header-end"> 176 <?php 177 $settingsSections = [ 178 'general' => ['label' => __('Allgemein', 'easynewsletter'), 'title' => __('Allgemeine Einstellungen', 'easynewsletter')], 179 'signup' => ['label' => __('Anmeldung', 'easynewsletter'), 'title' => __('Anmeldung', 'easynewsletter')], 180 'design' => ['label' => __('Design', 'easynewsletter'), 'title' => __('Newsletter-Design', 'easynewsletter')], 181 ]; 182 $settingSection = static function ($setting) { 183 if (in_array($setting, ['newsletterCSS', 'newsletterContentWidth', 'newsletterWideWidth', 'newsletterSideSpacing', 'newsletterUseThemeStyles', 'newsletterCSSFile', 'newsletterExcludedStyles'], true)) { return 'design'; } 184 if (in_array($setting, ['signupFormFields', 'signupCheckboxText', 'subscriberMode', 'subscriberCategory', 'subscriberRole', 'addedUserRoleKey', 'standardActivationPost', 'standardUnsubscribePost', 'standardWelcomePost', 'registrationPageID', 'registrationSuccessPageID', 'confirmationSuccessPageID', 'confirmationDeniedPageID', 'unsubscribedPageID', 'registrationFormPageID', 'unsubscribeFormPageID', 'unsubscribedConfirmedPageID'], true)) { return 'signup'; } 185 return 'general'; 186 }; 187 $selectedSection = sanitize_key($_POST['en_settings_section'] ?? 'general'); 188 if (!isset($settingsSections[$selectedSection])) { $selectedSection = 'general'; } 189 ?> 190 <nav class="en-settings-navigation" aria-label="<?php esc_attr_e('Einstellungsbereiche', 'easynewsletter'); ?>"> 191 <?php foreach ($settingsSections as $sectionID => $section): ?> 192 <a href="#en-settings-<?php echo esc_attr($sectionID); ?>" data-en-settings-link="<?php echo esc_attr($sectionID); ?>"><?php echo esc_html($section['label']); ?></a> 193 <?php endforeach; ?> 194 </nav> 161 195 <form method="post" name="easyNewsletterSettings" id="easyNewsletterSettings"> 196 <input type="hidden" name="en_settings_section" value="<?php echo esc_attr($selectedSection); ?>"> 197 <?php 198 $allSettings = (array)databaseConnector::instance()->getAllSettings(); 199 $designSettings = []; 200 foreach (['newsletterCSS', 'newsletterContentWidth', 'newsletterWideWidth', 'newsletterSideSpacing'] as $designKey) { 201 if (array_key_exists($designKey, $allSettings)) { 202 $designSettings[$designKey] = $allSettings[$designKey]; 203 unset($allSettings[$designKey]); 204 } 205 } 206 $allSettings = array_merge($allSettings, $designSettings); 207 ?> 208 <?php foreach ($settingsSections as $sectionID => $section): ?> 209 <section id="en-settings-<?php echo esc_attr($sectionID); ?>" data-en-settings-panel="<?php echo esc_attr($sectionID); ?>" aria-labelledby="en-settings-heading-<?php echo esc_attr($sectionID); ?>"> 210 <h1 id="en-settings-heading-<?php echo esc_attr($sectionID); ?>"><?php echo esc_html($section['title']); ?></h1> 162 211 <table class="form-table"> 163 212 <tbody> 164 213 <?php 165 $allSettings = databaseConnector::instance()->getAllSettings();166 214 foreach ($allSettings as $setting => $value){ 215 if ($settingSection($setting) !== $sectionID) { continue; } 216 217 if (in_array($setting, ['newsletterUseThemeStyles', 'newsletterCSSFile', 'newsletterExcludedStyles'], true)) { continue; } 218 if ($setting === 'newsletterCSS') { 219 require_once __DIR__ . '/newsletterStyleSettings.php'; 220 newsletterStyleSettings::render(); 221 } 167 222 168 223 //TODO Maybe put them somewhere to be seen in a hidden settings menu or something. … … 195 250 'intervalInSeconds' => "<th scope='row'>".__("Interval in seconds ", 'easynewsletter')."</th>", 196 251 'signupFormFields' => "<th scope='row'>".__("Signup form fields ", 'easynewsletter')."</th>", 252 'signupCheckboxText' => "<th scope='row'>".esc_html__("Text der Pflicht-Checkbox im Anmeldeformular", 'easynewsletter')."</th>", 197 253 'activeNewsletter' => "<th scope='row'>".__("Currently active newsletter ", 'easynewsletter')."</th>", 198 254 'activeNewsletterID' => "<th scope='row'>".__("Currently active newsletter ID ", 'easynewsletter')."</th>", … … 204 260 'subscriberCategory' => "<th scope='row'>".__("Available subscriber category ", 'easynewsletter')."</th>", 205 261 'subscriberRole' => "<th scope='row'>".__("Available subscriber roles ", 'easynewsletter')."</th>", 206 'newsletterContentWidth' => "<th scope='row'>".__("Newsletter-Inhaltsbreite (px)", 'easynewsletter')."</th>", 207 'newsletterCSS' => "<th scope='row'>".__("Custom Newsletter CSS ", 'easynewsletter')."</th>", 262 'newsletterContentWidth' => "<th scope='row'>".__("Standard-Breite (px)", 'easynewsletter')."</th>", 263 'newsletterSideSpacing' => "<th scope='row'>".__("Seitlicher Inhaltsabstand (px)", 'easynewsletter')."</th>", 264 'newsletterWideWidth' => "<th scope='row'>".__("Weite Breite (px)", 'easynewsletter')."</th>", 265 'newsletterCSS' => "<th scope='row'>".__("Benutzerdefiniertes CSS", 'easynewsletter')."</th>", 208 266 'addedUserRoleKey' => "<th scope='row'>".__("New user role key ", 'easynewsletter')."</th>", 209 267 'registrationPageID' => "<th scope='row'>".__("Registration page id ", 'easynewsletter')."</th>", … … 219 277 'senderName' => "<input type='text' name='" . esc_attr($setting) . "' id='" . esc_attr($setting) . "' value='" .stripslashes(esc_attr($value)) . "'>", 220 278 'maxEmailPerInterval', 'intervalInSeconds' => "<input type='number' name='" . esc_attr($setting) . "' id='" . esc_attr($setting) . "' value='" . esc_attr($value) . "'>", 221 'newsletterContentWidth' => "<input type='number' min='200' max='2000' step='1' name='newsletterContentWidth' id='newsletterContentWidth' value='" . esc_attr($value) . "'><p class='description'>" . esc_html__("Maximale Desktop-Inhaltsbreite und Grundlage für die Bildgrößenberechnung. Auf schmalen Bildschirmen bleibt der Newsletter flexibel. Standard: 800 px.", 'easynewsletter') . "</p>", 279 'newsletterContentWidth' => "<input type='number' min='200' max='2000' step='1' name='newsletterContentWidth' id='newsletterContentWidth' value='" . esc_attr($value) . "'>", 280 'newsletterSideSpacing' => "<input type='number' min='0' max='200' step='1' name='newsletterSideSpacing' id='newsletterSideSpacing' value='" . esc_attr($value) . "'>", 281 'newsletterWideWidth' => "<input type='number' min='200' max='2000' step='1' name='newsletterWideWidth' id='newsletterWideWidth' value='" . esc_attr($value) . "'>", 222 282 'signupFormFields' => createCheckboxes($value), 283 'signupCheckboxText' => "<textarea name='signupCheckboxText' id='signupCheckboxText' rows='4' cols='50' aria-describedby='signupCheckboxTextDescription'>" . esc_textarea($value) . "</textarea><p class='description' id='signupCheckboxTextDescription'>" . esc_html__("Eigener Text, z. B. zur Bestätigung der Datenschutzhinweise. Sicheres HTML wie Links und Hervorhebungen ist erlaubt. Leer lassen für den Standardtext „Ich bin ein Mensch“. Die bestehende Spam-Prüfung bleibt aktiv.", 'easynewsletter') . "</p>", 223 284 'subscriberMode' => generateModeSelection(), 224 285 'sendingInProgress', 'activeNewsletter', 'activeNewsletterID' => "<input type='text' name='" . $setting . "' id='" . $setting . "' value='" . $value . "' disabled>", 225 286 'subscriberCategory' => generateTargetGroupsCategory(), 226 287 'subscriberRole' => generateTargetGroupsRoles(), 227 'newsletterCSS' => "<textarea name='" . esc_attr($setting) . "' id='" . esc_attr($setting) . "'rows='4' cols='50' form='easyNewsletterSettings'>". $value."</textarea>",288 'newsletterCSS' => "<textarea name='" . esc_attr($setting) . "' id='" . esc_attr($setting) . "'rows='4' cols='50' form='easyNewsletterSettings'>".esc_textarea($value)."</textarea>", 228 289 default => "<input type='text' name='" . esc_attr($setting) . "' id='" . esc_attr($setting) . "' value='" . esc_attr($value) . "'>", 229 290 }; … … 232 293 </tbody> 233 294 </table> 295 </section> 296 <?php endforeach; ?> 234 297 <?php echo "<input type='hidden' name='wp_en_nonce' value='".wp_create_nonce()."'>"?> 235 298 <input type="submit" id="en_submitSaveButton" class="button button-primary" name="submit" value="<?php _e("Save", 'easynewsletter')?>"> 236 299 300 <div data-en-settings-panel="general"> 237 301 <h3><?php _e("Press this Button to stop the Newsletter sending Process", 'easynewsletter')?></h3> 238 302 <input type="submit" class="button button-primary" style="background-color: red; border-color: red" name="stopNewsletterSending" value="<?php _e("Stop Newsletter Sending Process", 'easynewsletter')?>"> 303 </div> 239 304 </form> 240 305 <br> 241 306 </div> 242 -
easynewsletter/trunk/vendor-prefixed/autoload.php
r3710879 r3730982 20 20 require_once __DIR__ . '/composer/autoload_real.php'; 21 21 22 return ComposerAutoloaderInit 9130e49187f279f2bf1d421ae64cfb1b::getLoader();22 return ComposerAutoloaderInit2a106f58d8aeb22de715011744b3955b::getLoader(); -
easynewsletter/trunk/vendor-prefixed/composer/autoload_real.php
r3710879 r3730982 3 3 // autoload_real.php @generated by Composer 4 4 5 class ComposerAutoloaderInit 9130e49187f279f2bf1d421ae64cfb1b5 class ComposerAutoloaderInit2a106f58d8aeb22de715011744b3955b 6 6 { 7 7 private static $loader; … … 23 23 } 24 24 25 spl_autoload_register(array('ComposerAutoloaderInit 9130e49187f279f2bf1d421ae64cfb1b', 'loadClassLoader'), true, true);25 spl_autoload_register(array('ComposerAutoloaderInit2a106f58d8aeb22de715011744b3955b', 'loadClassLoader'), true, true); 26 26 self::$loader = $loader = new \Farn\EasyNewsletter\Composer\Autoload\ClassLoader(\dirname(__DIR__)); 27 spl_autoload_unregister(array('ComposerAutoloaderInit 9130e49187f279f2bf1d421ae64cfb1b', 'loadClassLoader'));27 spl_autoload_unregister(array('ComposerAutoloaderInit2a106f58d8aeb22de715011744b3955b', 'loadClassLoader')); 28 28 29 29 require __DIR__ . '/autoload_static.php'; 30 call_user_func(\Farn\EasyNewsletter\Composer\Autoload\ComposerStaticInit 9130e49187f279f2bf1d421ae64cfb1b::getInitializer($loader));30 call_user_func(\Farn\EasyNewsletter\Composer\Autoload\ComposerStaticInit2a106f58d8aeb22de715011744b3955b::getInitializer($loader)); 31 31 32 32 $loader->setClassMapAuthoritative(true); -
easynewsletter/trunk/vendor-prefixed/composer/autoload_static.php
r3710879 r3730982 5 5 namespace Farn\EasyNewsletter\Composer\Autoload; 6 6 7 class ComposerStaticInit 9130e49187f279f2bf1d421ae64cfb1b7 class ComposerStaticInit2a106f58d8aeb22de715011744b3955b 8 8 { 9 9 public static $prefixLengthsPsr4 = array ( … … 33 33 { 34 34 return \Closure::bind(function () use ($loader) { 35 $loader->prefixLengthsPsr4 = ComposerStaticInit 9130e49187f279f2bf1d421ae64cfb1b::$prefixLengthsPsr4;36 $loader->prefixDirsPsr4 = ComposerStaticInit 9130e49187f279f2bf1d421ae64cfb1b::$prefixDirsPsr4;37 $loader->classMap = ComposerStaticInit 9130e49187f279f2bf1d421ae64cfb1b::$classMap;35 $loader->prefixLengthsPsr4 = ComposerStaticInit2a106f58d8aeb22de715011744b3955b::$prefixLengthsPsr4; 36 $loader->prefixDirsPsr4 = ComposerStaticInit2a106f58d8aeb22de715011744b3955b::$prefixDirsPsr4; 37 $loader->classMap = ComposerStaticInit2a106f58d8aeb22de715011744b3955b::$classMap; 38 38 39 39 }, null, ClassLoader::class);
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)