Which AI-powered tools are actually worth using?
WORDPRESS
The Top 5 AI-Powered Tools for WordPress Creatives – WordPress.com News
![The Top 5 AI-Powered Tools for WordPress Creatives – WordPress.com News The Top 5 AI-Powered Tools for WordPress Creatives – WordPress.com News](https://articles.entireweb.com/wp-content/uploads/2024/05/The-Top-5-AI-Powered-Tools-for-WordPress-Creatives-–-WordPresscom.jpg)
While AI tools offer great potential to streamline our creative processes, their proliferation has made it hard to know which specific tools are useful and which are more showy than practical. In today’s Build and Beyond video, Jamie Marsland walks us through his five favorite AI-powered tools, including text-to-speech software, the best image generator on the market, and a great overview of Jetpack’s built-in AI Assistant.
Ready to get going? Click below to embark on your free trial today:
Here are AI-powered tools mentioned in the video:
![The Top 5 AI-Powered Tools for WordPress Creatives – WordPress.com News Fontjoy's home page, showing the UI of the AI-powered font-pairing tool.](https://articles.entireweb.com/wp-content/uploads/2024/05/1714780562_742_The-Top-5-AI-Powered-Tools-for-WordPress-Creatives-–-WordPresscom.png)
Fontjoy
Generate font combinations with the click of a button.
![The Top 5 AI-Powered Tools for WordPress Creatives – WordPress.com News Relume's wireframe generator, which works to give you a sitemap after entering a short website description.](https://articles.entireweb.com/wp-content/uploads/2024/05/1714780562_770_The-Top-5-AI-Powered-Tools-for-WordPress-Creatives-–-WordPresscom.png)
Relume
From site description to an optimized sitemap in seconds.
![The Top 5 AI-Powered Tools for WordPress Creatives – WordPress.com News A screenshot of how Adobe Firefly looks after you give it an image prompt.](https://articles.entireweb.com/wp-content/uploads/2024/05/1714780562_966_The-Top-5-AI-Powered-Tools-for-WordPress-Creatives-–-WordPresscom.png)
Adobe Firefly
The best, most powerful image generator on the market.
![The Top 5 AI-Powered Tools for WordPress Creatives – WordPress.com News Screenshot of Elevenlabs text-to-speech tool user experience.](https://articles.entireweb.com/wp-content/uploads/2024/05/1714780562_35_The-Top-5-AI-Powered-Tools-for-WordPress-Creatives-–-WordPresscom.png)
![The Top 5 AI-Powered Tools for WordPress Creatives – WordPress.com News Screenshot of the Jetpack AI Assistant user experience within the Site Editor of WordPress.com.](https://articles.entireweb.com/wp-content/uploads/2024/05/1714780562_912_The-Top-5-AI-Powered-Tools-for-WordPress-Creatives-–-WordPresscom.png)
Join 110.4M other subscribers
WORDPRESS
Do the Woo 4.0 – WordPress.com News
![Do the Woo 4.0 – WordPress.com News Do the Woo 4.0 – WordPress.com News](https://articles.entireweb.com/wp-content/uploads/2024/06/Do-the-Woo-40-–-WordPresscom-News.jpg)
Editor’s note: This is a guest post from Bob Dunn, founder of Do the Woo, a podcast channel elevating voices of the WooCommerce and WordPress community.
It was early 2023, and I’d been searching for the perfect direction for Do the Woo, the podcast and site we first launched in 2016. I’d made some small steps, but I hadn’t yet found the grand recipe. I just knew that with the shows changing format, the site needed to as well.
Finally, it came to me, as clear as day.
If I was going to do a major site redesign, I wanted it to be built on blocks. I wanted a full-site editor. And I wanted a hosting platform that would take care of things so I could focus on the content. That meant WordPress.com.
So that fall, around the time of WordCamp US, I sent Matt Mullenweg a message. Just as I’d shared ideas with Matt in the past, I did so now—except now was my vision for the next Do the Woo, and the platform I thought it should be built upon. Incredibly, he saw what I saw, and not long after a dream partnership formed: I began working with WordPress.com’s special projects team on the next version of Do the Woo.
Between then and now, a lot has happened. A lot of wireframes, a lot of discussion, a lot of iteration. The WordPress.com team has done incredible work throughout. During my visit to CloudFest in Germany, we even soft-launched the new site. But now it’s official. Today, on the first day of WordCamp Europe, I’m excited to announce Do the Woo 4.0: the podcast by WordPress and WooCommerce builder and business community, for the community.
There are so many things I could tell you about the new site, but it’s all right there on DotheWoo.io, so head over there to learn about our launch. And stay tuned—there’s lots of exciting stuff in the works.
This wouldn’t have happened without the folks from WordPress.com, WooCommerce, and Jetpack. I’m thrilled with what they’ve done with the site, and even more thrilled that my new home is on WordPress.com. I can’t wait for you to join our journey. Let’s do the Woo!
Join 111.3M other subscribers
WORDPRESS
How to Start a Dropshipping Business and Earn $150k/yr: A Step-by-Step Guide
![How to Start a Dropshipping Business and Earn $150k/yr: A Step-by-Step Guide How to Start a Dropshipping Business and Earn $150k/yr: A Step-by-Step Guide](https://articles.entireweb.com/wp-content/uploads/2024/06/How-to-Start-a-Dropshipping-Business-and-Earn-150kyr-A.jpg)
Exposing the Lies of Dropshipping Gurus: How to Start a Dropshipping Business the Right Way
Are you tired of watching countless videos promising to teach you how to start a dropshipping business and make a fortune overnight? Have you followed their advice, only to see your e-commerce store fail miserably? If so, you’re not alone. The truth is, many aspiring entrepreneurs have been misled by dropshipping gurus who claim to have the secret formula for success.
We strongly recommend that you check out our guide on how to take advantage of AI in today’s passive income economy.
Dropshipping gurus often showcase their impressive revenue figures, claiming to have made thousands of dollars in a single day. They promise that anyone can easily replicate their success and start a dropshipping business with minimal effort. However, what they fail to mention is that these results are usually short-lived and unsustainable.
The reality is that building a successful dropshipping business requires far more than just selecting a winning product, setting up a store, and launching a few ads. It involves careful planning, strategic marketing, and exceptional customer service. Unfortunately, most gurus focus solely on the initial stages of starting a dropshipping business, neglecting the crucial aspects that determine long-term success.
The Pitfalls of Following Guru Advice
When you start a dropshipping business based on the advice of gurus, you may experience a brief period of success. You might make a few sales and feel excited about the potential of your new venture. However, this initial success is often followed by a sudden drop in revenue, leaving you confused and frustrated.
The reason for this decline is simple: the strategies taught by most gurus are not designed for long-term sustainability. They focus on short-term tactics, such as running aggressive ad campaigns and testing countless products, without considering the importance of building a strong brand and providing excellent customer service.
The Importance of Customer Satisfaction
One of the most critical factors in running a successful dropshipping business is customer satisfaction. When you start a dropshipping business, your primary goal should be to provide an exceptional experience for your customers. This includes offering high-quality products, timely shipping, and responsive customer support.
Unfortunately, many dropshippers overlook the importance of customer satisfaction in their pursuit of quick profits. They focus solely on driving traffic to their store and making sales, neglecting the needs of their customers once the transaction is complete. As a result, they often face a high number of complaints, refunds, and chargebacks, which can quickly erode their profits and damage their reputation.
Building a Sustainable Dropshipping Business
To start a dropshipping business that stands the test of time, you need to shift your focus from short-term gains to long-term sustainability. This involves investing time and effort into building a strong foundation for your business, rather than chasing the latest trends or relying on questionable tactics.
One of the key elements of a sustainable dropshipping business is a well-defined niche. Instead of trying to sell a wide range of unrelated products, focus on a specific category that aligns with your interests and expertise. This will allow you to establish yourself as an authority in your niche, attract a loyal customer base, and differentiate yourself from competitors.
Another crucial aspect of building a sustainable dropshipping business is branding. Develop a unique brand identity that resonates with your target audience and sets you apart from other dropshippers. This includes creating a professional logo, designing an attractive website, and maintaining a consistent brand voice across all your marketing channels.
Prioritizing Customer Service
To ensure the long-term success of your dropshipping business, you must prioritize customer service. This means going above and beyond to meet the needs and expectations of your customers, even if it requires additional time and effort on your part.
Start by providing clear and detailed product descriptions, including accurate shipping times and return policies. Respond promptly to customer inquiries and complaints, and be proactive in addressing any issues that arise. Consider offering personalized thank-you notes, free gifts, or discounts to show your appreciation for your customers’ business.
By focusing on customer satisfaction, you’ll build a loyal customer base that will not only make repeat purchases but also recommend your business to others. This word-of-mouth marketing can be incredibly valuable in driving sustainable growth for your dropshipping business.
Automating Your Dropshipping Business
As your dropshipping business grows, you may find it challenging to keep up with the increasing demands of customer service and order fulfillment. This is where automation comes into play. By implementing automated systems and tools, you can streamline your operations, reduce manual labor, and focus on scaling your business.
One effective way to automate your dropshipping business is by using a reliable order fulfillment service. These services handle the entire process of storing, packing, and shipping your products, allowing you to focus on marketing and customer service. They also provide real-time tracking information, which can help reduce customer inquiries and improve the overall shopping experience.
Another area where automation can be beneficial is customer support. Implementing a chatbot or a comprehensive FAQ page can help address common customer questions and concerns, freeing up your time to handle more complex issues. You can also use email automation to send personalized order confirmations, shipping updates, and follow-up messages, keeping your customers informed and engaged throughout the buying process.
Starting a dropshipping business can be a lucrative and rewarding venture, but it requires a strategic approach and a long-term mindset. Instead of falling for the empty promises of dropshipping gurus, focus on building a sustainable and customer-centric business that can withstand the test of time.
Begin by carefully selecting your niche and products, ensuring that they align with your interests and target audience. Invest time in creating a strong brand identity and developing a professional website that showcases your unique value proposition. Prioritize customer satisfaction by providing exceptional service, responsive support, and high-quality products.
As you start a dropshipping business and your business grows, continuously monitor your performance and make data-driven decisions to optimize your operations. Implement automation tools and systems to streamline your processes and free up your time to focus on growth and expansion.
Conclusion
In conclusion, starting a successful dropshipping business requires more than just following the advice of self-proclaimed gurus. It demands a commitment to providing value to your customers, building a strong brand, and continuously adapting to the ever-changing e-commerce landscape.
By prioritizing customer satisfaction, focusing on long-term sustainability, and leveraging automation tools, you can start a dropshipping business that not only survives but thrives in the competitive online marketplace. Remember, success in dropshipping is not about chasing quick profits or replicating someone else’s tactics; it’s about creating a business that genuinely serves your customers and stands the test of time.
So, if you’re ready to start a dropshipping business the right way, ignore the hype and focus on the fundamentals. With dedication, hard work, and a customer-centric approach, you can build a profitable and rewarding dropshipping business that you can be proud of.
Frequently Asked Questions (FAQ)
How can a beginner start dropshipping?
A beginner can start a dropshipping business by following these steps:
- Choose a niche and research products that are in demand.
- Find reliable suppliers who offer dropshipping services.
- Create an e-commerce website using platforms like Shopify, WooCommerce, or Magento.
- List your products on your website and set competitive prices.
- Market your store through various channels, such as social media, paid advertising, and email marketing.
- Process orders and coordinate with your suppliers to ship products directly to your customers.
- Provide excellent customer service and continuously optimize your business based on performance data.
How profitable is dropshipping?
The profitability of dropshipping varies depending on several factors, such as your niche, product pricing, marketing strategies, and operational costs. Some dropshippers earn a few hundred dollars per month, while others generate six or even seven-figure incomes. To maximize your profitability, it’s essential to:
- Choose products with healthy profit margins.
- Negotiate favorable terms with your suppliers.
- Optimize your pricing strategy to remain competitive while ensuring profitability.
- Continuously monitor and reduce your operational costs.
- Invest in effective marketing campaigns to drive targeted traffic to your store.
How much do I need to start a dropshipping business?
The cost of starting a dropshipping business can vary greatly depending on your approach and the tools you choose to use. However, here are some general expenses to consider:
- E-commerce platform subscription (e.g., Shopify, WooCommerce): $29 to $299 per month.
- Domain name registration: $10 to $20 per year.
- Website hosting: $10 to $100 per month.
- Product sourcing and samples: $50 to $500, depending on your niche and product types.
- Marketing and advertising: $100 to $1,000 or more per month, depending on your strategies and target audience.
On average, you can expect to invest between $500 to $3,000 to start a basic dropshipping business. However, it’s possible to start with a smaller budget by opting for more affordable tools and focusing on organic marketing strategies.
Can you do dropshipping in Nigeria?
Yes, it is possible to start a dropshipping business in Nigeria. However, there are some challenges and considerations to keep in mind:
- Payment processing: Not all international payment gateways are available in Nigeria, so you may need to use local payment options like Paystack or Flutterwave.
- Shipping and logistics: Shipping times and costs may be higher when dropshipping to Nigeria, so it’s essential to find reliable suppliers and logistics partners who can handle international shipping.
- Import duties and taxes: Be aware of any import duties, taxes, or regulations that may apply to your products when shipping to Nigeria.
- Internet connectivity: Ensure that you have a stable internet connection to manage your online store and communicate with suppliers and customers.
- Market demand: Research the Nigerian market to identify products that are in demand and can be profitably dropshipped to the country.
Despite these challenges, dropshipping in Nigeria can be a viable business opportunity, especially if you focus on serving the local market and adapting your strategies to the specific needs and preferences of Nigerian consumers.
We strongly recommend that you check out our guide on how to take advantage of AI in today’s passive income economy.
WORDPRESS
Making a WordPress plugin extensible with PHP classes
![Making a WordPress plugin extensible with PHP classes Making a WordPress plugin extensible with PHP classes](https://articles.entireweb.com/wp-content/uploads/2024/06/Making-a-WordPress-plugin-extensible-with-PHP-classes.jpg)
WordPress plugins can be extended with additional functionality, as demonstrated by popular plugins like WooCommerce and Gravity Forms. In the article “Architecting a WordPress plugin to support extensions,” we learn there are two primary ways to make a WordPress plugin extensible:
- By setting up hooks (actions and filters) for extension plugins to inject their own functionality
- By providing PHP classes that extension plugins can inherit
The first method relies more on documentation, detailing available hooks and their usage. The second method, by contrast, offers ready-to-use code for extensions, reducing the need for extensive documentation. This is advantageous because creating documentation alongside code can complicate the plugin’s management and release.
Providing PHP classes directly effectively replaces documentation with code. Instead of teaching how to implement a feature, the plugin supplies the necessary PHP code, simplifying the task for third-party developers.
Let’s explore some techniques for achieving this, with the ultimate goal of fostering an ecosystem of integrations around our WordPress plugin.
Defining base PHP classes in the WordPress plugin
The WordPress plugin will include PHP classes intended for use by extension plugins. These PHP classes might not be used by the main plugin itself but are provided specifically for others to use.
Let’s see how this is implemented in the open-source Gato GraphQL plugin.
AbstractPlugin class:
AbstractPlugin
represents a plugin, both for the main Gato GraphQL plugin and its extensions:
abstract class AbstractPlugin implements PluginInterface
{
protected string $pluginBaseName;
protected string $pluginSlug;
protected string $pluginName;
public function __construct(
protected string $pluginFile,
protected string $pluginVersion,
?string $pluginName,
) {
$this->pluginBaseName = plugin_basename($pluginFile);
$this->pluginSlug = dirname($this->pluginBaseName);
$this->pluginName = $pluginName ?? $this->pluginBaseName;
}
public function getPluginName(): string
{
return $this->pluginName;
}
public function getPluginBaseName(): string
{
return $this->pluginBaseName;
}
public function getPluginSlug(): string
{
return $this->pluginSlug;
}
public function getPluginFile(): string
{
return $this->pluginFile;
}
public function getPluginVersion(): string
{
return $this->pluginVersion;
}
public function getPluginDir(): string
{
return dirname($this->pluginFile);
}
public function getPluginURL(): string
{
return plugin_dir_url($this->pluginFile);
}
// ...
}
AbstractMainPlugin class:
AbstractMainPlugin
extends AbstractPlugin
to represent the main plugin:
abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface
{
public function __construct(
string $pluginFile,
string $pluginVersion,
?string $pluginName,
protected MainPluginInitializationConfigurationInterface $pluginInitializationConfiguration,
) {
parent::__construct(
$pluginFile,
$pluginVersion,
$pluginName,
);
}
// ...
}
AbstractExtension class:
Similarly, AbstractExtension
extends AbstractPlugin
to represent an extension plugin:
abstract class AbstractExtension extends AbstractPlugin implements ExtensionInterface
{
public function __construct(
string $pluginFile,
string $pluginVersion,
?string $pluginName,
protected ?ExtensionInitializationConfigurationInterface $extensionInitializationConfiguration,
) {
parent::__construct(
$pluginFile,
$pluginVersion,
$pluginName,
);
}
// ...
}
Notice that AbstractExtension
is included within the main plugin, providing functionality to register and initialize an extension. However, it is only used by extensions, not by the main plugin itself.
The AbstractPlugin
class contains shared initialization code invoked at different times. These methods are defined at the ancestor level but are invoked by the inheriting classes according to their lifecycles.
The main plugin and extensions are initialized by executing the setup
method on the corresponding class, invoked from within the main WordPress plugin file.
For instance, in Gato GraphQL, this is done in gatographql.php
:
$pluginFile = __FILE__;
$pluginVersion = '2.4.0';
$pluginName = __('Gato GraphQL', 'gatographql');
PluginApp::getMainPluginManager()->register(new Plugin(
$pluginFile,
$pluginVersion,
$pluginName
))->setup();
setup method:
At the ancestor level, setup
contains the common logic between the plugin and its extensions, such as unregistering them when the plugin is deactivated. This method is not final; It can be overridden by the inheriting classes to add their functionality:
abstract class AbstractPlugin implements PluginInterface
{
// ...
public function setup(): void
{
register_deactivation_hook(
$this->getPluginFile(),
$this->deactivate(...)
);
}
public function deactivate(): void
{
$this->removePluginVersion();
}
private function removePluginVersion(): void
{
$pluginVersions = get_option('gatographql-plugin-versions', []);
unset($pluginVersions[$this->pluginBaseName]);
update_option('gatographql-plugin-versions', $pluginVersions);
}
}
Main plugin’s setup method:
The main plugin’s setup
method initializes the application’s lifecycle. It executes the main plugin’s functionality through methods like initialize
, configureComponents
, configure
, and boot
, and triggers corresponding action hooks for extensions:
abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface
{
public function setup(): void
{
parent::setup();
add_action('plugins_loaded', function (): void
{
// 1. Initialize main plugin
$this->initialize();
// 2. Initialize extensions
do_action('gatographql:initializeExtension');
// 3. Configure main plugin components
$this->configureComponents();
// 4. Configure extension components
do_action('gatographql:configureExtensionComponents');
// 5. Configure main plugin
$this->configure();
// 6. Configure extension
do_action('gatographql:configureExtension');
// 7. Boot main plugin
$this->boot();
// 8. Boot extension
do_action('gatographql:bootExtension');
}
// ...
}
// ...
}
Extension setup method:
The AbstractExtension
class executes its logic on the corresponding hooks:
abstract class AbstractExtension extends AbstractPlugin implements ExtensionInterface
{
// ...
final public function setup(): void
{
parent::setup();
add_action('plugins_loaded', function (): void
{
// 2. Initialize extensions
add_action(
'gatographql:initializeExtension',
$this->initialize(...)
);
// 4. Configure extension components
add_action(
'gatographql:configureExtensionComponents',
$this->configureComponents(...)
);
// 6. Configure extension
add_action(
'gatographql:configureExtension',
$this->configure(...)
);
// 8. Boot extension
add_action(
'gatographql:bootExtension',
$this->boot(...)
);
}, 20);
}
}
Methods initialize
, configureComponents
, configure
, and boot
are common to both the main plugin and extensions and may share logic. This shared logic is stored in the AbstractPlugin
class.
For example, the configure
method configures the plugin or extensions, calling callPluginInitializationConfiguration
, which has different implementations for the main plugin and extensions and is defined as abstract and getModuleClassConfiguration
, which provides a default behavior but can be overridden if needed:
abstract class AbstractPlugin implements PluginInterface
{
// ...
public function configure(): void
{
$this->callPluginInitializationConfiguration();
$appLoader = App::getAppLoader();
$appLoader->addModuleClassConfiguration($this->getModuleClassConfiguration());
}
abstract protected function callPluginInitializationConfiguration(): void;
/**
* @return array,mixed> [key]: Module class, [value]: Configuration
*/
public function getModuleClassConfiguration(): array
{
return [];
}
}
The main plugin provides its implementation for callPluginInitializationConfiguration
:
abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface
{
// ...
protected function callPluginInitializationConfiguration(): void
{
$this->pluginInitializationConfiguration->initialize();
}
}
Similarly, the extension class provides its implementation:
abstract class AbstractExtension extends AbstractPlugin implements ExtensionInterface
{
// ...
protected function callPluginInitializationConfiguration(): void
{
$this->extensionInitializationConfiguration?->initialize();
}
}
Methods initialize
, configureComponents
and boot
are defined at the ancestor level and can be overridden by inheriting classes:
abstract class AbstractPlugin implements PluginInterface
{
// ...
public function initialize(): void
{
$moduleClasses = $this->getModuleClassesToInitialize();
App::getAppLoader()->addModuleClassesToInitialize($moduleClasses);
}
/**
* @return array> List of `Module` class to initialize
*/
abstract protected function getModuleClassesToInitialize(): array;
public function configureComponents(): void
{
$classNamespace = ClassHelpers::getClassPSR4Namespace(get_called_class());
$moduleClass = $classNamespace . '\Module';
App::getModule($moduleClass)->setPluginFolder(dirname($this->pluginFile));
}
public function boot(): void
{
// By default, do nothing
}
}
All methods can be overridden by AbstractMainPlugin
or AbstractExtension
to extend them with their custom functionality.
For the main plugin, the setup
method also removes any caching from the WordPress instance when the plugin or any of its extensions is activated or deactivated:
abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface
{
public function setup(): void
{
parent::setup();
// ...
// Main-plugin specific methods
add_action(
'activate_plugin',
function (string $pluginFile): void {
$this->maybeRegenerateContainerWhenPluginActivatedOrDeactivated($pluginFile);
}
);
add_action(
'deactivate_plugin',
function (string $pluginFile): void {
$this->maybeRegenerateContainerWhenPluginActivatedOrDeactivated($pluginFile);
}
);
}
public function maybeRegenerateContainerWhenPluginActivatedOrDeactivated(string $pluginFile): void
{
// Removed code for simplicity
}
// ...
}
Similarly, the deactivate
method removes caching and boot
executes additional action hooks for the main plugin only:
abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface
{
public function deactivate(): void
{
parent::deactivate();
$this->removeTimestamps();
}
protected function removeTimestamps(): void
{
$userSettingsManager = UserSettingsManagerFacade::getInstance();
$userSettingsManager->removeTimestamps();
}
public function boot(): void
{
parent::boot();
add_filter(
'admin_body_class',
function (string $classes): string {
$extensions = PluginApp::getExtensionManager()->getExtensions();
$commercialExtensionActivatedLicenseObjectProperties = SettingsHelpers::getCommercialExtensionActivatedLicenseObjectProperties();
foreach ($extensions as $extension) {
$extensionCommercialExtensionActivatedLicenseObjectProperties = $commercialExtensionActivatedLicenseObjectProperties[$extension->getPluginSlug()] ?? null;
if ($extensionCommercialExtensionActivatedLicenseObjectProperties === null) {
continue;
}
return $classes . ' is-gatographql-customer';
}
return $classes;
}
);
}
}
From all the code presented above, it is clear that when designing and coding a WordPress plugin, we need to consider the needs of its extensions and reuse code across them as much as possible. Implementing sound Object-Oriented Programming patterns (such as the SOLID principles) helps achieve this, making the codebase maintainable for the long term.
Declaring and validating the version dependency
Since the extension inherits from a PHP class provided by the plugin, it’s crucial to validate that the required version of the plugin is present. Failing to do so could cause conflicts that bring the site down.
For example, if the AbstractExtension
class is updated with breaking changes and releases a major version 4.0.0
from the previous 3.4.0
, loading the extension without checking the version could result in a PHP error, preventing WordPress from loading.
To avoid this, the extension must validate that the installed plugin is version 3.x.x
. When version 4.0.0
is installed, the extension will be disabled, thus preventing errors.
The extension can accomplish this validation using the following logic, executed on the plugins_loaded
hook (since the main plugin will be loaded by then) in the extension’s main plugin file. This logic accesses the ExtensionManager
class, which is included in the main plugin to manage extensions:
/**
* Create and set-up the extension
*/
add_action(
'plugins_loaded',
function (): void {
/**
* Extension's name and version.
*
* Use a stability suffix as supported by Composer.
*/
$extensionVersion = '1.1.0';
$extensionName = __('Gato GraphQL - Extension Template');
/**
* The minimum version required from the Gato GraphQL plugin
* to activate the extension.
*/
$gatoGraphQLPluginVersionConstraint="^1.0";
/**
* Validate Gato GraphQL is active
*/
if (!class_exists(GatoGraphQLGatoGraphQLPlugin::class)) {
add_action('admin_notices', function () use ($extensionName) {
printf(
'',
sprintf(
__('Plugin %s is not installed or activated. Without it, plugin %s will not be loaded.'),
__('Gato GraphQL'),
$extensionName
)
);
});
return;
}
$extensionManager = GatoGraphQLGatoGraphQLPluginApp::getExtensionManager();
if (!$extensionManager->assertIsValid(
GatoGraphQLExtension::class,
$extensionVersion,
$extensionName,
$gatoGraphQLPluginVersionConstraint
)) {
return;
}
// Load Composer’s autoloader
require_once(__DIR__ . '/vendor/autoload.php');
// Create and set-up the extension instance
$extensionManager->register(new GatoGraphQLExtension(
__FILE__,
$extensionVersion,
$extensionName,
))->setup();
}
);
Notice how the extension declares a dependency on version constraint ^1.0
of the main plugin (using Composer’s version constraints). Thus, when version 2.0.0
of Gato GraphQL is installed, the extension will not be activated.
The version constraint is validated via the ExtensionManager::assertIsValid
method, which calls Semver::satisfies
(provided by the composer/semver
package):
use ComposerSemverSemver;
class ExtensionManager extends AbstractPluginManager
{
/**
* Validate that the required version of the Gato GraphQL for WP plugin is installed.
*
* If the assertion fails, it prints an error on the WP admin and returns false
*
* @param string|null $mainPluginVersionConstraint the semver version constraint required for the plugin (eg: "^1.0" means >=1.0.0 and getPlugin();
$mainPluginVersion = $mainPlugin->getPluginVersion();
if (
$mainPluginVersionConstraint !== null && !Semver::satisfies(
$mainPluginVersion,
$mainPluginVersionConstraint
)
) {
$this->printAdminNoticeErrorMessage(
sprintf(
__('Extension or bundle %s requires plugin %s to satisfy version constraint %s
, but the current version %s
does not. The extension or bundle has not been loaded.', 'gatographql'),
$extensionName ?? $extensionClass,
$mainPlugin->getPluginName(),
$mainPluginVersionConstraint,
$mainPlugin->getPluginVersion(),
)
);
return false;
}
return true;
}
protected function printAdminNoticeErrorMessage(string $errorMessage): void
{
add_action('admin_notices', function () use ($errorMessage): void {
$adminNotice_safe = sprintf(
'',
$errorMessage
);
echo $adminNotice_safe;
});
}
}
Running integration tests against a WordPress server
To make it easier for third-party developers to create extensions for your plugins, provide them with tools for development and testing, including workflows for their continuous integration and continuous delivery (CI/CD) processes.
During development, anyone can easily spin up a web server using DevKinsta, install the plugin for which they are coding the extension, and immediately validate that the extension is compatible with the plugin.
To automate testing during CI/CD, we need to have the web server accessible over a network to the CI/CD service. Services such as InstaWP can create a sandbox site with WordPress installed for this purpose.
If the extension’s codebase is hosted on GitHub, developers can use GitHub Actions to run integration tests against the InstaWP service. The following workflow installs the extension on an InstaWP sandbox site (alongside the latest stable version of the main plugin) and then runs the integration tests:
name: Integration tests (InstaWP)
on:
workflow_run:
workflows: [Generate plugins]
types:
- completed
jobs:
provide_data:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
name: Retrieve the GitHub Action artifact URLs to install in InstaWP
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: 8.1
coverage: none
env:
COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: "ramsey/composer-install@v2"
- name: Retrieve artifact URLs from GitHub workflow
uses: actions/github-script@v6
id: artifact-url
with:
script: |
const allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
const artifactURLs = allArtifacts.data.artifacts.map((artifact) => {
return artifact.url.replace('https://api.github.com/repos', 'https://nightly.link') + '.zip'
}).concat([
"https://downloads.wordpress.org/plugin/gatographql.latest-stable.zip"
]);
return artifactURLs.join(',');
result-encoding: string
- name: Artifact URL for InstaWP
run: echo "Artifact URL for InstaWP - ${{ steps.artifact-url.outputs.result }}"
shell: bash
outputs:
artifact_url: ${{ steps.artifact-url.outputs.result }}
process:
needs: provide_data
name: Launch InstaWP site from template 'integration-tests' and execute integration tests against it
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: 8.1
coverage: none
env:
COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: "ramsey/composer-install@v2"
- name: Create InstaWP instance
uses: instawp/wordpress-testing-automation@main
id: create-instawp
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INSTAWP_TOKEN: ${{ secrets.INSTAWP_TOKEN }}
INSTAWP_TEMPLATE_SLUG: "integration-tests"
REPO_ID: 25
INSTAWP_ACTION: create-site-template
ARTIFACT_URL: ${{ needs.provide_data.outputs.artifact_url }}
- name: InstaWP instance URL
run: echo "InstaWP instance URL - ${{ steps.create-instawp.outputs.instawp_url }}"
shell: bash
- name: Extract InstaWP domain
id: extract-instawp-domain
run: |
instawp_domain="$(echo "${{ steps.create-instawp.outputs.instawp_url }}" | sed -e s#https://##)"
echo "instawp-domain=$(echo $instawp_domain)" >> $GITHUB_OUTPUT
- name: Run tests
run: |
INTEGRATION_TESTS_WEBSERVER_DOMAIN=${{ steps.extract-instawp-domain.outputs.instawp-domain }}
INTEGRATION_TESTS_AUTHENTICATED_ADMIN_USER_USERNAME=${{ steps.create-instawp.outputs.iwp_wp_username }}
INTEGRATION_TESTS_AUTHENTICATED_ADMIN_USER_PASSWORD=${{ steps.create-instawp.outputs.iwp_wp_password }}
vendor/bin/phpunit --filter=Integration
- name: Destroy InstaWP instance
uses: instawp/wordpress-testing-automation@main
id: destroy-instawp
if: ${{ always() }}
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INSTAWP_TOKEN: ${{ secrets.INSTAWP_TOKEN }}
INSTAWP_TEMPLATE_SLUG: "integration-tests"
REPO_ID: 25
INSTAWP_ACTION: destroy-site
This workflow accesses the .zip file via Nightly Link, a service that allows accessing an artifact from GitHub without logging in, simplifying the configuration of InstaWP.
Releasing the extension plugin
We can provide tools to help release the extensions, automating the procedures as much as possible.
The Monorepo Builder is a library for managing any PHP project, including a WordPress plugin. It provides the monorepo-builder release
command to release a version of the project, incrementing either the major, minor, or patch component of the version according to semantic versioning.
This command executes a series of release workers, which are PHP classes that execute certain logic. The default workers include one that creates a git tag
with the new version and another that pushes the tag to the remote repository. Custom workers can be injected before, after, or in between these steps.
The release workers are configured via a configuration file:
use SymplifyMonorepoBuilderConfigMBConfig;
use SymplifyMonorepoBuilderReleaseReleaseWorkerAddTagToChangelogReleaseWorker;
use SymplifyMonorepoBuilderReleaseReleaseWorkerPushNextDevReleaseWorker;
use SymplifyMonorepoBuilderReleaseReleaseWorkerPushTagReleaseWorker;
use SymplifyMonorepoBuilderReleaseReleaseWorkerSetCurrentMutualDependenciesReleaseWorker;
use SymplifyMonorepoBuilderReleaseReleaseWorkerSetNextMutualDependenciesReleaseWorker;
use SymplifyMonorepoBuilderReleaseReleaseWorkerTagVersionReleaseWorker;
use SymplifyMonorepoBuilderReleaseReleaseWorkerUpdateBranchAliasReleaseWorker;
use SymplifyMonorepoBuilderReleaseReleaseWorkerUpdateReplaceReleaseWorker;
return static function (MBConfig $mbConfig): void {
// release workers - in order to execute
$mbConfig->workers([
UpdateReplaceReleaseWorker::class,
SetCurrentMutualDependenciesReleaseWorker::class,
AddTagToChangelogReleaseWorker::class,
TagVersionReleaseWorker::class,
PushTagReleaseWorker::class,
SetNextMutualDependenciesReleaseWorker::class,
UpdateBranchAliasReleaseWorker::class,
PushNextDevReleaseWorker::class,
]);
};
We can provide custom release workers to augment the release process tailored to the needs of a WordPress plugin. For example, the InjectStableTagVersionInPluginReadmeFileReleaseWorker
sets the new version as the “Stable tag” entry in the extension’s readme.txt file:
use NetteUtilsStrings;
use PharIoVersionVersion;
use SymplifySmartFileSystemSmartFileInfo;
use SymplifySmartFileSystemSmartFileSystem;
class InjectStableTagVersionInPluginReadmeFileReleaseWorker implements ReleaseWorkerInterface
{
public function __construct(
// This class is provided by the Monorepo Builder
private SmartFileSystem $smartFileSystem,
) {
}
public function getDescription(Version $version): string
{
return 'Have the "Stable tag" point to the new version in the plugin's readme.txt file';
}
public function work(Version $version): void
{
$replacements = [
'/Stable tag:s+[a-z0-9.-]+/' => 'Stable tag: ' . $version->getVersionString(),
];
$this->replaceContentInFiles(['/readme.txt'], $replacements);
}
/**
* @param string[] $files
* @param array $regexPatternReplacements regex pattern to search, and its replacement
*/
protected function replaceContentInFiles(array $files, array $regexPatternReplacements): void
{
foreach ($files as $file) {
$fileContent = $this->smartFileSystem->readFile($file);
foreach ($regexPatternReplacements as $regexPattern => $replacement) {
$fileContent = Strings::replace($fileContent, $regexPattern, $replacement);
}
$this->smartFileSystem->dumpFile($file, $fileContent);
}
}
}
By adding InjectStableTagVersionInPluginReadmeFileReleaseWorker
to the configuration list, whenever executing the monorepo-builder release
command to release a new version of the plugin, the “Stable tag” in the extension’s readme.txt file will be automatically updated.
Publishing the extension plugin to the WP.org directory
We can also distribute a workflow to help release the extension to the WordPress Plugin Directory. When tagging the project on the remote repository, the following workflow will publish the WordPress extension plugin to the directory:
# See: https://github.com/10up/action-wordpress-plugin-deploy#deploy-on-pushing-a-new-tag
name: Deploy to WordPress.org Plugin Directory (SVN)
on:
push:
tags:
- "*"
jobs:
tag:
name: New tag
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: WordPress Plugin Deploy
uses: 10up/action-wordpress-plugin-deploy@stable
env:
SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
SLUG: ${{ secrets.SLUG }}
This workflow uses the 10up/action-wordpress-plugin-deploy
action, which retrieves the code from a Git repository and pushes it to the WordPress.org SVN repository, simplifying the operation.
Summary
When creating an extensible plugin for WordPress, our goal is to make it as easy as possible for third-party developers to extend it, thereby maximizing the chances of fostering a vibrant ecosystem around our plugins.
While providing extensive documentation can guide developers on how to extend the plugin, an even more effective approach is to supply the necessary PHP code and tooling for development, testing, and releasing their extensions.
By including the additional code needed by extensions directly in our plugin, we simplify the process for developers.
Do you plan to make your WordPress plugin extensible? Let us know in the comments section.
-
SEO7 days ago
Google On How It Manages Disclosure Of Search Incidents
-
SEO6 days ago
8 Ways To Promote Your Facebook Page Successfully
-
SEARCHENGINES5 days ago
Daily Search Forum Recap: June 10, 2024
-
SEARCHENGINES6 days ago
Google Search Ranking Algorithm Volatility On June 8th
-
SEO5 days ago
Google’s Statement About CTR And HCU
-
WORDPRESS5 days ago
Say Hello to the Hosting Dashboard – WordPress.com News
-
SEO4 days ago
How to Persuade Your Boss to Send You to Ahrefs Evolve
-
WORDPRESS4 days ago
A Small Business Guide to Building An E-Commerce Website | CO