From 194424f2e0dbdc0b72827a45acf42c8d70a86c07 Mon Sep 17 00:00:00 2001 From: Dennis Ploetner Date: Wed, 13 May 2026 13:15:15 +0200 Subject: [PATCH 1/6] Fix weak typing --- includes/Component/Input/Text.php | 2 +- includes/MslsAdmin.php | 7 +-- includes/MslsAdminIcon.php | 76 ++++--------------------------- includes/MslsJson.php | 6 ++- includes/MslsMetaBox.php | 8 ++-- includes/MslsPostTag.php | 5 +- 6 files changed, 26 insertions(+), 78 deletions(-) diff --git a/includes/Component/Input/Text.php b/includes/Component/Input/Text.php index 149d72797..16b0e7440 100644 --- a/includes/Component/Input/Text.php +++ b/includes/Component/Input/Text.php @@ -36,7 +36,7 @@ final class Text extends Component { */ public function __construct( string $key, ?string $value, int $size = self::DEFAULT_SIZE, bool $read_only = false ) { $this->key = $key; - $this->value = $value; + $this->value = $value ?? ''; $this->size = $size; $this->readonly = $read_only ? ' readonly="readonly"' : ''; } diff --git a/includes/MslsAdmin.php b/includes/MslsAdmin.php index 1c0a1173a..b21f30ef4 100644 --- a/includes/MslsAdmin.php +++ b/includes/MslsAdmin.php @@ -98,7 +98,7 @@ public function get_options_page_link(): string { * * @return mixed */ - public function __call( $method, $args ) { + public function __call( string $method, $args ) { $parts = explode( '_', $method, 2 ); if ( 2 === count( $parts ) && 'rewrite' === $parts[0] ) { $this->render_rewrite( $parts[1] ); @@ -433,8 +433,9 @@ public function content_priority(): void { * @param mixed $key */ public function render_rewrite( $key ): void { - $rewrite = get_post_type_object( $key )->rewrite; - $value = $rewrite['slug'] ?? ''; + $pt_object = get_post_type_object( $key ); + $rewrite = $pt_object ? $pt_object->rewrite : array(); + $value = $rewrite['slug'] ?? ''; // phpcs:ignore WordPress.Security.EscapeOutput echo ( new Text( "rewrite_{$key}", $value, 30, true ) )->render(); diff --git a/includes/MslsAdminIcon.php b/includes/MslsAdminIcon.php index 1245c5dce..4faa0cc06 100644 --- a/includes/MslsAdminIcon.php +++ b/includes/MslsAdminIcon.php @@ -13,23 +13,21 @@ */ class MslsAdminIcon { - protected string $icon_type = 'action'; - protected string $language; - public string $origin_language; - protected string $src; - protected string $href; - protected int $blog_id; - protected string $type; - protected string $path = 'post-new.php'; - protected int $id; + protected string $icon_type = 'action'; + protected string $language = ''; + public string $origin_language = ''; + protected string $src = ''; + protected string $href = ''; + protected int $blog_id = 0; + protected string $type = ''; + protected string $path = 'post-new.php'; + protected int $id = 0; const TYPE_FLAG = 'flag'; const TYPE_LABEL = 'label'; /** * Constructor - * - * @param string $type */ public function __construct( ?string $type = null ) { $this->type = $type ?? ''; @@ -37,16 +35,11 @@ public function __construct( ?string $type = null ) { $this->set_path(); } - /** - * @return string - */ - public function __toString() { + public function __toString(): string { return $this->get_a(); } /** - * @param ?string $type - * * @return MslsAdminIcon|MslsAdminIconTaxonomy */ public static function create( ?string $type = null ) { @@ -61,10 +54,6 @@ public static function create( ?string $type = null ) { /** * Set the icon path - * - * @param string $icon_type - * - * @return MslsAdminIcon */ public function set_icon_type( string $icon_type ): MslsAdminIcon { $this->icon_type = $icon_type; @@ -74,8 +63,6 @@ public function set_icon_type( string $icon_type ): MslsAdminIcon { /** * Set the path by type - * - * @return MslsAdminIcon */ public function set_path(): MslsAdminIcon { if ( 'post' !== $this->type ) { @@ -86,39 +73,18 @@ public function set_path(): MslsAdminIcon { return $this; } - /** - * Set language - * - * @param string $language - * - * @return MslsAdminIcon - */ public function set_language( string $language ): MslsAdminIcon { $this->language = $language; return $this; } - /** - * Set src - * - * @param string $src - * - * @return MslsAdminIcon - */ public function set_src( string $src ): MslsAdminIcon { $this->src = $src; return $this; } - /** - * Set href - * - * @param int $id - * - * @return MslsAdminIcon - */ public function set_href( int $id ): MslsAdminIcon { $this->href = get_edit_post_link( $id ) ?? ''; @@ -127,10 +93,6 @@ public function set_href( int $id ): MslsAdminIcon { /** * Sets the id of the object this icon is for - * - * @param int $id - * - * @return MslsAdminIcon */ public function set_id( int $id ): MslsAdminIcon { $this->id = $id; @@ -140,10 +102,6 @@ public function set_id( int $id ): MslsAdminIcon { /** * Sets the origin language for this icon - * - * @param string $origin_language - * - * @return MslsAdminIcon */ public function set_origin_language( string $origin_language ): MslsAdminIcon { $this->origin_language = $origin_language; @@ -153,8 +111,6 @@ public function set_origin_language( string $origin_language ): MslsAdminIcon { /** * Get image as html-tag - * - * @return string */ public function get_img(): string { return sprintf( '%s', $this->language, $this->src ); @@ -162,8 +118,6 @@ public function get_img(): string { /** * Get link as html-tag - * - * @return string */ public function get_a(): string { if ( empty( $this->href ) ) { @@ -185,18 +139,12 @@ public function get_a(): string { return sprintf( '%3$s ', esc_attr( $title ), esc_url( $href ), $this->get_icon() ); } - /** - * @return bool - */ protected function should_quick_create(): bool { return 0 !== $this->id && '' !== $this->origin_language && msls_options()->activate_quick_create; } - /** - * @return string - */ protected function get_quick_create_a(): string { $collection = msls_blog_collection(); $source_blog_id = $collection->get_blog_id( $this->origin_language ); @@ -218,8 +166,6 @@ protected function get_quick_create_a(): string { /** * Get icon as html-tag - * - * @return string */ public function get_icon(): string { if ( ! $this->language ) { @@ -254,8 +200,6 @@ public function get_icon(): string { /** * Creates new admin link - * - * @return string */ public function get_edit_new(): string { $path = $this->path; diff --git a/includes/MslsJson.php b/includes/MslsJson.php index 7aaa0937f..7aabd10ff 100644 --- a/includes/MslsJson.php +++ b/includes/MslsJson.php @@ -40,7 +40,7 @@ public function add( $value, $label ) { * * @return int */ - public static function compare( array $a, array $b ) { + public static function compare( array $a, array $b ): int { return strnatcmp( $a['label'], $b['label'] ); } @@ -63,7 +63,9 @@ public function get(): array { * @return string */ public function encode(): string { - return wp_json_encode( $this->get() ); + $json_string = wp_json_encode( $this->get() ); + + return is_string( $json_string ) ? $json_string : ''; } /** diff --git a/includes/MslsMetaBox.php b/includes/MslsMetaBox.php index 58b0f4a58..8cf607a04 100644 --- a/includes/MslsMetaBox.php +++ b/includes/MslsMetaBox.php @@ -10,6 +10,7 @@ use lloc\Msls\Component\Wrapper; use lloc\Msls\ContentImport\MetaBox as ContentImportMetaBox; use WP_Post; +use WP_Post_Type; /** * Meta box for the edit mode of the (custom) post types @@ -206,10 +207,9 @@ public function render_select(): void { $icon->set_href( $linked_post_id ); } - $selects = ''; - $p_object = get_post_type_object( $type ); - - if ( $p_object->hierarchical ) { + $selects = ''; + $pt_object = get_post_type_object( $type ); + if ( $pt_object instanceof WP_Post_Type && $pt_object->hierarchical ) { $args = array( 'post_type' => $type, 'selected' => $mydata->$language, diff --git a/includes/MslsPostTag.php b/includes/MslsPostTag.php index 27d497d1a..d5a156862 100644 --- a/includes/MslsPostTag.php +++ b/includes/MslsPostTag.php @@ -7,6 +7,7 @@ } use lloc\Msls\Component\Component; +use WP_Term; /** * Post Tag @@ -91,7 +92,7 @@ public static function suggest(): void { ) ); - wp_die( wp_json_encode( $results ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + wp_die( (string) wp_json_encode( $results ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } public static function init(): void { @@ -208,7 +209,7 @@ public function the_input( ?\WP_Term $tag, string $title_format, string $item_fo if ( $mydata->has_value( $language ) ) { $term = get_term( $mydata->$language, $type ); - if ( is_object( $term ) ) { + if ( $term instanceof WP_Term ) { $icon->set_href( (int) $mydata->$language ); $value = $mydata->$language; $title = $term->name; From 43bf7d90e1bd3040ddafc339a85da44f295da65a Mon Sep 17 00:00:00 2001 From: Dennis Ploetner Date: Wed, 13 May 2026 16:36:36 +0200 Subject: [PATCH 2/6] PHPStan issues fixed --- includes/MslsAdmin.php | 11 ++++++----- includes/MslsJson.php | 10 +++++----- includes/MslsLink.php | 2 +- includes/MslsShortCode.php | 12 ++++++++---- includes/MslsSqlCacher.php | 19 +++++++++++++++++-- tests/phpunit/TestMslsAdminIcon.php | 2 +- tests/phpunit/TestMslsShortCode.php | 2 +- 7 files changed, 39 insertions(+), 19 deletions(-) diff --git a/includes/MslsAdmin.php b/includes/MslsAdmin.php index b21f30ef4..c13b767b5 100644 --- a/includes/MslsAdmin.php +++ b/includes/MslsAdmin.php @@ -11,6 +11,7 @@ use lloc\Msls\Component\Input\Label; use lloc\Msls\Component\Input\Text; use lloc\Msls\Component\Input\Select; +use WP_Post_Type; /** * Administration of the options @@ -93,12 +94,12 @@ public function get_options_page_link(): string { /** * You can use every method of the decorated object * - * @param string $method - * @param mixed $args + * @param string $method + * @param mixed[] $args * * @return mixed */ - public function __call( string $method, $args ) { + public function __call( string $method, array $args ) { $parts = explode( '_', $method, 2 ); if ( 2 === count( $parts ) && 'rewrite' === $parts[0] ) { $this->render_rewrite( $parts[1] ); @@ -434,8 +435,8 @@ public function content_priority(): void { */ public function render_rewrite( $key ): void { $pt_object = get_post_type_object( $key ); - $rewrite = $pt_object ? $pt_object->rewrite : array(); - $value = $rewrite['slug'] ?? ''; + $rewrite = $pt_object instanceof WP_Post_Type ? $pt_object->rewrite : null; + $value = is_array( $rewrite ) ? ( $rewrite['slug'] ?? '' ) : ''; // phpcs:ignore WordPress.Security.EscapeOutput echo ( new Text( "rewrite_{$key}", $value, 30, true ) )->render(); diff --git a/includes/MslsJson.php b/includes/MslsJson.php index 7aabd10ff..6cb9b1b95 100644 --- a/includes/MslsJson.php +++ b/includes/MslsJson.php @@ -11,7 +11,7 @@ class MslsJson { /** - * @var array> + * @var array */ protected array $arr = array(); @@ -35,8 +35,8 @@ public function add( $value, $label ) { /** * Compare the item with the key "label" of the array $a and the array $b * - * @param array $a - * @param array $b + * @param array{value: int, label: string} $a + * @param array{value: int, label: string} $b * * @return int */ @@ -47,12 +47,12 @@ public static function compare( array $a, array $b ): int { /** * Get the array container sorted by label * - * @return array> + * @return array */ public function get(): array { $arr = $this->arr; - usort( $arr, array( __CLASS__, 'compare' ) ); + usort( $arr, \Closure::fromCallable( array( __CLASS__, 'compare' ) ) ); return $arr; } diff --git a/includes/MslsLink.php b/includes/MslsLink.php index 6d25860d2..8f6eb330c 100644 --- a/includes/MslsLink.php +++ b/includes/MslsLink.php @@ -19,7 +19,7 @@ class MslsLink extends MslsGetSet implements LinkInterface { /** * Gets all link types as an array with "id => name"-items * - * @return string[] + * @return array */ public static function get_types() { return array( diff --git a/includes/MslsShortCode.php b/includes/MslsShortCode.php index 4e5cf2a1a..ea5df3308 100644 --- a/includes/MslsShortCode.php +++ b/includes/MslsShortCode.php @@ -5,16 +5,20 @@ class MslsShortCode { public static function init(): void { - add_shortcode( 'sc_msls_widget', array( __CLASS__, 'render_widget' ) ); + add_shortcode( 'sc_msls_widget', \Closure::fromCallable( array( __CLASS__, 'render_widget' ) ) ); add_shortcode( 'sc_msls', 'msls_get_switcher' ); } /** * Renders output using the widget's output * - * @return string|false + * @param array|string $atts + * @param string|null $content + * @param string $tag + * + * @return string */ - public static function render_widget() { + public static function render_widget( $atts = array(), ?string $content = null, string $tag = '' ): string { if ( msls_options()->is_excluded() ) { return ''; } @@ -23,6 +27,6 @@ public static function render_widget() { the_widget( MslsWidget::class ); $output = ob_get_clean(); - return $output; + return false === $output ? '' : $output; } } diff --git a/includes/MslsSqlCacher.php b/includes/MslsSqlCacher.php index 0e10bfecd..34d9acabb 100644 --- a/includes/MslsSqlCacher.php +++ b/includes/MslsSqlCacher.php @@ -94,15 +94,30 @@ public function __get( string $name ) { */ public function __call( string $method, array $args ) { if ( 'get_' !== substr( $method, 0, 4 ) ) { - return call_user_func_array( array( $this->db, $method ), $args ); + return $this->invoke( $method, $args ); } $result = wp_cache_get( $this->cache_key, self::CACHE_GROUP ); if ( false === $result ) { - $result = call_user_func_array( array( $this->db, $method ), $args ); + $result = $this->invoke( $method, $args ); wp_cache_set( $this->cache_key, $result, self::CACHE_GROUP, $this->expire ); } return $result; } + + /** + * @param string $method + * @param array $args + * + * @return mixed + */ + protected function invoke( string $method, array $args ) { + $callback = array( $this->db, $method ); + if ( ! is_callable( $callback ) ) { + return null; + } + + return $callback( ...$args ); + } } diff --git a/tests/phpunit/TestMslsAdminIcon.php b/tests/phpunit/TestMslsAdminIcon.php index 00b0a8211..77e4d5935 100644 --- a/tests/phpunit/TestMslsAdminIcon.php +++ b/tests/phpunit/TestMslsAdminIcon.php @@ -152,7 +152,7 @@ public function test_get_img_post_page(): void { } public function test_set_id_with_null_constructor(): void { - Functions\expect( 'add_query_arg' )->once(); + Functions\expect( 'add_query_arg' )->once()->andReturn( 'post-new.php' ); $obj = new MslsAdminIcon( null ); diff --git a/tests/phpunit/TestMslsShortCode.php b/tests/phpunit/TestMslsShortCode.php index f3e441b77..58bced8f5 100644 --- a/tests/phpunit/TestMslsShortCode.php +++ b/tests/phpunit/TestMslsShortCode.php @@ -9,7 +9,7 @@ final class TestMslsShortCode extends MslsUnitTestCase { public function test_init(): void { - Functions\expect( 'add_shortcode' )->once()->with( 'sc_msls_widget', array( MslsShortCode::class, 'render_widget' ) ); + Functions\expect( 'add_shortcode' )->once()->with( 'sc_msls_widget', \Mockery::type( \Closure::class ) ); Functions\expect( 'add_shortcode' )->once()->with( 'sc_msls', 'msls_get_switcher' ); $this->expectNotToPerformAssertions(); From 898662e828772745173066cb24618240478c1a02 Mon Sep 17 00:00:00 2001 From: Dennis Ploetner Date: Wed, 13 May 2026 16:44:12 +0200 Subject: [PATCH 3/6] Fix PHPStan errors in factory classes --- includes/MslsLink.php | 15 +++++++-------- includes/MslsOptions.php | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/includes/MslsLink.php b/includes/MslsLink.php index 8f6eb330c..da28f8838 100644 --- a/includes/MslsLink.php +++ b/includes/MslsLink.php @@ -19,7 +19,7 @@ class MslsLink extends MslsGetSet implements LinkInterface { /** * Gets all link types as an array with "id => name"-items * - * @return array + * @return array> */ public static function get_types() { return array( @@ -42,13 +42,13 @@ public static function get_description(): string { /** * Gets an array with all link descriptions * - * @return array + * @return array */ public static function get_types_description(): array { $types = array(); foreach ( self::get_types() as $key => $class ) { - $types[ $key ] = call_user_func( array( $class, 'get_description' ) ); + $types[ $key ] = $class::get_description(); } return $types; @@ -70,11 +70,10 @@ public static function create( ?int $display ): LinkInterface { $obj = new $types[ $display ](); if ( has_filter( 'msls_link_create' ) ) { - /** - * @param LinkInterface $obj - * @param int $display - */ - $obj = apply_filters( 'msls_link_create', $obj, $display ); + $filtered = apply_filters( 'msls_link_create', $obj, $display ); + if ( $filtered instanceof LinkInterface ) { + $obj = $filtered; + } } return $obj; diff --git a/includes/MslsOptions.php b/includes/MslsOptions.php index 286b0956f..95372835d 100644 --- a/includes/MslsOptions.php +++ b/includes/MslsOptions.php @@ -101,12 +101,12 @@ public static function create( $id = 0 ) { } elseif ( self::is_tax_page() ) { $options = MslsOptionsTax::create(); } elseif ( self::is_query_page() ) { - $options = MslsOptionsQuery::create(); + $options = MslsOptionsQuery::create() ?? new MslsOptions(); } else { $options = new MslsOptionsPost( get_queried_object_id() ); } - add_filter( self::MSLS_GET_POSTLINK_HOOK, array( $options, 'check_for_blog_slug' ), 10, 2 ); + add_filter( self::MSLS_GET_POSTLINK_HOOK, \Closure::fromCallable( array( self::class, 'check_for_blog_slug' ) ), 10, 2 ); return $options; } From bdb77a96357dbc530b62225b323016141d92a28a Mon Sep 17 00:00:00 2001 From: Dennis Ploetner Date: Wed, 13 May 2026 16:57:43 +0200 Subject: [PATCH 4/6] Admin classes clean --- includes/MslsAdmin.php | 13 +++++++++++-- includes/MslsBlogCollection.php | 12 +++++++----- includes/MslsMain.php | 9 +++++---- includes/MslsMetaBox.php | 25 +++++++++++++++++++------ includes/MslsPostTag.php | 8 ++++++-- includes/MslsTranslationPickerTable.php | 4 ++++ 6 files changed, 52 insertions(+), 19 deletions(-) diff --git a/includes/MslsAdmin.php b/includes/MslsAdmin.php index c13b767b5..e5b31ae3c 100644 --- a/includes/MslsAdmin.php +++ b/includes/MslsAdmin.php @@ -127,8 +127,13 @@ public function __call( string $method, array $args ) { ); if ( isset( $checkboxes[ $method ] ) ) { + $value = $this->options->$method; + if ( is_bool( $value ) ) { + $value = $value ? '1' : ''; + } + $group = ( new Group() ) - ->add( new Checkbox( $method, $this->options->$method ) ) + ->add( new Checkbox( $method, $value ) ) ->add( new Label( $method, $checkboxes[ $method ] ) ); echo $group->render(); // phpcs:ignore WordPress.Security.EscapeOutput @@ -334,7 +339,11 @@ public function rewrites_section(): int { */ protected function add_settings_fields( array $map, string $section ): int { foreach ( $map as $id => $title ) { - add_settings_field( $id, $title, array( $this, $id ), __CLASS__, $section, array( 'label_for' => $id ) ); + $callback = array( $this, $id ); + if ( ! is_callable( $callback ) ) { + continue; + } + add_settings_field( $id, $title, $callback, __CLASS__, $section, array( 'label_for' => $id ) ); } /** diff --git a/includes/MslsBlogCollection.php b/includes/MslsBlogCollection.php index 910fbc03c..fbfa5ba31 100644 --- a/includes/MslsBlogCollection.php +++ b/includes/MslsBlogCollection.php @@ -95,7 +95,10 @@ public function __construct() { } } - uasort( $this->objects, array( MslsBlog::class, $this->objects_order ) ); + $compare = array( MslsBlog::class, $this->objects_order ); + if ( is_callable( $compare ) ) { + uasort( $this->objects, $compare ); + } } } @@ -134,12 +137,11 @@ public function get_blogs_of_reference_user( MslsOptions $options ) { $options->reference_user : current( $this->get_users( 'ID', 1 ) ); - $blogs = get_blogs_of_user( $reference_user ); - foreach ( $blogs as $key => $blog ) { - $blogs[ $key ]->blog_id = $blog->userblog_id; + if ( ! is_int( $reference_user ) ) { + $reference_user = 0; } - return $blogs; + return get_blogs_of_user( $reference_user ); } /** diff --git a/includes/MslsMain.php b/includes/MslsMain.php index e8d552ecf..8929e3e80 100644 --- a/includes/MslsMain.php +++ b/includes/MslsMain.php @@ -125,8 +125,8 @@ public function delete( $object_id ): void { /** * Save * - * @param int $object_id - * @param string $class_name + * @param int $object_id + * @param class-string $class_name * * @codeCoverageIgnore */ @@ -145,13 +145,14 @@ protected function save( $object_id, $class_name ): void { return; } - if ( ! $this->collection->has_current_blog() ) { + $current_blog = $this->collection->get_current_blog(); + if ( ! $current_blog instanceof MslsBlog ) { $this->debugger( 'BlogCollection returns false when calling has_current_blog.' ); return; } - $language = $this->collection->get_current_blog()->get_language(); + $language = $current_blog->get_language(); $msla = new MslsLanguageArray( $this->get_input_array( $object_id ) ); $options = new $class_name( $object_id ); $temp = $options->get_arr(); diff --git a/includes/MslsMetaBox.php b/includes/MslsMetaBox.php index 8cf607a04..d25d7e200 100644 --- a/includes/MslsMetaBox.php +++ b/includes/MslsMetaBox.php @@ -89,7 +89,7 @@ public static function suggest(): void { ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - wp_die( wp_json_encode( $results ) ); + wp_die( wp_json_encode( $results ) ?: '' ); } /** @@ -109,14 +109,19 @@ public static function get_suggested_fields( MslsJson $json, array $args ): Msls $args = (array) apply_filters( 'msls_meta_box_suggest_args', $args ); foreach ( get_posts( $args ) as $post ) { + if ( ! $post instanceof \WP_Post ) { + continue; + } + /** * Manipulates the WP_Post object before using it * - * @param WP_Post $post - * * @since 0.9.9 */ - $post = apply_filters( 'msls_meta_box_suggest_post', $post ); + $filtered = apply_filters( 'msls_meta_box_suggest_post', $post ); + if ( $filtered instanceof \WP_Post ) { + $post = $filtered; + } $json->add( $post->ID, get_the_title( $post ) ); } @@ -181,7 +186,11 @@ public function render_select(): void { if ( $blogs ) { global $post; - $type = get_post_type( $post->ID ); + $type = get_post_type( $post->ID ); + if ( false === $type ) { + return; + } + $mydata = new MslsOptionsPost( $post->ID ); $origin_language = MslsBlogCollection::get_blog_language(); $is_saved = 'auto-draft' !== get_post_status( $post ); @@ -321,7 +330,11 @@ public function render_input(): void { if ( $blogs ) { global $post; - $post_type = get_post_type( $post->ID ); + $post_type = get_post_type( $post->ID ); + if ( false === $post_type ) { + return; + } + $my_data = new MslsOptionsPost( $post->ID ); $origin_language = MslsBlogCollection::get_blog_language(); $is_saved = 'auto-draft' !== get_post_status( $post ); diff --git a/includes/MslsPostTag.php b/includes/MslsPostTag.php index d5a156862..d77d1212b 100644 --- a/includes/MslsPostTag.php +++ b/includes/MslsPostTag.php @@ -55,8 +55,12 @@ public static function suggest(): void { * * @since 0.9.9 */ - $args = (array) apply_filters( 'msls_post_tag_suggest_args', $args ); - foreach ( get_terms( $args ) as $term ) { + $args = (array) apply_filters( 'msls_post_tag_suggest_args', $args ); + $terms = get_terms( $args ); + if ( ! is_array( $terms ) ) { + $terms = array(); + } + foreach ( $terms as $term ) { /** * Manipulates the term object before using it * diff --git a/includes/MslsTranslationPickerTable.php b/includes/MslsTranslationPickerTable.php index b8bf1c861..f389315cb 100644 --- a/includes/MslsTranslationPickerTable.php +++ b/includes/MslsTranslationPickerTable.php @@ -191,6 +191,10 @@ public function prepare_items(): void { $taxonomies = array_keys( $this->get_admin_column_taxonomies() ); foreach ( $query->posts as $post ) { + if ( ! $post instanceof \WP_Post ) { + continue; + } + $terms_by_tax = array(); foreach ( $taxonomies as $tax_name ) { $terms = get_the_terms( $post->ID, $tax_name ); From 27392d2a7b50ba3891783bc7992bd4e31a74569e Mon Sep 17 00:00:00 2001 From: Dennis Ploetner Date: Wed, 13 May 2026 17:25:07 +0200 Subject: [PATCH 5/6] Content Import subsystem --- includes/ContentImport/ContentImporter.php | 64 ++++++++++--------- includes/ContentImport/ImportCoordinates.php | 7 ++ includes/ContentImport/ImportLogger.php | 16 +++-- .../Importers/AttachmentsImporters.php | 2 +- .../Importers/ImportersBaseFactory.php | 5 +- .../Importers/PostFieldsImporters.php | 2 +- .../Importers/PostMetaImporters.php | 2 +- .../Importers/PostThumbnail/Linking.php | 15 +++-- .../Importers/PostThumbnailImporters.php | 2 +- .../Importers/Terms/ShallowDuplicating.php | 13 ++-- .../Importers/TermsImporters.php | 2 +- includes/ContentImport/MetaBox.php | 9 ++- includes/ContentImport/Relations.php | 14 ++-- .../ContentImport/TestContentImporter.php | 2 +- 14 files changed, 100 insertions(+), 55 deletions(-) diff --git a/includes/ContentImport/ContentImporter.php b/includes/ContentImport/ContentImporter.php index b6530ee3f..94f0b73a5 100644 --- a/includes/ContentImport/ContentImporter.php +++ b/includes/ContentImport/ContentImporter.php @@ -99,8 +99,12 @@ public function set_relations( Relations $relations ): void { * @return string[] The updated, if needed, data array. */ public function handle_import( array $data = array() ) { + if ( ! $this->pre_flight_check() ) { + return $data; + } + $sources = $this->parse_sources(); - if ( ! $this->pre_flight_check() || false === $sources ) { + if ( null === $sources ) { return $data; } @@ -176,21 +180,21 @@ protected function pre_flight_check() { /** * Parses the source blog and post IDs from the $_POST array validating them. * - * @return int[]|bool + * @return array{0: int, 1: int}|null */ - public function parse_sources() { + public function parse_sources(): ?array { if ( ! MslsRequest::has_var( 'msls_import' ) ) { - return false; + return null; } $msls_import = MslsRequest::get_var( 'msls_import' ); - $import_data = array_filter( explode( '|', trim( $msls_import ) ), 'is_numeric' ); + $import_data = array_values( array_filter( explode( '|', trim( $msls_import ) ), 'is_numeric' ) ); if ( count( $import_data ) !== 2 ) { - return false; + return null; } - return array_map( 'intval', $import_data ); + return array( (int) $import_data[0], (int) $import_data[1] ); } /** @@ -198,12 +202,12 @@ public function parse_sources() { * * @return int */ - protected function get_the_blog_post_ID( $blog_id ) { + protected function get_the_blog_post_ID( $blog_id ): int { switch_to_blog( $blog_id ); $id = get_the_ID(); - if ( ! empty( $id ) ) { + if ( false !== $id && ! empty( $id ) ) { restore_current_blog(); return $id; @@ -226,11 +230,11 @@ protected function get_the_blog_post_ID( $blog_id ) { * @param int $blog_id * @param array $data * - * @return bool|int + * @return int */ - protected function insert_blog_post( $blog_id, array $data = array() ) { + protected function insert_blog_post( $blog_id, array $data = array() ): int { if ( empty( $data ) ) { - return false; + return 0; } switch_to_blog( $blog_id ); @@ -238,7 +242,7 @@ protected function insert_blog_post( $blog_id, array $data = array() ) { if ( ! empty( $data['post_type'] ) && ! post_type_exists( $data['post_type'] ) ) { restore_current_blog(); - return false; + return 0; } $this->handle( false ); @@ -249,7 +253,7 @@ protected function insert_blog_post( $blog_id, array $data = array() ) { } $this->handle( true ); - $this->has_created_post = $post_id > 0 ? $post_id : false; + $this->has_created_post = $post_id > 0 ? $post_id : 0; restore_current_blog(); @@ -319,29 +323,29 @@ public function import_content( ImportCoordinates $import_coordinates, array $po $importers = Map::instance()->make( $import_coordinates ); } - if ( is_null( $this->get_logger() ) ) { - $this->set_logger( new ImportLogger( $import_coordinates ) ); - } + $logger = $this->logger ?? new ImportLogger( $import_coordinates ); + $this->set_logger( $logger ); - if ( is_null( $this->get_relations() ) ) { - $this->set_relations( new Relations( $import_coordinates ) ); - } + $relations = $this->relations ?? new Relations( $import_coordinates ); + $this->set_relations( $relations ); if ( ! empty( $importers ) ) { $source_post_id = $import_coordinates->source_post_id; $dest_lang = $import_coordinates->dest_lang; $dest_post_id = $import_coordinates->dest_post_id; - $this->relations->should_create( MslsOptionsPost::create( $source_post_id ), $dest_lang, $dest_post_id ); + $relations->should_create( MslsOptionsPost::create( $source_post_id ), $dest_lang, $dest_post_id ); foreach ( $importers as $key => $importer ) { - /** @var Importer $importer */ + if ( ! $importer instanceof Importer ) { + continue; + } $post_fields = $importer->import( $post_fields ); - $this->logger->merge( $importer->get_logger() ); - $this->relations->merge( $importer->get_relations() ); + $logger->merge( $importer->get_logger() ); + $relations->merge( $importer->get_relations() ); } - $this->relations->create(); - $this->logger->save(); + $relations->create(); + $logger->save(); } /** @@ -353,7 +357,7 @@ public function import_content( ImportCoordinates $import_coordinates, array $po * * @since TBD */ - do_action( self::MSLS_AFTER_IMPORT_ACTION, $import_coordinates, $this->logger, $this->relations ); + do_action( self::MSLS_AFTER_IMPORT_ACTION, $import_coordinates, $logger, $relations ); /** * Filters the data after the import ran. @@ -367,8 +371,8 @@ public function import_content( ImportCoordinates $import_coordinates, array $po 'msls_content_import_data_after_import', $post_fields, $import_coordinates, - $this->logger, - $this->relations + $logger, + $relations ); } @@ -395,7 +399,7 @@ protected function update_inserted_blog_post_data( $blog_id, $post_id, array $da */ protected function redirect_to_blog_post( $dest_blog_id, $post_id ) { switch_to_blog( $dest_blog_id ); - $edit_post_link = html_entity_decode( get_edit_post_link( $post_id ) ); + $edit_post_link = html_entity_decode( get_edit_post_link( $post_id ) ?? '' ); wp_safe_redirect( $edit_post_link ); die(); } diff --git a/includes/ContentImport/ImportCoordinates.php b/includes/ContentImport/ImportCoordinates.php index 12c3d46fb..9a18455cf 100644 --- a/includes/ContentImport/ImportCoordinates.php +++ b/includes/ContentImport/ImportCoordinates.php @@ -97,7 +97,14 @@ public function parse_importers_from_request(): void { } } + if ( ! is_array( $importers ) ) { + return; + } + foreach ( $importers as $importer_type => $slug ) { + if ( ! is_string( $slug ) ) { + continue; + } $this->set_importer_for( $importer_type, $slug ); } } diff --git a/includes/ContentImport/ImportLogger.php b/includes/ContentImport/ImportLogger.php index 23d6749f3..c510fab4b 100644 --- a/includes/ContentImport/ImportLogger.php +++ b/includes/ContentImport/ImportLogger.php @@ -8,7 +8,7 @@ class ImportLogger { /** - * @var string + * @var non-empty-string */ protected string $levels_delimiter = '/'; @@ -154,9 +154,9 @@ public function get_levels_delimiter(): string { /** * Sets the string that will be used to split paths into levels. * - * @param string $levels_delimiter + * @param non-empty-string $levels_delimiter */ - public function set_levels_delimiter( $levels_delimiter ): void { + public function set_levels_delimiter( string $levels_delimiter ): void { $this->levels_delimiter = $levels_delimiter; } @@ -197,9 +197,17 @@ public function get_error( $where ) { protected function get_nested_value( $where ) { $path = $this->build_path( $where ); - $data = $this->data[ array_shift( $path ) ]; + $first = array_shift( $path ); + if ( null === $first || ! isset( $this->data[ $first ] ) ) { + return null; + } + + $data = $this->data[ $first ]; foreach ( $path as $frag ) { + if ( ! is_array( $data ) || ! isset( $data[ $frag ] ) ) { + return null; + } $data = $data[ $frag ]; } diff --git a/includes/ContentImport/Importers/AttachmentsImporters.php b/includes/ContentImport/Importers/AttachmentsImporters.php index f1f333993..96947764d 100644 --- a/includes/ContentImport/Importers/AttachmentsImporters.php +++ b/includes/ContentImport/Importers/AttachmentsImporters.php @@ -9,7 +9,7 @@ class AttachmentsImporters extends ImportersBaseFactory { const TYPE = 'attachments'; /** - * @var array + * @var array> */ protected array $importers_map = array( Linking::TYPE => Linking::class, diff --git a/includes/ContentImport/Importers/ImportersBaseFactory.php b/includes/ContentImport/Importers/ImportersBaseFactory.php index 122d316af..885de3496 100644 --- a/includes/ContentImport/Importers/ImportersBaseFactory.php +++ b/includes/ContentImport/Importers/ImportersBaseFactory.php @@ -13,7 +13,7 @@ abstract class ImportersBaseFactory extends MslsRegistryInstance implements Impo const TYPE = 'none'; /** - * @var array An array defining the slug and Importer class relationships in + * @var array> An array defining the slug and Importer class relationships in * the shape [ => ] */ protected array $importers_map = array(); @@ -67,6 +67,9 @@ public function make( ImportCoordinates $import_coordinates ) { // If there is some incoherence, return the null-doing base importer. $class = ! empty( $slug ) && isset( $map[ $slug ] ) ? $map[ $slug ] : BaseImporter::class; + if ( ! is_string( $class ) || ! is_a( $class, Importer::class, true ) ) { + $class = BaseImporter::class; + } return new $class( $import_coordinates ); } diff --git a/includes/ContentImport/Importers/PostFieldsImporters.php b/includes/ContentImport/Importers/PostFieldsImporters.php index 2e0d989e9..7313b786e 100644 --- a/includes/ContentImport/Importers/PostFieldsImporters.php +++ b/includes/ContentImport/Importers/PostFieldsImporters.php @@ -9,7 +9,7 @@ class PostFieldsImporters extends ImportersBaseFactory { const TYPE = 'post-fields'; /** - * @var array + * @var array> */ protected array $importers_map = array( Duplicating::TYPE => Duplicating::class, diff --git a/includes/ContentImport/Importers/PostMetaImporters.php b/includes/ContentImport/Importers/PostMetaImporters.php index 8930ff9dd..4d7de1bc5 100644 --- a/includes/ContentImport/Importers/PostMetaImporters.php +++ b/includes/ContentImport/Importers/PostMetaImporters.php @@ -9,7 +9,7 @@ class PostMetaImporters extends ImportersBaseFactory { const TYPE = 'post-meta'; /** - * @var array + * @var array> */ protected array $importers_map = array( Duplicating::TYPE => Duplicating::class, diff --git a/includes/ContentImport/Importers/PostThumbnail/Linking.php b/includes/ContentImport/Importers/PostThumbnail/Linking.php index d276c9a10..65bafd4f5 100644 --- a/includes/ContentImport/Importers/PostThumbnail/Linking.php +++ b/includes/ContentImport/Importers/PostThumbnail/Linking.php @@ -63,15 +63,20 @@ public function import( array $data ) { // In some instances, the folder sep. `/` might be duplicated, we de-duplicate it. array_walk( $source_upload_dir, - function ( &$entry ) { - $entry = str_replace( '//', '/', $entry ); + function ( &$entry ): void { + if ( is_string( $entry ) ) { + $entry = str_replace( '//', '/', $entry ); + } } ); + $subdir = is_string( $source_upload_dir['subdir'] ) ? $source_upload_dir['subdir'] : ''; + $path = is_string( $source_upload_dir['path'] ) ? $source_upload_dir['path'] : ''; + $source_uploads_dir = untrailingslashit( str_replace( - $source_upload_dir['subdir'], + $subdir, '', - $source_upload_dir['path'] + $path ) ); $source_post_thumbnail_file = $source_uploads_dir . '/' . $source_post_thumbnail_meta['_wp_attached_file']; @@ -91,7 +96,7 @@ function ( &$entry ) { $found = get_posts( array( 'post_type' => 'attachment', - 'title' => $attachment['post_title'], + 'title' => $attachment['post_title'] ?? '', ) ); if ( isset( $found[0]->ID ) ) { diff --git a/includes/ContentImport/Importers/PostThumbnailImporters.php b/includes/ContentImport/Importers/PostThumbnailImporters.php index d1cb0716e..a658b42dd 100644 --- a/includes/ContentImport/Importers/PostThumbnailImporters.php +++ b/includes/ContentImport/Importers/PostThumbnailImporters.php @@ -9,7 +9,7 @@ class PostThumbnailImporters extends ImportersBaseFactory { const TYPE = 'post-thumbnail'; /** - * @var array + * @var array> */ protected array $importers_map = array( Linking::TYPE => Linking::class, diff --git a/includes/ContentImport/Importers/Terms/ShallowDuplicating.php b/includes/ContentImport/Importers/Terms/ShallowDuplicating.php index 49ac1479b..9120f3bec 100644 --- a/includes/ContentImport/Importers/Terms/ShallowDuplicating.php +++ b/includes/ContentImport/Importers/Terms/ShallowDuplicating.php @@ -53,7 +53,13 @@ public function import( array $data ) { switch_to_blog( $source_blog_id ); - $source_terms = wp_get_post_terms( $source_post_id, get_taxonomies() ); + $source_terms = wp_get_post_terms( $source_post_id, get_taxonomies() ); + if ( is_wp_error( $source_terms ) ) { + restore_current_blog(); + + return $data; + } + $source_terms_ids = wp_list_pluck( $source_terms, 'term_id' ); $msls_terms = array_combine( $source_terms_ids, @@ -62,7 +68,6 @@ public function import( array $data ) { switch_to_blog( $this->import_coordinates->dest_blog_id ); - /** @var \WP_Term $term */ foreach ( $source_terms as $term ) { // is there a translation for the term in this blog? $msls_term = $msls_terms[ $term->term_id ]; @@ -72,7 +77,7 @@ public function import( array $data ) { $dest_term_id = $this->create_local_term( $term, $msls_term, $dest_lang ); } - if ( false === $dest_term_id ) { + if ( ! is_int( $dest_term_id ) ) { continue; } @@ -82,7 +87,7 @@ public function import( array $data ) { // While we think the term translation exists it might not, let's create it. $dest_term_id = $this->create_local_term( $term, $msls_term, $dest_lang ); - if ( false === $dest_term_id ) { + if ( ! is_int( $dest_term_id ) ) { continue; } diff --git a/includes/ContentImport/Importers/TermsImporters.php b/includes/ContentImport/Importers/TermsImporters.php index 21b7d86d9..a06bd34c5 100644 --- a/includes/ContentImport/Importers/TermsImporters.php +++ b/includes/ContentImport/Importers/TermsImporters.php @@ -9,7 +9,7 @@ class TermsImporters extends ImportersBaseFactory { const TYPE = 'terms'; /** - * @var array + * @var array> */ protected array $importers_map = array( ShallowDuplicating::TYPE => ShallowDuplicating::class, diff --git a/includes/ContentImport/MetaBox.php b/includes/ContentImport/MetaBox.php index 9457cef69..577657d1d 100644 --- a/includes/ContentImport/MetaBox.php +++ b/includes/ContentImport/MetaBox.php @@ -23,7 +23,11 @@ class MetaBox extends MslsRegistryInstance { * Renders the content import metabox. */ public function render(): void { - $post = get_post(); + $post = get_post(); + if ( ! $post instanceof \WP_Post ) { + return; + } + $mydata = new MslsOptionsPost( $post->ID ); $languages = MslsOptionsPost::instance()->get_available_languages(); $current = MslsBlogCollection::get_blog_language( get_current_blog_id() ); @@ -185,6 +189,9 @@ protected function inline_thickbox_html( $output = true, array $data = array() ) > + * @var array */ protected array $local_options = array(); @@ -93,7 +93,7 @@ protected function create_local_to_source(): void { * @param mixed $created If not `null` then the class will not create the local to source relation. * @param int $local_id * @param int $source_id - * @param MslsOptions $source_option + * @param OptionsInterface $source_option */ $created = apply_filters( 'msls_content_import_relation_local_to_source_create', @@ -106,9 +106,15 @@ protected function create_local_to_source(): void { continue; } + if ( ! $source_option instanceof MslsOptions ) { + continue; + } + $option_class = get_class( $source_option ); - $local_option = call_user_func( array( $option_class, 'create' ), $local_id ); - $local_option->save( array( $this->import_coordinates->source_lang => $source_id ) ); + $local_option = $option_class::create( $local_id ); + if ( $local_option instanceof MslsOptions ) { + $local_option->save( array( $this->import_coordinates->source_lang => $source_id ) ); + } } } diff --git a/tests/phpunit/ContentImport/TestContentImporter.php b/tests/phpunit/ContentImport/TestContentImporter.php index a94d24304..a80879191 100644 --- a/tests/phpunit/ContentImport/TestContentImporter.php +++ b/tests/phpunit/ContentImport/TestContentImporter.php @@ -43,7 +43,7 @@ public function test_handle_import(): void { public function test_parse_sources_no_post(): void { $test = $this->ContentImporterFactory(); - $this->assertFalse( $test->parse_sources() ); + $this->assertNull( $test->parse_sources() ); } public function test_handle_false(): void { From cde0c748d3a0558750f22e88054e821d9ccb94f9 Mon Sep 17 00:00:00 2001 From: Dennis Ploetner Date: Wed, 13 May 2026 17:55:18 +0200 Subject: [PATCH 6/6] Issues that came up during review fixed --- includes/MslsJson.php | 2 +- includes/MslsOptions.php | 2 +- includes/MslsShortCode.php | 2 +- tests/phpunit/TestMslsShortCode.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/includes/MslsJson.php b/includes/MslsJson.php index 6cb9b1b95..a224dbf0e 100644 --- a/includes/MslsJson.php +++ b/includes/MslsJson.php @@ -52,7 +52,7 @@ public static function compare( array $a, array $b ): int { public function get(): array { $arr = $this->arr; - usort( $arr, \Closure::fromCallable( array( __CLASS__, 'compare' ) ) ); + usort( $arr, array( __CLASS__, 'compare' ) ); return $arr; } diff --git a/includes/MslsOptions.php b/includes/MslsOptions.php index 95372835d..82f2f0011 100644 --- a/includes/MslsOptions.php +++ b/includes/MslsOptions.php @@ -106,7 +106,7 @@ public static function create( $id = 0 ) { $options = new MslsOptionsPost( get_queried_object_id() ); } - add_filter( self::MSLS_GET_POSTLINK_HOOK, \Closure::fromCallable( array( self::class, 'check_for_blog_slug' ) ), 10, 2 ); + add_filter( self::MSLS_GET_POSTLINK_HOOK, array( self::class, 'check_for_blog_slug' ), 10, 2 ); return $options; } diff --git a/includes/MslsShortCode.php b/includes/MslsShortCode.php index ea5df3308..6e21db094 100644 --- a/includes/MslsShortCode.php +++ b/includes/MslsShortCode.php @@ -5,7 +5,7 @@ class MslsShortCode { public static function init(): void { - add_shortcode( 'sc_msls_widget', \Closure::fromCallable( array( __CLASS__, 'render_widget' ) ) ); + add_shortcode( 'sc_msls_widget', array( __CLASS__, 'render_widget' ) ); add_shortcode( 'sc_msls', 'msls_get_switcher' ); } diff --git a/tests/phpunit/TestMslsShortCode.php b/tests/phpunit/TestMslsShortCode.php index 58bced8f5..f3e441b77 100644 --- a/tests/phpunit/TestMslsShortCode.php +++ b/tests/phpunit/TestMslsShortCode.php @@ -9,7 +9,7 @@ final class TestMslsShortCode extends MslsUnitTestCase { public function test_init(): void { - Functions\expect( 'add_shortcode' )->once()->with( 'sc_msls_widget', \Mockery::type( \Closure::class ) ); + Functions\expect( 'add_shortcode' )->once()->with( 'sc_msls_widget', array( MslsShortCode::class, 'render_widget' ) ); Functions\expect( 'add_shortcode' )->once()->with( 'sc_msls', 'msls_get_switcher' ); $this->expectNotToPerformAssertions();