Skip to content

Commit

Permalink
Show settings link & API Key in Network Admin sites list (#583)
Browse files Browse the repository at this point in the history
  • Loading branch information
jblz committed Jan 25, 2022
1 parent ca700db commit 9c930ed
Show file tree
Hide file tree
Showing 6 changed files with 315 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ jobs:
if: ${{ matrix.php != 8.0 }}
run: composer testwp --no-interaction

- name: Run integration tests (multi site)
run: composer testwp-ms --no-interaction

- name: Run integration tests (multisite site with code coverage)
if: ${{ matrix.php == 8.0 }}
run: composer coveragewp-ci --no-interaction
125 changes: 125 additions & 0 deletions src/UI/class-network-admin-sites-list.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php
/**
* Parsely Network Admin Site List class
*
* @package Parsely
* @since 3.2.0
*/

declare(strict_types=1);

namespace Parsely\UI;

use Parsely\Parsely;
use WP_Site;

/**
* Render the additions to the WordPress Multisite Network Admin Sites List page
*
* @since 3.2.0
*/
final class Network_Admin_Sites_List {
const COLUMN_NAME = 'parsely-api-key';

/**
* Constructor.
*
* @param Parsely $parsely Instance of Parsely class.
*/
public function __construct( Parsely $parsely ) {
$this->parsely = $parsely;
}

/**
* Attach network admin page functionality to the appropriate action and filter hooks.
*
* @since 3.2.0
* @return void
*/
public function run(): void {
add_filter( 'manage_sites_action_links', array( __CLASS__, 'add_action_link' ), 10, 2 );
add_filter( 'wpmu_blogs_columns', array( __CLASS__, 'add_api_key_column' ) );
add_action( 'manage_sites_custom_column', array( $this, 'populate_api_key_column' ), 10, 2 );
}

/**
* Use the manage_sites_action_links filter to append a link to the settings page in the "row actions."
*
* @since 3.2.0
*
* @param array $actions The list of actions meant to be displayed for the current site's context in the row actions.
* @param int $_blog_id The blog ID for the current context.
* @return array The list of actions including ours.
*/
public static function add_action_link( array $actions, int $_blog_id ): array {
if ( ! current_user_can( Parsely::CAPABILITY ) ) {
return $actions;
}

$actions['parsely-settings'] = sprintf(
'<a href="%1$s" aria-label="%2$s">%3$s</a>',
esc_url( esc_url( Parsely::get_settings_url( $_blog_id ) ) ),
esc_attr( self::generate_aria_label_for_blog_id( $_blog_id ) ),
__( 'Parse.ly Settings', 'wp-parsely' )
);

return $actions;
}

/**
* Generate ARIA label content.
*
* @since 3.2.0
*
* @param int $_blog_id Which sub-site to include in the ARIA label.
* @return string ARIA label content including the blogname.
*/
private static function generate_aria_label_for_blog_id( int $_blog_id ): string {
$site = get_blog_details( $_blog_id );

return sprintf(
/* translators: blog name or blog id if empty */
__( 'Go to Parse.ly stats for "%s"', 'wp-parsely' ),
empty( $site->blogname ) ? $_blog_id : $site->blogname
);
}

/**
* Use the wpmu_blogs_columns filter to register the column where we'll display the site's API Key (if configured).
*
* @since 3.2.0
*
* @param array $sites_columns The list of columns meant to be displayed in the sites list table.
* @return array The list of columns to display in the network admin table including ours.
*/
public static function add_api_key_column( array $sites_columns ): array {
$sites_columns[ self::COLUMN_NAME ] = __( 'Parse.ly API Key', 'wp-parsely' );
return $sites_columns;
}

/**
* Use the manage_sites_custom_column action to output each site's API Key (if configured).
*
* @since 3.2.0
*
* @param string $column_name The column name for the current context.
* @param int $_blog_id The blog ID for the current context.
* @return void
*/
public function populate_api_key_column( string $column_name, int $_blog_id ): void {
if ( self::COLUMN_NAME !== $column_name ) {
return;
}

// phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.switch_to_blog_switch_to_blog
switch_to_blog( $_blog_id );
$apikey = $this->parsely->get_api_key();
restore_current_blog();

if ( strlen( $apikey ) > 0 ) {
echo esc_html( $apikey );
} else {
echo '<em>' . esc_html__( 'Parse.ly API key is missing', 'wp-parsely' ) . '</em>';
}
}
}
6 changes: 4 additions & 2 deletions src/class-parsely.php
Original file line number Diff line number Diff line change
Expand Up @@ -974,10 +974,12 @@ public function get_clean_parsely_page_value( ?string $val ): string {
/**
* Get the URL of the plugin settings page.
*
* @param int $_blog_id The Blog ID for the multisite subsite to use for context (Default null for current).
*
* @return string
*/
public static function get_settings_url(): string {
return admin_url( 'options-general.php?page=' . self::MENU_SLUG );
public static function get_settings_url( int $_blog_id = null ): string {
return get_admin_url( $_blog_id, 'options-general.php?page=' . self::MENU_SLUG );
}

/**
Expand Down
116 changes: 116 additions & 0 deletions tests/Integration/UI/NetworkAdminSitesListTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php
/**
* UI Tests for the Network Admin Sites List.
*
* @package Parsely
*/

declare(strict_types=1);

namespace Parsely\Tests\Integration\UI;

use Parsely\Parsely;
use Parsely\Tests\Integration\TestCase;
use Parsely\UI\Network_Admin_Sites_List;
use WP_Site;

/**
* UI Tests for the Network Admin Sites List.
*/
final class NetworkAdminSitesListTest extends TestCase {
/**
* Hold an insance of Network_Admin_Sites_List
*
* @var Network_Admin_Sites_List
*/
private static $sites_list;

/**
* Hold an instance of WP_MS_Sites_List_Table
*
* @var WP_MS_Sites_List_Table
*/
public $table = false;

/**
* Skip all tests for non-multisite runs.
* Set up an instance variable to hold a `WP_MS_Sites_List_Table` object.
*
* @return void
*/
public function set_up(): void {
parent::set_up();

if ( ! is_multisite() ) {
self::markTestSkipped();
}

$this->table = _get_list_table( 'WP_MS_Sites_List_Table', array( 'screen' => 'ms-sites' ) );
self::$sites_list = new Network_Admin_Sites_List( new Parsely() );
}

/**
* Make sure the custom column is included.
*
* @covers \Parsely\UI\Network_Admin_Sites_List::add_api_key_column
* @covers \Parsely\UI\Network_Admin_Sites_List::run
* @uses \Parsely\UI\Network_Admin_Sites_List::__construct
* @return void
*/
public function test_api_key_column_is_present(): void {
$columns = $this->table->get_columns();
self::assertArrayNotHasKey( 'parsely-api-key', $columns );

self::$sites_list->run();
$columns = $this->table->get_columns();

self::assertArrayHasKey( 'parsely-api-key', $columns );
self::assertSame( 'Parse.ly API Key', $columns['parsely-api-key'] );
}

/**
* Make sure the custom column is populated with default data for no option and the API key when set.
*
* @covers \Parsely\UI\Network_Admin_Sites_List::populate_api_key_column
* @covers \Parsely\UI\Network_Admin_Sites_List::run
* @uses \Parsely\Parsely::api_key_is_set
* @uses \Parsely\Parsely::get_api_key
* @uses \Parsely\Parsely::get_options
* @uses \Parsely\UI\Network_Admin_Sites_List::__construct
* @return void
*/
public function test_api_key_column_is_correctly_printed(): void {
$blog_id_with_api_key = $this->factory->blog->create();
$blog_id_without_api_key = $this->factory->blog->create();

self::$sites_list->run();

update_blog_option( $blog_id_with_api_key, Parsely::OPTIONS_KEY, array( 'apikey' => 'parselyrocks.example.com' ) );

$this->table->prepare_items();

self::assertCount( 3, $this->table->items, 'There should be the main site, the subsite with the apikey set, and a subsite without.' );

foreach ( $this->table->items as $site ) {
self::assertInstanceOf( WP_Site::class, $site );

ob_start();
$this->table->column_default( $site->to_array(), 'parsely-api-key' );
$api_key_col_value = ob_get_clean();

if ( $blog_id_with_api_key === (int) $site->blog_id ) {
self::assertSame(
'parselyrocks.example.com',
$api_key_col_value,
'The API key was not printed and should have been.'
);
} else {
self::assertSame(
'<em>Parse.ly API key is missing</em>',
$api_key_col_value,
'The default value was not printed and should have been.'
);
}
}
}
}
53 changes: 53 additions & 0 deletions tests/Integration/UI/SettingsPageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,57 @@ public function test_validate_duplicate_tracking_with_unexpected_array_order():
$actual = self::$settings_page->validate_options( $options );
self::assertSame( $expected, $actual );
}

/**
* Make sure that the settings URL is correctly returned for single sites and multisites with and without a blog_id param.
*
* @covers \Parsely\Parsely::get_settings_url
* @uses \Parsely\UI\Settings_Page::__construct
* @return void
*/
public function test_get_settings_url_with_and_without_blog_id(): void {
self::assertSame(
'http://example.org/wp-admin/options-general.php?page=parsely',
self::$parsely->get_settings_url(),
'The URL did not match the expected value without a $blog_id param.'
);

self::assertSame(
'http://example.org/wp-admin/options-general.php?page=parsely',
self::$parsely->get_settings_url( get_current_blog_id() ),
'The URL did not match the expected value with a $blog_id param.'
);

if ( ! is_multisite() ) {
return;
}

$subsite_blog_id = $this->factory->blog->create(
array(
'domain' => 'parselyrocks.example.org',
'path' => '/vipvipvip',
)
);

self::assertSame(
'http://parselyrocks.example.org/vipvipvip/wp-admin/options-general.php?page=parsely',
self::$parsely->get_settings_url( $subsite_blog_id ),
'The URL did not match when passing $subsite_blog_id.'
);

// phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.switch_to_blog_switch_to_blog
switch_to_blog( $subsite_blog_id );
self::assertSame(
'http://parselyrocks.example.org/vipvipvip/wp-admin/options-general.php?page=parsely',
self::$parsely->get_settings_url(),
'The URL did not match the subsite without passing a $blog_id param.'
);
restore_current_blog();

self::assertSame(
'http://example.org/wp-admin/options-general.php?page=parsely',
self::$parsely->get_settings_url(),
'The URL did not match the expected value for the main site with no $blog_id param after switching back.'
);
}
}
14 changes: 14 additions & 0 deletions wp-parsely.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
use Parsely\UI\Admin_Bar;
use Parsely\UI\Admin_Warning;
use Parsely\UI\Plugins_Actions;
use Parsely\UI\Network_Admin_Sites_List;
use Parsely\UI\Recommended_Widget;
use Parsely\UI\Row_Actions;
use Parsely\UI\Settings_Page;
Expand Down Expand Up @@ -104,6 +105,19 @@ function parsely_admin_menu_register(): void {
$settings_page->run();
}

require __DIR__ . '/src/UI/class-network-admin-sites-list.php';

add_action( 'admin_init', __NAMESPACE__ . '\\admin_init_network_sites_list' );
/**
* Register the additions the Multisite Network Admin Sites List table.
*
* @return void
*/
function admin_init_network_sites_list(): void {
$network_admin_sites_list = new Network_Admin_Sites_List( $GLOBALS['parsely'] );
$network_admin_sites_list->run();
}

require __DIR__ . '/src/UI/class-recommended-widget.php';

add_action( 'widgets_init', __NAMESPACE__ . '\\parsely_recommended_widget_register' );
Expand Down

0 comments on commit 9c930ed

Please sign in to comment.