Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ parameters:
level: 5
paths:
- MslsMenu.php
scanDirectories:
- vendor/lloc/multisite-language-switcher/includes
bootstrapFiles:
- tests/Pest.php
- tests/phpstan-bootstrap.php
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
- `composer qa` runs the full gate: `phpcs`, `phpstan`, then `pest`.
- `composer pest` executes the Pest suite with mocked WordPress APIs.
- `composer phpcs` checks the WordPress coding standards and PHP 7.4 compatibility via `.phpcs.xml.dist`; `composer phpcbf` fixes what can be fixed automatically.
- `composer analyze` runs PHPStan (level 5) against `MslsMenu.php`; keep it passing before pushing.
- `composer analyze` runs PHPStan (level 5) against `MslsMenu.php`; keep it passing before pushing. It analyses against the real Multisite Language Switcher, pulled in as a dev dependency and bootstrapped in `tests/phpstan-bootstrap.php` — the stubs in `tests/Pest.php` keep the unit tests WordPress-free but do not reflect the MSLS API.
- `composer pest:coverage` enables Xdebug coverage for pull request evidence.
- `composer build` runs `composer update --no-dev` and then `bin/git-release.sh`, recreating `mslsmenu/` and `mslsmenu.zip`. It strips your local dev dependencies — run `composer install` afterwards to get the tooling back.

Expand All @@ -35,4 +35,4 @@
## Release Packaging Tips
- Bump the plugin header version in `MslsMenu.php` and the `Stable tag` in `readme.txt` to the same value, and add the matching `= x.y.z =` entry to the `readme.txt` changelog. WordPress.org rejects the release if header and stable tag disagree.
- Run `composer build` locally only to inspect the result. The shipped artifact is built in CI: `.github/workflows/deploy.yml` runs the same `composer build`, verifies the contents and deploys them through `BUILD_DIR: mslsmenu`.
- Tag without a `v` prefix (`3.0.0`) and push the tag — that push is what triggers the deploy workflow.
- Tag without a `v` prefix (`3.0.1`) and push the tag — that push is what triggers the deploy workflow.
50 changes: 12 additions & 38 deletions MslsMenu.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*
* Plugin Name: MslsMenu
* Requires Plugins: multisite-language-switcher
* Version: 3.0.0
* Version: 3.0.1
* Plugin URI: https://wordpress.org/plugins/mslsmenu/
* Description: Adds the Multisite Language Switcher to the primary-nav-menu
* Author: Dennis Ploetner
Expand Down Expand Up @@ -82,8 +82,8 @@ public static function init( $options ): MslsMenu {
return $obj;
}

private function get_msls_output(): lloc\Msls\MslsOutput {
return function_exists( 'msls_output' ) ? msls_output() : lloc\Msls\MslsOutput::init();
private function get_msls_output(): lloc\Msls\Frontend\Output {
return msls_output();
}

/**
Expand All @@ -102,7 +102,7 @@ public function nav_item( string $items, \stdClass $args ): string {
$menu = '';

$obj = $this->get_msls_output();
foreach ( $obj->get( (int) $this->options->mslsmenu_display, false, (int) $this->options->only_with_translation ) as $item ) {
foreach ( $obj->get( (int) $this->options->mslsmenu_display, false, (bool) $this->options->only_with_translation ) as $item ) {
$menu .= $this->options->mslsmenu_before_item . $item . $this->options->mslsmenu_after_item;
}

Expand All @@ -129,15 +129,13 @@ public function admin_register( string $page ) {
* Callback for add_settings_section in admin_register
*/
public function add_settings(): void {
$args = array( 'msls_admin' => lloc\Msls\MslsAdmin::init() );

$label = __( 'Theme Location', 'mslsmenu' );
$callback = array( $this, 'theme_location' );
add_settings_field( 'mslsmenu_theme_location', $label, $callback, $this->page, self::SID, $args );
add_settings_field( 'mslsmenu_theme_location', $label, $callback, $this->page, self::SID );

$label = __( 'Display', 'mslsmenu' );
$callback = array( $this, 'display' );
add_settings_field( 'mslsmenu_display', $label, $callback, $this->page, self::SID, $args );
add_settings_field( 'mslsmenu_display', $label, $callback, $this->page, self::SID );

$fields = array(
'mslsmenu_before_output' => __( 'Text/HTML before the list', 'mslsmenu' ),
Expand All @@ -148,7 +146,7 @@ public function add_settings(): void {

$callback = array( $this, 'input' );
foreach ( $fields as $id => $label ) {
$args['mslsmenu_input'] = $id;
$args = array( 'mslsmenu_input' => $id );
add_settings_field( $id, $label, $callback, $this->page, self::SID, $args );
}
}
Expand Down Expand Up @@ -188,23 +186,10 @@ protected function selected( string $needle, $locations ): string {
*
* @param array $args
*/
public function display( array $args ) {
$types = lloc\Msls\MslsLink::get_types_description();
public function display( array $args ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Signature is dictated by add_settings_field().
$types = lloc\Msls\Link\Link::get_types_description();
$display = $this->options->mslsmenu_display ?? '0';

/**
* Backward compatibility
*/
if ( ! class_exists( lloc\Msls\Component\Input\Select::class ) ) {
// @codeCoverageIgnoreStart

// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['msls_admin']->render_select( 'mslsmenu_display', $types, $display );

return;
// @codeCoverageIgnoreEnd
}

// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo ( new lloc\Msls\Component\Input\Select( 'mslsmenu_display', $types, $display ) )->render();
}
Expand All @@ -215,19 +200,6 @@ public function display( array $args ) {
* @param array $args
*/
public function input( array $args ) {
/**
* Backward compatibility
*/
if ( ! class_exists( 'lloc\Msls\Component\Input\Text' ) ) {
// @codeCoverageIgnoreStart

// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['msls_admin']->render_input( $args['mslsmenu_input'] );

return;
// @codeCoverageIgnoreEnd
}

$key = $args['mslsmenu_input'] ?? '';
$value = $this->options->$key ?? '';

Expand All @@ -241,7 +213,9 @@ public function input( array $args ) {
add_action(
'plugins_loaded',
function () {
$options = class_exists( lloc\Msls\MslsOptions::class ) ? lloc\Msls\MslsOptions::instance() : null;
// The namespaced class only exists from MSLS 3.0 on. Against an older MSLS the
// null object keeps every hook unregistered instead of fataling on a missing type.
$options = class_exists( lloc\Msls\Options\Options::class ) ? msls_options() : null;
MslsMenu::init( $options );
}
);
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ MslsMenu

MslsMenu extends the Multisite Language Switcher by appending its language links to the navigation menus of your choice. Use it when you want the switcher inside a nav menu without editing your theme or writing a custom walker.

Multisite Language Switcher has to be installed and active first — MslsMenu is an add-on and declares that through its `Requires Plugins` header.
Multisite Language Switcher **3.0 or newer** has to be installed and active first — MslsMenu is an add-on and declares that through its `Requires Plugins` header. That header carries no version constraint, so against an older MSLS the plugin stays inactive rather than raising an error.

## Install
- From WordPress: search for “MslsMenu” under Plugins → Add New and activate it.
Expand Down
10 changes: 9 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
"wp-coding-standards/wpcs": "^3.1",
"phpcompatibility/phpcompatibility-wp": "^2.1",
"lloc/composer-i18n-scripts": "^1.0"
"lloc/composer-i18n-scripts": "^1.0",
"lloc/multisite-language-switcher": "^3.0"
},
"scripts": {
"phpcs": "phpcs",
Expand Down Expand Up @@ -60,5 +61,12 @@
"pestphp/pest-plugin": true,
"lloc/composer-i18n-scripts": true
}
},
"extra": {
"installer-paths": {
"vendor/{$vendor}/{$name}/": [
"type:wordpress-plugin"
]
}
}
}
4 changes: 2 additions & 2 deletions languages/mslsmenu.pot
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
# This file is distributed under the GPLv2 or later.
msgid ""
msgstr ""
"Project-Id-Version: MslsMenu 3.0.0\n"
"Project-Id-Version: MslsMenu 3.0.1\n"
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/MslsMenu\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"POT-Creation-Date: 2026-08-22T14:16:27+00:00\n"
"POT-Creation-Date: 2026-08-22T14:39:19+00:00\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"X-Generator: WP-CLI 2.11.0\n"
"X-Domain: mslsmenu\n"
Expand Down
13 changes: 7 additions & 6 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Tags: multilingual, multisite, language, switcher, menu
Requires at least: 6.1
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 3.0.0
Stable tag: 3.0.1
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html

Expand Down Expand Up @@ -35,10 +35,11 @@ But this can lead to fatal errors if you don't know much about PHP, or maybe the

== Changelog ==

= 3.0.0 =
* Changed: version aligned with Multisite Language Switcher 3.0 - this is the add-on release for the new MSLS major version.
= 3.0.1 =
* Changed: the version number now matches Multisite Language Switcher 3.0.1 - this is the add-on release for the new MSLS major version.
* Changed: requires WordPress 6.1 or newer, tested up to 7.1.
* Internal: the release is deployed from the built distribution, the same artifact Plugin Check verifies. Build-only dependencies are no longer shipped.
* Changed: requires Multisite Language Switcher 3.0 or newer. MslsMenu now calls the namespaced MSLS classes instead of the pre-3.0 compatibility aliases. With an older MSLS it stays inactive instead of raising an error.
* Internal: the release is deployed from the built distribution, the same artifact Plugin Check verifies. Build-only dependencies are no longer shipped. Static analysis now runs against the real MSLS classes instead of test stubs.

= 2.5.1 =
* plugin check integration added
Expand Down Expand Up @@ -90,5 +91,5 @@ But this can lead to fatal errors if you don't know much about PHP, or maybe the

== Upgrade Notice ==

= 3.0.0 =
Version alignment with Multisite Language Switcher 3.0. Requires WordPress 6.1 or newer.
= 3.0.1 =
Version alignment with Multisite Language Switcher 3.0.1. Requires MSLS 3.0 or newer and WordPress 6.1 or newer.
14 changes: 3 additions & 11 deletions tests/Pest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,6 @@ public static function instance() {
}
}

class Admin {

public static function init() {
return new self();
}
}

class Link {

public static function get_types_description() {
Expand Down Expand Up @@ -50,9 +43,8 @@ public function render(): string {
}
}

class_alias( Options::class, 'lloc\Msls\MslsOptions' );
class_alias( Admin::class, 'lloc\Msls\MslsAdmin' );
class_alias( Link::class, 'lloc\Msls\MslsLink' );
class_alias( Output::class, 'lloc\Msls\MslsOutput' );
class_alias( Options::class, 'lloc\Msls\Options\Options' );
class_alias( Link::class, 'lloc\Msls\Link\Link' );
class_alias( Output::class, 'lloc\Msls\Frontend\Output' );
class_alias( Select::class, 'lloc\Msls\Component\Input\Select' );
class_alias( Text::class, 'lloc\Msls\Component\Input\Text' );
29 changes: 26 additions & 3 deletions tests/ValidObjectTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function () {

beforeEach(
function () {
$options = \Mockery::mock( 'lloc\Msls\MslsOptions' );
$options = \Mockery::mock( 'lloc\Msls\Options\Options' );
$options->mslsmenu_theme_location = array( 'test' );

$this->sut = \MslsMenu::init( $options );
Expand Down Expand Up @@ -69,8 +69,8 @@ function () {
function () {
$expected = '';

$args = new \stdClass();
$args->theme_locations = 'test';
$args = new \stdClass();
$args->theme_location = 'other';

$result = $this->sut->nav_item( '', $args );

Expand Down Expand Up @@ -123,3 +123,26 @@ function () {
$this->expectOutputString( $expected );
}
);

it(
'appends the switcher on $sut->nav_item() when the theme location matches',
function () {
Functions\when( 'msls_output' )->justReturn( new \Output() );

$options = \Mockery::mock( 'lloc\Msls\Options\Options' );
$options->mslsmenu_theme_location = array( 'primary' );
$options->mslsmenu_display = 0;
$options->only_with_translation = false;
$options->mslsmenu_before_output = '<ul>';
$options->mslsmenu_after_output = '</ul>';
$options->mslsmenu_before_item = '<li>';
$options->mslsmenu_after_item = '</li>';

$sut = \MslsMenu::init( $options );

$args = new \stdClass();
$args->theme_location = 'primary';

expect( $sut->nav_item( '', $args ) )->toEqual( '<ul><li>de</li><li>en</li></ul>' );
}
);
21 changes: 21 additions & 0 deletions tests/phpstan-bootstrap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php
/**
* PHPStan bootstrap.
*
* Loads the real Multisite Language Switcher so that the `lloc\Msls\*` symbols
* MslsMenu calls are analysed against the actual API. The stubs in tests/Pest.php
* exist to keep the unit tests free of WordPress, but they also hide changes in
* MSLS - `MslsAdmin::init()` losing its return value in MSLS 3.0 went unnoticed
* because the stub still returned an object.
*
* @package mslsmenu
*/

declare( strict_types=1 );

// phpstan-wordpress may have defined it already; aliases.php bails out without it.
if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', dirname( __DIR__ ) . '/' );
}

require_once dirname( __DIR__ ) . '/vendor/lloc/multisite-language-switcher/includes/aliases.php';
Loading