Skip to content

Commit

Permalink
feat: add ymir_admin_notices filter to handle admin notices
Browse files Browse the repository at this point in the history
  • Loading branch information
carlalexander committed Sep 6, 2024
1 parent 20db281 commit 514679b
Show file tree
Hide file tree
Showing 7 changed files with 342 additions and 10 deletions.
23 changes: 19 additions & 4 deletions pluggable.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,34 @@
use Ymir\Plugin\Email\Email;
use Ymir\Plugin\Email\EmailClientInterface;
use Ymir\Plugin\Plugin;
use Ymir\Plugin\Support\Collection;

/**
* Pluggable functions used by the Ymir plugin.
*/
global $pagenow, $ymir;

if ($ymir->isSesEnabled() && function_exists('wp_mail') && !in_array($pagenow, ['plugins.php', 'update-core.php'], true)) {
add_action('admin_notices', function () {
echo '<div class="notice notice-warning"><p><strong>Ymir:</strong> Sending emails using SES is disabled because the "wp_mail" function was already overridden by another plugin.</p></div>';
add_filter('ymir_admin_notices', function ($notices) {
if ($notices instanceof Collection) {
$notices[] = [
'message' => 'Sending emails using SES is disabled because the "wp_mail" function was already overridden by another plugin.',
'type' => 'warning',
];
}

return $notices;
});
} elseif ($ymir->isSesEnabled() && $ymir->isUsingVanityDomain()) {
add_action('admin_notices', function () {
echo '<div class="notice notice-warning"><p><strong>Ymir:</strong> Sending emails using SES is disabled because the site is using a vanity domain. To learn how to map a domain to your environment, check out <a href="https://docs.ymirapp.com/guides/domain-mapping.html">this guide</a>.</p></div>';
add_filter('ymir_admin_notices', function ($notices) {
if ($notices instanceof Collection) {
$notices[] = [
'message' => 'Sending emails using SES is disabled because the site is using a vanity domain. To learn how to map a domain to your environment, check out <a href="https://docs.ymirapp.com/guides/domain-mapping.html">this guide</a>.',
'type' => 'warning',
];
}

return $notices;
});
} elseif ($ymir->isSesEnabled() && !$ymir->isUsingVanityDomain() && !function_exists('wp_mail')) {
/**
Expand Down
1 change: 1 addition & 0 deletions src/Configuration/EventManagementConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public function modify(Container $container)
$container['subscribers'] = $container->service(function (Container $container) {
$subscribers = [
// Ymir subscribers
new Subscriber\AdminSubscriber(),
new Subscriber\AssetsSubscriber($container['content_directory_name'], $container['site_url'], $container['assets_url'], $container['ymir_project_type'], $container['uploads_baseurl']),
new Subscriber\ContentDeliveryNetworkPageCachingSubscriber($container['cloudfront_client'], $container['rest_url'], $container['is_page_caching_disabled']),
new Subscriber\DisallowIndexingSubscriber($container['ymir_using_vanity_domain']),
Expand Down
60 changes: 60 additions & 0 deletions src/Subscriber/AdminSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

/*
* This file is part of Ymir WordPress plugin.
*
* (c) Carl Alexander <support@ymirapp.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Ymir\Plugin\Subscriber;

use Ymir\Plugin\EventManagement\AbstractEventManagerAwareSubscriber;
use Ymir\Plugin\Support\Collection;

/**
* Subscriber that handles interactions with the WordPress admin.
*/
class AdminSubscriber extends AbstractEventManagerAwareSubscriber
{
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array
{
return [
'admin_notices' => 'displayAdminNotices',
];
}

/**
* Display all admin notices.
*/
public function displayAdminNotices()
{
$notices = $this->eventManager->filter('ymir_admin_notices', new Collection());

if (!$notices instanceof Collection) {
return;
}

$notices->map(function ($notice) {
return is_string($notice) ? ['message' => $notice] : $notice;
})->filter(function ($notice) {
return is_array($notice) && !empty($notice['message']);
})->each(function (array $notice) {
$message = $notice['message'] ?? '';
$type = strtolower($notice['type'] ?? 'info');

if (!in_array($type, ['error', 'info', 'success', 'warning'])) {
$type = 'info';
}

printf('<div class="notice notice-%s %s"><p><strong>Ymir:</strong> %s</p></div>', $type, !empty($notice['dismissible']) ? 'is-dismissible' : '', $message);
});
}
}
14 changes: 9 additions & 5 deletions src/Subscriber/DisallowIndexingSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace Ymir\Plugin\Subscriber;

use Ymir\Plugin\EventManagement\SubscriberInterface;
use Ymir\Plugin\Support\Collection;

/**
* Subscriber for managing whether we allow indexing or not.
Expand Down Expand Up @@ -41,21 +42,24 @@ public function __construct(bool $usingVanityDomain)
public static function getSubscribedEvents(): array
{
return [
'admin_notices' => 'displayAdminNotice',
'ymir_admin_notices' => 'displayAdminNotice',
'pre_option_blog_public' => 'filterBlogPublic',
];
}

/**
* Display admin notice about search indexing being disabled.
*/
public function displayAdminNotice()
public function displayAdminNotice($notices)
{
if (!$this->usingVanityDomain) {
return;
if ($notices instanceof Collection && $this->usingVanityDomain) {
$notices[] = [
'message' => 'Search engine indexing is disallowed when using a vanity domain. To learn how to map a domain to your environment, check out <a href="https://docs.ymirapp.com/guides/domain-mapping.html">this guide</a>.',
'type' => 'warning',
];
}

echo '<div class="notice notice-warning"><p><strong>Ymir:</strong> Search engine indexing is disallowed when using a vanity domain. To learn how to map a domain to your environment, check out <a href="https://docs.ymirapp.com/guides/domain-mapping.html">this guide</a>.</p></div>';
return $notices;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Support/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
/**
* A collection offers a fluent interface for easier array manipulation.
*/
class Collection implements \ArrayAccess
class Collection implements \ArrayAccess, \Countable
{
/**
* The items contained in the collection.
Expand Down
232 changes: 232 additions & 0 deletions tests/Unit/Subscriber/AdminSubscriberTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
<?php

declare(strict_types=1);

/*
* This file is part of Ymir WordPress plugin.
*
* (c) Carl Alexander <support@ymirapp.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Ymir\Plugin\Tests\Unit\Subscriber;

use Ymir\Plugin\Subscriber\AdminSubscriber;
use Ymir\Plugin\Support\Collection;
use Ymir\Plugin\Tests\Mock\EventManagerMockTrait;
use Ymir\Plugin\Tests\Mock\FunctionMockTrait;
use Ymir\Plugin\Tests\Unit\TestCase;

/**
* @covers \Ymir\Plugin\Subscriber\AdminSubscriber
*/
class AdminSubscriberTest extends TestCase
{
use EventManagerMockTrait;
use FunctionMockTrait;

public function testDisplayAdminNoticesAddsIsDismissibleClassWhenDismissibleIsTrue()
{
$eventManager = $this->getEventManagerMock();
$notices = new Collection([
['message' => 'foo', 'type' => 'warning', 'dismissible' => true],
]);
$printf = $this->getFunctionMock($this->getNamespace(AdminSubscriber::class), 'printf');
$subscriber = new AdminSubscriber();

$eventManager->expects($this->once())
->method('filter')
->with('ymir_admin_notices', $this->isInstanceOf(Collection::class))
->willReturn($notices);

$printf->expects($this->once())
->with(
$this->identicalTo('<div class="notice notice-%s %s"><p><strong>Ymir:</strong> %s</p></div>'),
$this->identicalTo('warning'),
$this->identicalTo('is-dismissible'),
$this->identicalTo('foo')
);

$subscriber->setEventManager($eventManager);

$subscriber->displayAdminNotices();
}

public function testDisplayAdminNoticesConvertsStringToArray()
{
$eventManager = $this->getEventManagerMock();
$notices = new Collection('foo');
$printf = $this->getFunctionMock($this->getNamespace(AdminSubscriber::class), 'printf');
$subscriber = new AdminSubscriber();

$eventManager->expects($this->once())
->method('filter')
->with('ymir_admin_notices', $this->isInstanceOf(Collection::class))
->willReturn($notices);

$printf->expects($this->once())
->with(
$this->identicalTo('<div class="notice notice-%s %s"><p><strong>Ymir:</strong> %s</p></div>'),
$this->identicalTo('info'),
$this->identicalTo(''),
$this->identicalTo('foo')
);

$subscriber->setEventManager($eventManager);

$subscriber->displayAdminNotices();
}

public function testDisplayAdminNoticesDefaultsToInfoTypeWithInvalidType()
{
$eventManager = $this->getEventManagerMock();
$notices = new Collection([
['message' => 'foo', 'type' => 'bar'],
]);
$printf = $this->getFunctionMock($this->getNamespace(AdminSubscriber::class), 'printf');
$subscriber = new AdminSubscriber();

$eventManager->expects($this->once())
->method('filter')
->with('ymir_admin_notices', $this->isInstanceOf(Collection::class))
->willReturn($notices);

$printf->expects($this->once())
->with(
$this->identicalTo('<div class="notice notice-%s %s"><p><strong>Ymir:</strong> %s</p></div>'),
$this->identicalTo('info'),
$this->identicalTo(''),
$this->identicalTo('foo')
);

$subscriber->setEventManager($eventManager);

$subscriber->displayAdminNotices();
}

public function testDisplayAdminNoticesDoesNothingIfCollectionIsEmpty()
{
$eventManager = $this->getEventManagerMock();
$printf = $this->getFunctionMock(AdminSubscriber::class, 'printf');
$subscriber = new AdminSubscriber();

$eventManager->expects($this->once())
->method('filter')
->with('ymir_admin_notices', $this->isInstanceOf(Collection::class))
->willReturnArgument(1);

$printf->expects($this->never());

$subscriber->setEventManager($eventManager);

$subscriber->displayAdminNotices();
}

public function testDisplayAdminNoticesDoesNothingIfCollectionItemIsntAnArrayOrString()
{
$eventManager = $this->getEventManagerMock();
$notices = new Collection(1);
$printf = $this->getFunctionMock(AdminSubscriber::class, 'printf');
$subscriber = new AdminSubscriber();

$eventManager->expects($this->once())
->method('filter')
->with('ymir_admin_notices', $this->isInstanceOf(Collection::class))
->willReturn($notices);

$printf->expects($this->never());

$subscriber->setEventManager($eventManager);

$subscriber->displayAdminNotices();
}

public function testDisplayAdminNoticesDoesNothingIfFilterDoesntReturnCollection()
{
$eventManager = $this->getEventManagerMock();
$printf = $this->getFunctionMock(AdminSubscriber::class, 'printf');
$subscriber = new AdminSubscriber();

$eventManager->expects($this->once())
->method('filter')
->with('ymir_admin_notices', $this->isInstanceOf(Collection::class))
->willReturn(null);

$printf->expects($this->never());

$subscriber->setEventManager($eventManager);

$subscriber->displayAdminNotices();
}

public function testDisplayAdminNoticesDoesntAddIsDismissibleClassWhenDismissibleIsFalse()
{
$eventManager = $this->getEventManagerMock();
$notices = new Collection([
['message' => 'foo', 'type' => 'warning', 'dismissible' => false],
]);
$printf = $this->getFunctionMock($this->getNamespace(AdminSubscriber::class), 'printf');
$subscriber = new AdminSubscriber();

$eventManager->expects($this->once())
->method('filter')
->with('ymir_admin_notices', $this->isInstanceOf(Collection::class))
->willReturn($notices);

$printf->expects($this->once())
->with(
$this->identicalTo('<div class="notice notice-%s %s"><p><strong>Ymir:</strong> %s</p></div>'),
$this->identicalTo('warning'),
$this->identicalTo(''),
$this->identicalTo('foo')
);

$subscriber->setEventManager($eventManager);

$subscriber->displayAdminNotices();
}

public function testDisplayAdminNoticesUsesValidType()
{
$eventManager = $this->getEventManagerMock();
$notices = new Collection([
['message' => 'foo', 'type' => 'warning'],
]);
$printf = $this->getFunctionMock($this->getNamespace(AdminSubscriber::class), 'printf');
$subscriber = new AdminSubscriber();

$eventManager->expects($this->once())
->method('filter')
->with('ymir_admin_notices', $this->isInstanceOf(Collection::class))
->willReturn($notices);

$printf->expects($this->once())
->with(
$this->identicalTo('<div class="notice notice-%s %s"><p><strong>Ymir:</strong> %s</p></div>'),
$this->identicalTo('warning'),
$this->identicalTo(''),
$this->identicalTo('foo')
);

$subscriber->setEventManager($eventManager);

$subscriber->displayAdminNotices();
}

public function testGetSubscribedEvents()
{
$callbacks = AdminSubscriber::getSubscribedEvents();

foreach ($callbacks as $callback) {
$this->assertTrue(method_exists(AdminSubscriber::class, is_array($callback) ? $callback[0] : $callback));
}

$subscribedEvents = [
'admin_notices' => 'displayAdminNotices',
];

$this->assertSame($subscribedEvents, $callbacks);
}
}
Loading

0 comments on commit 514679b

Please sign in to comment.