Skip to content
Closed
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
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
49 changes: 49 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,53 @@ function alter_slugs( $res, $action, $args ) {
return $res;
}

/**
* Order 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 ) || 'plugin_information' !== $action || 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
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
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php
/**
* Tests for FAIR\Packages\Admin\order_sections_by_predefined_order().
*
* @package FAIR
*/

use function FAIR\Packages\Admin\order_sections_by_predefined_order;

/**
* Tests for FAIR\Packages\Admin\order_sections_by_predefined_order().
*
* @covers FAIR\Packages\Admin\order_sections_by_predefined_order
*/
class OrderSectionsByPredefinedOrderTest 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 $expected_order The sections in order we expect them to be.
*/
public function test_should_return_sections_in_predefined_order( array $sections, array $expected_order ) {
$this->assertSame(
$expected_order,
order_sections_by_predefined_order( $sections )
);
}

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