diff --git a/.husky/pre-commit b/.husky/pre-commit index 8eb3c88954..9c7fa64906 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -5,4 +5,4 @@ npx lint-staged -c mailpoet/package.json --cwd mailpoet npx lint-staged -c package.json npx lint-staged -c packages/js/email-editor/package.json --cwd packages/js/email-editor -cd packages/php/email-editor && ../../../mailpoet/tools/vendor/composer.phar code-style +npx lint-staged -c packages/php/email-editor/.lintstagedrc.json --cwd packages/php/email-editor diff --git a/packages/php/email-editor/.lintstagedrc.json b/packages/php/email-editor/.lintstagedrc.json new file mode 100644 index 0000000000..6ee76c45f5 --- /dev/null +++ b/packages/php/email-editor/.lintstagedrc.json @@ -0,0 +1,4 @@ +{ + "*": "../../../mailpoet/tools/vendor/composer.phar code-style", + "*.{php}": "../../../mailpoet/tools/vendor/composer.phar phpstan" +} diff --git a/packages/php/email-editor/composer.json b/packages/php/email-editor/composer.json index 5b7a506ed8..c59392da81 100644 --- a/packages/php/email-editor/composer.json +++ b/packages/php/email-editor/composer.json @@ -5,7 +5,10 @@ "autoload": { "classmap": [ "src/" - ] + ], + "files": [ + "src/exceptions.php" + ] }, "autoload-dev": { "classmap": [ diff --git a/packages/php/email-editor/src/Engine/Patterns/class-patterns.php b/packages/php/email-editor/src/Engine/Patterns/class-patterns.php index e03f95526a..2134ef8b43 100644 --- a/packages/php/email-editor/src/Engine/Patterns/class-patterns.php +++ b/packages/php/email-editor/src/Engine/Patterns/class-patterns.php @@ -1,6 +1,6 @@ ' . wp_strip_all_tags( (string) apply_filters( 'mailpoet_email_content_renderer_styles', $styles, $post ) ) . ''; - return CssInliner::fromHtml( $styles . $html )->inlineCss()->render(); + return CssInliner::fromHtml( $styles . $html )->inlineCss()->render(); // @phpstan-ignore-line TODO: Install CssInliner } } diff --git a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-process-manager.php b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-process-manager.php index 3ec19db846..ab4fa83715 100644 --- a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-process-manager.php +++ b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-process-manager.php @@ -1,6 +1,6 @@ templates->get_block_template( $template_id ); - $theme = $this->templates->get_block_template_theme( $template_id, $template->wp_id ); + /** @var \WP_Block_Template $template */ // phpcs:ignore + $template = $this->templates->get_block_template( $template_id ); + $theme = $this->templates->get_block_template_theme( $template_id, $template->wp_id ); // Set the theme for the template. This is merged with base theme.json and core json before rendering. self::$theme = new WP_Theme_JSON( $theme, 'default' ); @@ -140,7 +143,7 @@ class Renderer { * @return string */ private function inline_css_styles( $template ) { - return CssInliner::fromHtml( $template )->inlineCss()->render(); + return CssInliner::fromHtml( $template )->inlineCss()->render(); // @phpstan-ignore-line TODO: Install CssInliner } /** @@ -151,7 +154,7 @@ class Renderer { */ private function render_text_version( $template ) { $template = ( mb_detect_encoding( $template, 'UTF-8', true ) ) ? $template : mb_convert_encoding( $template, 'UTF-8', mb_list_encodings() ); - $result = Html2Text::convert( $template ); + $result = Html2Text::convert( $template ); // @phpstan-ignore-line TODO: Install Html2Text if ( false === $result ) { return ''; } diff --git a/packages/php/email-editor/src/Engine/Renderer/template-canvas.php b/packages/php/email-editor/src/Engine/Renderer/template-canvas.php index 1478cbfd80..e659e97be1 100644 --- a/packages/php/email-editor/src/Engine/Renderer/template-canvas.php +++ b/packages/php/email-editor/src/Engine/Renderer/template-canvas.php @@ -1,6 +1,6 @@ > - <?php echo esc_html( $subject ); ?> + <?php echo esc_html( $subject ); // @phpstan-ignore-line ?> - + - -
+ +
diff --git a/packages/php/email-editor/src/Engine/Templates/class-templates.php b/packages/php/email-editor/src/Engine/Templates/class-templates.php index 62957af63b..195f33116f 100644 --- a/packages/php/email-editor/src/Engine/Templates/class-templates.php +++ b/packages/php/email-editor/src/Engine/Templates/class-templates.php @@ -1,6 +1,6 @@ post_content ) && ! empty( $changes->ID ) ) { @@ -370,6 +371,7 @@ class Templates { return array_map( function ( $custom_template ) { + /** @var \WP_Post $custom_template */ // phpcs:ignore return $this->utils->build_block_template_from_post( $custom_template ); }, $custom_templates diff --git a/packages/php/email-editor/src/Engine/Templates/class-utils.php b/packages/php/email-editor/src/Engine/Templates/class-utils.php index 781cf2934f..6a2401ea51 100644 --- a/packages/php/email-editor/src/Engine/Templates/class-utils.php +++ b/packages/php/email-editor/src/Engine/Templates/class-utils.php @@ -1,6 +1,6 @@ + * } $template_object Template object. * @return WP_Block_Template */ public function build_block_template_from_file( $template_object ): WP_Block_Template { + // phpcs:enable $template = new WP_Block_Template(); $template->id = $template_object->id; $template->theme = $template_object->theme; diff --git a/packages/php/email-editor/src/Engine/class-send-preview-email.php b/packages/php/email-editor/src/Engine/class-send-preview-email.php index e0436efdf6..934953f2ce 100644 --- a/packages/php/email-editor/src/Engine/class-send-preview-email.php +++ b/packages/php/email-editor/src/Engine/class-send-preview-email.php @@ -1,6 +1,6 @@ fetch_post( $post_id ); - $subject = $post->post_title ?? __( 'Email Preview', 'mailpoet' ); + $subject = $post->post_title; $language = get_bloginfo( 'language' ); $rendered_data = $this->renderer->render( diff --git a/packages/php/email-editor/src/Engine/class-theme-controller.php b/packages/php/email-editor/src/Engine/class-theme-controller.php index 0d911b194c..692583dff2 100644 --- a/packages/php/email-editor/src/Engine/class-theme-controller.php +++ b/packages/php/email-editor/src/Engine/class-theme-controller.php @@ -1,6 +1,6 @@ recursive_extract_preset_variables( $style_value ); } elseif ( strpos( $style_value, 'var:preset|' ) === 0 ) { + /** @var string $style_value */ // phpcs:ignore $styles[ $key ] = 'var(--wp--' . str_replace( '|', '--', str_replace( 'var:', '', $style_value ) ) . ')'; } else { $styles[ $key ] = $style_value; @@ -138,9 +138,8 @@ class Theme_Controller { $presets[ $pattern ] = $value; } - $theme_styles = $this->recursive_replace_presets( $theme_styles, $presets ); - - return $theme_styles; + /* @phpstan-ignore-next-line Return type defined above. */ + return $this->recursive_replace_presets( $theme_styles, $presets ); } /** @@ -161,7 +160,7 @@ class Theme_Controller { /** * Get layout settings from the theme. * - * @return array + * @return array{contentSize: string, wideSize: string, allowEditing: bool, allowCustomContentAndWideSize: bool} */ public function get_layout_settings(): array { return $this->get_theme()->get_settings()['layout']; @@ -170,8 +169,8 @@ class Theme_Controller { /** * Get stylesheet from context. * - * @param array $context Context. - * @param array $options Options. + * @param string $context Context. + * @param array $options Options. * @return string */ public function get_stylesheet_from_context( $context, $options = array() ): string { diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-button.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-button.php index 420cfd720c..d3a190a5ee 100644 --- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-button.php +++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-button.php @@ -1,6 +1,6 @@ get_styles_from_block( diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-image.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-image.php index 54ce278bb3..d64b395274 100644 --- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-image.php +++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-image.php @@ -1,6 +1,6 @@ next_tag( array( 'tag_name' => 'img' ) ) ) { // Getting height from styles and if it's set, we set the height attribute. + /** @var string $styles */ // phpcs:ignore $styles = $html->get_attribute( 'style' ) ?? ''; $styles = $settings_controller->parse_styles_to_array( $styles ); $height = $styles['height'] ?? null; if ( $height && 'auto' !== $height && is_numeric( $settings_controller->parse_number_from_string_with_pixels( $height ) ) ) { $height = $settings_controller->parse_number_from_string_with_pixels( $height ); + /* @phpstan-ignore-next-line Wrong annotation for parameter in WP. */ $html->set_attribute( 'height', esc_attr( $height ) ); } if ( isset( $parsed_block['attrs']['width'] ) ) { $width = $settings_controller->parse_number_from_string_with_pixels( $parsed_block['attrs']['width'] ); + /* @phpstan-ignore-next-line Wrong annotation for parameter in WP. */ $html->set_attribute( 'width', esc_attr( $width ) ); } $block_content = $html->get_updated_html(); @@ -277,6 +280,7 @@ class Image extends Abstract_Block_Renderer { private function add_style_to_element( $block_content, array $tag, string $style ): string { $html = new \WP_HTML_Tag_Processor( $block_content ); if ( $html->next_tag( $tag ) ) { + /** @var string $element_style */ // phpcs:ignore $element_style = $html->get_attribute( 'style' ) ?? ''; $element_style = ! empty( $element_style ) ? ( rtrim( $element_style, ';' ) . ';' ) : ''; // Adding semicolon if it's missing. $element_style .= $style; @@ -297,9 +301,10 @@ class Image extends Abstract_Block_Renderer { private function remove_style_attribute_from_element( $block_content, array $tag, string $style_name ): string { $html = new \WP_HTML_Tag_Processor( $block_content ); if ( $html->next_tag( $tag ) ) { + /** @var string $element_style */ // phpcs:ignore $element_style = $html->get_attribute( 'style' ) ?? ''; $element_style = preg_replace( '/' . $style_name . ':(.?[0-9]+px)+;?/', '', $element_style ); - $html->set_attribute( 'style', esc_attr( $element_style ) ); + $html->set_attribute( 'style', esc_attr( strval( $element_style ) ) ); $block_content = $html->get_updated_html(); } @@ -310,7 +315,7 @@ class Image extends Abstract_Block_Renderer { * Parse block content to get image URL, image HTML and caption HTML. * * @param string $block_content Block content. - * @return array{imageUrl: string, image: string, caption: string}|null + * @return array{imageUrl: string, image: string, caption: string, class: string}|null */ private function parse_block_content( string $block_content ): ?array { // If block's image is not set, we don't need to parse the content. diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-list-block.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-list-block.php index 5486ce32dd..5d0cff7cf3 100644 --- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-list-block.php +++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-list-block.php @@ -1,6 +1,6 @@ next_tag( array( 'tag_name' => $tag_name ) ) ) { + /** @var string $styles */ // phpcs:ignore $styles = $html->get_attribute( 'style' ) ?? ''; $styles = $settings_controller->parse_styles_to_array( $styles ); diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-text.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-text.php index 00e9dd1ca2..3fb8590759 100644 --- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-text.php +++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-text.php @@ -1,6 +1,6 @@ next_tag() ) { + /** @var string $block_classes */ // phpcs:ignore $block_classes = $html->get_attribute( 'class' ) ?? ''; $classes .= ' ' . $block_classes; // remove has-background to prevent double padding applied for wrapper and inner element. $block_classes = str_replace( 'has-background', '', $block_classes ); // remove border related classes because we handle border on wrapping table cell. $block_classes = preg_replace( '/[a-z-]+-border-[a-z-]+/', '', $block_classes ); + /** @var string $block_classes */ // phpcs:ignore $html->set_attribute( 'class', trim( $block_classes ) ); $block_content = $html->get_updated_html(); } @@ -62,7 +64,7 @@ class Text extends Abstract_Block_Renderer { ); $styles['text-align'] = 'left'; - if ( isset( $parsed_block['attrs']['textAlign'] ) ) { + if ( ! empty( $parsed_block['attrs']['textAlign'] ) ) { // in this case, textAlign needs to be one of 'left', 'center', 'right'. $styles['text-align'] = $parsed_block['attrs']['textAlign']; } elseif ( in_array( $parsed_block['attrs']['align'] ?? null, array( 'left', 'center', 'right' ), true ) ) { $styles['text-align'] = $parsed_block['attrs']['align']; @@ -87,7 +89,7 @@ class Text extends Abstract_Block_Renderer { esc_attr( $table_styles ), esc_attr( $classes ), esc_attr( $compiled_styles ), - esc_attr( $styles['text-align'] ?? 'left' ), + esc_attr( $styles['text-align'] ), $block_content ); } @@ -104,17 +106,19 @@ class Text extends Abstract_Block_Renderer { $html = new \WP_HTML_Tag_Processor( $block_content ); if ( $html->next_tag() ) { - $element_style = $html->get_attribute( 'style' ) ?? ''; + $element_style_value = $html->get_attribute( 'style' ); + $element_style = isset( $element_style_value ) ? strval( $element_style_value ) : ''; // Padding may contain value like 10px or variable like var(--spacing-10). $element_style = preg_replace( '/padding[^:]*:.?[0-9a-z-()]+;?/', '', $element_style ); // Remove border styles. We apply border styles on the wrapping table cell. - $element_style = preg_replace( '/border[^:]*:.?[0-9a-z-()#]+;?/', '', $element_style ); + $element_style = preg_replace( '/border[^:]*:.?[0-9a-z-()#]+;?/', '', strval( $element_style ) ); // We define the font-size on the wrapper element, but we need to keep font-size definition here // to prevent CSS Inliner from adding a default value and overriding the value set by user, which is on the wrapper element. // The value provided by WP uses clamp() function which is not supported in many email clients. - $element_style = preg_replace( '/font-size:[^;]+;?/', 'font-size: inherit;', $element_style ); + $element_style = preg_replace( '/font-size:[^;]+;?/', 'font-size: inherit;', strval( $element_style ) ); + /** @var string $element_style */ // phpcs:ignore $html->set_attribute( 'style', esc_attr( $element_style ) ); $block_content = $html->get_updated_html(); } diff --git a/packages/php/email-editor/src/Validator/class-schema.php b/packages/php/email-editor/src/Validator/class-schema.php index c41d2b518a..9b3d34a8b1 100644 --- a/packages/php/email-editor/src/Validator/class-schema.php +++ b/packages/php/email-editor/src/Validator/class-schema.php @@ -1,6 +1,6 @@ schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION ); $error = json_last_error(); if ( $error || false === $json ) { - throw new \Exception( \esc_html( json_last_error_msg() ), \esc_html( (string) $error ) ); + throw new \Exception( \esc_html( json_last_error_msg() ), 0 ); } return $json; } @@ -145,6 +145,7 @@ abstract class Schema { * Unsets the schema property. * * @param string $name Property name. + * @return static */ protected function unset_schema_property( string $name ) { $clone = clone $this; diff --git a/packages/php/email-editor/src/Validator/class-validation-exception.php b/packages/php/email-editor/src/Validator/class-validation-exception.php index e91f6955f3..f16b5b3ce7 100644 --- a/packages/php/email-editor/src/Validator/class-validation-exception.php +++ b/packages/php/email-editor/src/Validator/class-validation-exception.php @@ -1,6 +1,6 @@ $v ) { $result = $this->validate_and_sanitize_value_from_schema( $v, $schema['items'], $param_name . '[' . $i . ']' ); - if ( $this->wp->isWpError( $result ) ) { + if ( is_wp_error( $result ) ) { return $result; } } @@ -117,7 +116,7 @@ class Validator { foreach ( $value as $k => $v ) { if ( isset( $schema['properties'][ $k ] ) ) { $result = $this->validate_and_sanitize_value_from_schema( $v, $schema['properties'][ $k ], $param_name . '[' . $k . ']' ); - if ( $this->wp->isWpError( $result ) ) { + if ( is_wp_error( $result ) ) { return $result; } continue; @@ -126,7 +125,7 @@ class Validator { $pattern_property_schema = rest_find_matching_pattern_property_schema( $k, $schema ); if ( $pattern_property_schema ) { $result = $this->validate_and_sanitize_value_from_schema( $v, $pattern_property_schema, $param_name . '[' . $k . ']' ); - if ( $this->wp->isWpError( $result ) ) { + if ( is_wp_error( $result ) ) { return $result; } continue; @@ -134,7 +133,7 @@ class Validator { if ( isset( $schema['additionalProperties'] ) && is_array( $schema['additionalProperties'] ) ) { $result = $this->validate_and_sanitize_value_from_schema( $v, $schema['additionalProperties'], $param_name . '[' . $k . ']' ); - if ( $this->wp->isWpError( $result ) ) { + if ( is_wp_error( $result ) ) { return $result; } } @@ -161,7 +160,7 @@ class Validator { $errors = array(); foreach ( $any_of_schema['anyOf'] as $index => $schema ) { $result = $this->validate_and_sanitize_value_from_schema( $value, $schema, $param_name ); - if ( ! $this->wp->isWpError( $result ) ) { + if ( ! is_wp_error( $result ) ) { return $result; } $errors[] = array( @@ -170,6 +169,7 @@ class Validator { 'index' => $index, ); } + /* @phpstan-ignore-next-line Wrong annotation for parameter in WP. */ return rest_get_combining_operation_error( $value, $param_name, $errors ); } @@ -187,7 +187,7 @@ class Validator { $data = null; foreach ( $one_of_schema['oneOf'] as $index => $schema ) { $result = $this->validate_and_sanitize_value_from_schema( $value, $schema, $param_name ); - if ( $this->wp->isWpError( $result ) ) { + if ( is_wp_error( $result ) ) { $errors[] = array( 'error_object' => $result, 'schema' => $schema, @@ -200,7 +200,8 @@ class Validator { } if ( ! $matching_schemas ) { - return $this->wp->restGetCombiningOperationError( $value, $param_name, $errors ); + /* @phpstan-ignore-next-line Wrong annotation for parameter in WP. */ + return rest_get_combining_operation_error( $value, $param_name, $errors ); } if ( count( $matching_schemas ) > 1 ) { diff --git a/packages/php/email-editor/src/exceptions.php b/packages/php/email-editor/src/exceptions.php new file mode 100644 index 0000000000..2247bf6014 --- /dev/null +++ b/packages/php/email-editor/src/exceptions.php @@ -0,0 +1,117 @@ +message = $message; + return $this; + } + + /** @return static */ + public function withCode(int $code) { + $this->code = $code; + return $this; + } + + /** @return static */ + public function withErrors(array $errors) { + $this->errors = $errors; + return $this; + } + + /** @return static */ + public function withError(string $id, string $error) { + $this->errors[$id] = $error; + return $this; + } + + public function getErrors(): array { + return $this->errors; + } +} + + +/** + * USE: Generic runtime error. When possible, use a more specific exception instead. + * API: 500 Server Error (not HTTP-aware) + */ +class RuntimeException extends Exception {} + + +/** + * USE: When wrong data VALUE is received. + * API: 400 Bad Request + */ +class UnexpectedValueException extends RuntimeException implements HttpAwareException { + public function getHttpStatusCode(): int { + return 400; + } +} + + +/** + * USE: When an action is forbidden for given actor (although generally valid). + * API: 403 Forbidden + */ +class AccessDeniedException extends UnexpectedValueException implements HttpAwareException { + public function getHttpStatusCode(): int { + return 403; + } +} + + +/** + * USE: When the main resource we're interested in doesn't exist. + * API: 404 Not Found + */ +class NotFoundException extends UnexpectedValueException implements HttpAwareException { + public function getHttpStatusCode(): int { + return 404; + } +} + + +/** + * USE: When the main action produces conflict (i.e. duplicate key). + * API: 409 Conflict + */ +class ConflictException extends UnexpectedValueException implements HttpAwareException { + public function getHttpStatusCode(): int { + return 409; + } +} + + +/** + * USE: An application state that should not occur. Can be subclassed for feature-specific exceptions. + * API: 500 Server Error (not HTTP-aware) + */ +class InvalidStateException extends RuntimeException {} + +class NewsletterProcessingException extends Exception {}