From de46b4527d0679351e0ebdebb03ab25389cd026e Mon Sep 17 00:00:00 2001 From: Jake Oehler Morrison Date: Wed, 11 Apr 2018 17:17:27 +0100 Subject: [PATCH 1/3] Add styles to email content from a dynamic stylesheet Follow's WC's lead on this. --- ...ass-wp-job-manager-email-notifications.php | 50 + lib/emogrifier/class-emogrifier.php | 1557 +++++++++++++++++ templates/emails/email-styles.php | 38 + 3 files changed, 1645 insertions(+) create mode 100644 lib/emogrifier/class-emogrifier.php create mode 100644 templates/emails/email-styles.php diff --git a/includes/class-wp-job-manager-email-notifications.php b/includes/class-wp-job-manager-email-notifications.php index 0932dd0a5..738db322e 100644 --- a/includes/class-wp-job-manager-email-notifications.php +++ b/includes/class-wp-job-manager-email-notifications.php @@ -98,6 +98,9 @@ public static function _lazy_init() { add_action( 'shutdown', array( __CLASS__, '_send_deferred_notifications' ) ); include_once JOB_MANAGER_PLUGIN_DIR . '/includes/emails/class-wp-job-manager-email-admin-new-job.php'; + if ( ! class_exists( 'Emogrifier' ) && class_exists( 'DOMDocument' ) ) { + include_once JOB_MANAGER_PLUGIN_DIR . '/lib/emogrifier/class-emogrifier.php'; + } } /** @@ -304,9 +307,56 @@ private static function get_email_content( $email_notification_key, $args ) { do_action( 'job_manager_email_footer', $email_notification_key, $args, $plain_text ); $content = ob_get_clean(); + if ( ! $plain_text ) { + $content = self::inject_styles( $content ); + } + + /** + * Filter the content of the email. + * + * @since 1.31.0 + * + * @param string $content Email content. + * @param string $email_notification_key Unique email notification key. + * @param array $args Arguments passed for generating email. + * @param bool $plain_text True if sending plain text email. + */ + return apply_filters( 'job_manager_email_content', $content, $email_notification_key, $args, $plain_text ); + } + + /** + * Inject inline styles into email content. + * + * @param string $content + * @return string + */ + private static function inject_styles( $content ) { + if ( class_exists( 'Emogrifier' ) ) { + try { + $emogrifier = new Emogrifier( $content, self::get_styles() ); + $content = $emogrifier->emogrify(); + } catch ( Exception $e ) { + trigger_error( 'Unable to inject styles into email notification: ' . $e->getMessage() ); + } + } return $content; } + /** + * Gets the CSS styles to be used in email notifications. + * + * @return bool|string + */ + private static function get_styles() { + $email_styles_template = locate_job_manager_template( self::get_template_file_name( 'email-styles' ) ); + if ( ! file_exists( $email_styles_template ) ) { + return false; + } + ob_start(); + include $email_styles_template; + return ob_get_clean(); + } + /** * Checks if a particular notification is enabled or not. * diff --git a/lib/emogrifier/class-emogrifier.php b/lib/emogrifier/class-emogrifier.php new file mode 100644 index 000000000..6eaea21c1 --- /dev/null +++ b/lib/emogrifier/class-emogrifier.php @@ -0,0 +1,1557 @@ + + * @author Roman Ožana + * @author Sander Kruger + * + * @see https://raw.githubusercontent.com/MyIntervals/emogrifier/V1.2.0/Classes/Emogrifier.php + */ +// @codingStandardsIgnoreFile +class Emogrifier +{ + /** + * @var int + */ + const CACHE_KEY_CSS = 0; + + /** + * @var int + */ + const CACHE_KEY_SELECTOR = 1; + + /** + * @var int + */ + const CACHE_KEY_XPATH = 2; + + /** + * @var int + */ + const CACHE_KEY_CSS_DECLARATIONS_BLOCK = 3; + + /** + * @var int + */ + const CACHE_KEY_COMBINED_STYLES = 4; + + /** + * for calculating nth-of-type and nth-child selectors + * + * @var int + */ + const INDEX = 0; + + /** + * for calculating nth-of-type and nth-child selectors + * + * @var int + */ + const MULTIPLIER = 1; + + /** + * @var string + */ + const ID_ATTRIBUTE_MATCHER = '/(\\w+)?\\#([\\w\\-]+)/'; + + /** + * @var string + */ + const CLASS_ATTRIBUTE_MATCHER = '/(\\w+|[\\*\\]])?((\\.[\\w\\-]+)+)/'; + + /** + * @var string + */ + const CONTENT_TYPE_META_TAG = ''; + + /** + * @var string + */ + const DEFAULT_DOCUMENT_TYPE = ''; + + /** + * @var string + */ + private $html = ''; + + /** + * @var string + */ + private $css = ''; + + /** + * @var bool[] + */ + private $excludedSelectors = array(); + + /** + * @var string[] + */ + private $unprocessableHtmlTags = array( 'wbr' ); + + /** + * @var bool[] + */ + private $allowedMediaTypes = array( 'all' => true, 'screen' => true, 'print' => true ); + + /** + * @var mixed[] + */ + private $caches = array( + self::CACHE_KEY_CSS => array(), + self::CACHE_KEY_SELECTOR => array(), + self::CACHE_KEY_XPATH => array(), + self::CACHE_KEY_CSS_DECLARATIONS_BLOCK => array(), + self::CACHE_KEY_COMBINED_STYLES => array(), + ); + + /** + * the visited nodes with the XPath paths as array keys + * + * @var \DOMElement[] + */ + private $visitedNodes = array(); + + /** + * the styles to apply to the nodes with the XPath paths as array keys for the outer array + * and the attribute names/values as key/value pairs for the inner array + * + * @var string[][] + */ + private $styleAttributesForNodes = array(); + + /** + * Determines whether the "style" attributes of tags in the the HTML passed to this class should be preserved. + * If set to false, the value of the style attributes will be discarded. + * + * @var bool + */ + private $isInlineStyleAttributesParsingEnabled = true; + + /** + * Determines whether the