Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 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
2 changes: 1 addition & 1 deletion inc/packages/admin/info.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ function render_page( MetadataDocument $metadata, string $tab, string $section )
* @param string $section Page section.
*/
function render( MetadataDocument $doc, string $tab, string $section ) {
$sections = (array) $doc->sections;
$sections = Admin\order_sections_by_predefined_order( (array) $doc->sections );

if ( ! isset( $sections[ $section ] ) ) {
$section = array_keys( $sections )[0];
Expand Down
57 changes: 57 additions & 0 deletions inc/packages/admin/namespace.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use FAIR\Packages\MetadataDocument;
use FAIR\Packages\ReleaseDocument;
use FAIR\Updater;
use WP_Error;

const TAB_DIRECT = 'fair_direct';
const ACTION_INSTALL = 'fair-install-plugin';
Expand All @@ -34,6 +35,7 @@ function bootstrap() {
add_action( 'load-plugin-install.php', __NAMESPACE__ . '\\load_plugin_install' );
add_action( 'install_plugins_pre_plugin-information', __NAMESPACE__ . '\\maybe_hijack_plugin_info', 0 );
add_filter( 'plugins_api_result', __NAMESPACE__ . '\\alter_slugs', 10, 3 );
add_filter( 'plugins_api_result', __NAMESPACE__ . '\\order_plugin_information_sections', 11, 2 );
add_filter( 'plugin_install_action_links', __NAMESPACE__ . '\\maybe_hijack_plugin_install_button', 10, 2 );
add_filter( 'plugin_install_description', __NAMESPACE__ . '\\maybe_add_data_to_description', 10, 2 );
add_action( 'wp_ajax_check_plugin_dependencies', __NAMESPACE__ . '\\set_slug_to_hashed' );
Expand Down Expand Up @@ -450,6 +452,61 @@ function alter_slugs( $res, $action, $args ) {
return $res;
}

/**
* Orders the sections of Plugin Installation API response.
*
* @param object|WP_Error $res Response object or WP_Error.
* @param string $action The type of information being requested from the Plugin Installation API.
*/
function order_plugin_information_sections( $res, $action ) {
if ( is_wp_error( $res ) ) {
return $res;
}

if ( 'plugin_information' !== $action ) {
return $res;
}

if ( empty( $res->sections ) ) {
return $res;
}

$res->sections = order_sections_by_predefined_order( $res->sections );

return $res;
}

/**
* Order sections by a predefined order.
*
* @param array<string, string> $sections Sections to order.
*
* @return array<string, string> Ordered sections.
*/
function order_sections_by_predefined_order( array $sections ) : array {
$desired_order = [
'description',
'installation',
'faq',
'screenshots',
'changelog',
'security',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security is not a standard section and would therefore be grouped in other_notes

Copy link
Copy Markdown
Contributor Author

@meszarosrob meszarosrob Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not something that is returned/used by WP.org; however, it is defined in the protocol https://github.com/fairpm/fair-protocol/blob/main/specification.md#sections, so I have included it.

My assumption (which might be totally wrong) was that if it's in the protocol, then eventually it might get used.

If somebody passes it today, it will be displayed in the tabs.

'reviews',
'other_notes',
];

$desired_order_index = array_flip( $desired_order );

uksort($sections, function( $a, $b ) use ( $desired_order_index ) {
$pos_a = $desired_order_index[ $a ] ?? PHP_INT_MAX;
$pos_b = $desired_order_index[ $b ] ?? PHP_INT_MAX;

return $pos_a <=> $pos_b;
});

return $sections;
}

/**
* Override the install button, for bridged plugins.
*
Expand Down
97 changes: 97 additions & 0 deletions tests/phpunit/tests/Packages/Admin/NamespaceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php
/**
* Tests for functions under FAIR\Packages\Admin namespace.
*
* @package FAIR
*/

use function FAIR\Packages\Admin\order_sections_by_predefined_order;

/**
* Test cases for functions under FAIR\Packages\Admin namespace.
*/
class NamespaceTest extends WP_UnitTestCase {

/**
* Test that sections are ordered in a predefined order.
*
* @dataProvider data_plugin_detail_sections
*
* @param array $sections Sections provided in arbitrary order, as if returned from MetadataDocument.
* @param array $ordered_sections The sections in order we expect them to be.
*/
public function test_should_return_sections_in_predefined_order( array $sections, array $ordered_sections ) {
$this->assertSame(
$ordered_sections,
order_sections_by_predefined_order( $sections )
);
}

/**
* Data provider.
*/
public static function data_plugin_detail_sections(): array {
return [
'expected sections' => [
'arbitrary order' => [
'faq' => '',
'screenshots' => '',
'changelog' => '',
'description' => '',
'security' => '',
'reviews' => '',
'other_notes' => '',
'installation' => '',
],
'expected order' => [
'description' => '',
'installation' => '',
'faq' => '',
'screenshots' => '',
'changelog' => '',
'security' => '',
'reviews' => '',
'other_notes' => '',
],
],
'unknown sections' => [
'arbitrary order' => [
'foo' => '',
'bar' => '',
'baz' => '',
],
'expected order' => [
'foo' => '',
'bar' => '',
'baz' => '',
],
],
'expected and unknown sections' => [
'arbitrary order' => [
'faq' => '',
'foo' => '',
'screenshots' => '',
'changelog' => '',
'bar' => '',
'reviews' => '',
'installation' => '',
'security' => '',
],
'expected order' => [
'installation' => '',
'faq' => '',
'screenshots' => '',
'changelog' => '',
'security' => '',
'reviews' => '',
'foo' => '',
'bar' => '',
],
],
'empty sections' => [
'arbitrary order' => [],
'expected order' => [],
],
];
}
}