Easy methods to make a WordPress plugin extensible with PHP categories

by | Jun 10, 2024 | Etcetera | 0 comments

WordPress plugins will also be extended with additional capacity, as demonstrated by way of standard plugins like WooCommerce and Gravity Forms. Inside the article “Architecting a WordPress plugin to strengthen extensions,” we learn there are two primary techniques to make a WordPress plugin extensible:

  1. By means of setting up hooks (actions and filters) for extension plugins to inject their own capacity
  2. By means of providing PHP classes that extension plugins can inherit

The main manner is primarily based further on documentation, detailing available hooks and their usage. The second manner, by contrast, provides ready-to-use code for extensions, reducing the will for intensive documentation. This is prime quality because of rising documentation alongside code can complicate the plugin’s regulate and release.

Providing PHP classes in an instant effectively replaces documentation with code. Instead of teaching learn to implement a function, the plugin supplies the essential PHP code, simplifying the obligation for third-party developers.

Let’s uncover some techniques for achieving this, with the ultimate objective of fostering an ecosystem of integrations spherical our WordPress plugin.

Defining base PHP classes inside the WordPress plugin

The WordPress plugin will include PHP classes intended for use by way of extension plugins. The ones PHP classes will not be used by the principle plugin itself on the other hand are provided particularly for others to use.

Let’s see how this is performed inside the open-source Gato GraphQL plugin.

AbstractPlugin class:

AbstractPlugin represents a plugin, each and every for the principle Gato GraphQL plugin and its extensions:

abstract class AbstractPlugin implements PluginInterface
{
  safe string $pluginBaseName;
  safe string $pluginSlug;
  safe string $pluginName;

  public function __construct(
    safe string $pluginFile,
    safe 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 principle plugin:

abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface
{
  public function __construct(
    string $pluginFile,
    string $pluginVersion,
    ?string $pluginName,
    safe MainPluginInitializationConfigurationInterface $pluginInitializationConfiguration,
  ) {
    father or mother::__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,
    safe ?ExtensionInitializationConfigurationInterface $extensionInitializationConfiguration,
  ) {
    father or mother::__construct(
      $pluginFile,
      $pluginVersion,
      $pluginName,
    );
  }

  // ...
}

Notice that AbstractExtension is included within the principle plugin, providing capacity to check in and initialize an extension. Then again, it’s only used by extensions, no longer by way of the principle plugin itself.

The AbstractPlugin class accommodates shared initialization code invoked at different circumstances. The ones methods are defined at the ancestor level on the other hand are invoked by way of the inheriting classes in keeping with their lifecycles.

The main plugin and extensions are initialized by way of executing the setup manner on the corresponding class, invoked from within the principle WordPress plugin file.

For example, in Gato GraphQL, this is completed in gatographql.php:

$pluginFile = __FILE__;
$pluginVersion = '2.4.0';
$pluginName = __('Gato GraphQL', 'gatographql');
PluginApp::getMainPluginManager()->check in(new Plugin(
  $pluginFile,
  $pluginVersion,
  $pluginName
))->setup();

setup manner:

At the ancestor level, setup accommodates the common not unusual sense between the plugin and its extensions, similar to unregistering them when the plugin is deactivated. This system isn’t final; It can be overridden by way of the inheriting classes in an effort to upload their capacity:

abstract class AbstractPlugin implements PluginInterface
{
  // ...

  public function setup(): void
  {
    register_deactivation_hook(
      $this->getPluginFile(),
      $this->deactivate(...)
    );
  }

  public function deactivate(): void
  {
    $this->removePluginVersion();
  }

  non-public function removePluginVersion(): void
  {
    $pluginVersions = get_option('gatographql-plugin-versions', []);
    unset($pluginVersions[$this->pluginBaseName]);
    update_option('gatographql-plugin-versions', $pluginVersions);
  }
}

Number one plugin’s setup manner:

The main plugin’s setup manner initializes the applying’s lifecycle. It executes the principle plugin’s capacity through methods like initialize, configureComponents, configure, and boot, and triggers corresponding movement hooks for extensions:

abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface
{
  public function setup(): void
  {
    father or mother::setup();

    add_action('plugins_loaded', function (): void
    {
      // 1. Initialize number one plugin
      $this->initialize();

      // 2. Initialize extensions
      do_action('gatographql:initializeExtension');

      // 3. Configure number one plugin portions
      $this->configureComponents();

      // 4. Configure extension portions
      do_action('gatographql:configureExtensionComponents');

      // 5. Configure number one plugin
      $this->configure();

      // 6. Configure extension
      do_action('gatographql:configureExtension');

      // 7. Boot number one plugin
      $this->boot();

      // 8. Boot extension
      do_action('gatographql:bootExtension');
    }

    // ...
  }
  
  // ...
}

Extension setup manner:

The AbstractExtension class executes its not unusual sense on the corresponding hooks:

abstract class AbstractExtension extends AbstractPlugin implements ExtensionInterface
{
  // ...

  final public function setup(): void
  {
    father or mother::setup();

    add_action('plugins_loaded', function (): void
    {
      // 2. Initialize extensions
      add_action(
        'gatographql:initializeExtension',
        $this->initialize(...)
      );

      // 4. Configure extension portions
      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 don’t seem to be atypical to each and every the principle plugin and extensions and would perhaps percentage not unusual sense. This shared not unusual sense is stored inside the AbstractPlugin class.

See also  Tips on how to Spoof Monster Hunter Now on iPhone (2024)

As an example, the configure manner configures the plugin or extensions, calling callPluginInitializationConfiguration, which has different implementations for the principle plugin and extensions and is printed as abstract and getModuleClassConfiguration, which provides a default habits on the other hand will also be overridden if sought after:

abstract class AbstractPlugin implements PluginInterface
{
  // ...

  public function configure(): void
  {
    $this->callPluginInitializationConfiguration();

    $appLoader = App::getAppLoader();
    $appLoader->addModuleClassConfiguration($this->getModuleClassConfiguration());
  }

  abstract safe function callPluginInitializationConfiguration(): void;

  /**
   * @return array<class-string,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
{
  // ...

  safe function callPluginInitializationConfiguration(): void
  {
    $this->pluginInitializationConfiguration->initialize();
  }
}

Similarly, the extension class provides its implementation:

abstract class AbstractExtension extends AbstractPlugin implements ExtensionInterface
{
  // ...

  safe function callPluginInitializationConfiguration(): void
  {
    $this->extensionInitializationConfiguration?->initialize();
  }
}

Methods initialize, configureComponents and boot are defined at the ancestor level and will also be overridden by way of inheriting classes:

abstract class AbstractPlugin implements PluginInterface
{
  // ...

  public function initialize(): void
  {
    $moduleClasses = $this->getModuleClassesToInitialize();
    App::getAppLoader()->addModuleClassesToInitialize($moduleClasses);
  }

  /**
   * @return array<class-string> Report of `Module` class to initialize
   */
  abstract safe 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 means of default, don't the rest
  }
}

All methods will also be overridden by way of AbstractMainPlugin or AbstractExtension to extend them with their custom designed capacity.

For the principle plugin, the setup manner moreover 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
  {
    father or mother::setup();

    // ...

    // Number one-plugin particular 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 manner removes caching and boot executes additional movement hooks for the principle plugin most efficient:

abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface
{
  public function deactivate(): void
  {
    father or mother::deactivate();

    $this->removeTimestamps();
  }

  safe function removeTimestamps(): void
  {
    $userSettingsManager = UserSettingsManagerFacade::getInstance();
    $userSettingsManager->removeTimestamps();
  }

  public function boot(): void
  {
    father or mother::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 the entire code presented above, it’s clear that once designing and coding a WordPress plugin, we want to consider the desires of its extensions and reuse code right through them as much as possible. Enforcing sound Object-Orientated Programming patterns (such since the SOLID laws) helps achieve this, making the codebase maintainable for the longer term.

Citing and validating the version dependency

Since the extension inherits from a PHP class provided by way of the plugin, it’s crucial to validate that the required version of the plugin is supply. Failing to do so might objective conflicts that ship the internet website down.

As an example, if the AbstractExtension class is up to the moment with breaking changes and releases a large version 4.0.0 from the previous 3.4.0, loading the extension without checking the version might result in a PHP error, preventing WordPress from loading.

To avoid this, the extension will have to validate that the installed plugin is version 3.x.x. When version 4.0.0 is installed, the extension may also be disabled, thus preventing errors.

The extension can accomplish this validation the usage of the following not unusual sense, achieved on the plugins_loaded hook (given that number one plugin may also be loaded by way of then) inside the extension’s primary plugin record. This not unusual sense accesses the ExtensionManager class, which is included in the principle plugin to control extensions:

/**
 * Create and set-up the extension
 */
add_action(
  'plugins_loaded',
  function (): void {
    /**
     * Extension's identify and version.
     *
     * Use a stability suffix as supported by way of Composer.
     */
    $extensionVersion = '1.1.0';
    $extensionName = __('Gato GraphQL - Extension Template');

    /**
     * The minimum version required from the Gato GraphQL plugin
     * to show at the extension.
     */
    $gatoGraphQLPluginVersionConstraint = '^1.0';
    
    /**
     * Validate Gato GraphQL is lively
     */
    if (!class_exists(GatoGraphQLGatoGraphQLPlugin::class)) {
      add_action('admin_notices', function () use ($extensionName) {
        printf(
          '

%s

', 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->check in(new GatoGraphQLExtension( __FILE__, $extensionVersion, $extensionName, ))->setup(); } );

Notice how the extension broadcasts a dependency on version constraint ^1.0 of the principle plugin (the usage of Composer’s edition constraints). Thus, when version 2.0.0 of Gato GraphQL is installed, the extension will not be activated.

See also  Simplified Review: Features, Pricing & More (2024)

The version constraint is validated by way of the ExtensionManager::assertIsValid manner, which calls Semver::satisfies (provided by way of the composer/semver bundle):

use ComposerSemverSemver;

class ExtensionManager extends AbstractPluginManager
{
  /**
   * Validate that the required version of the Gato GraphQL for WP plugin is installed.
   *
   * If the remark 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 deal %s requires plugin %s to meet version constraint %s, on the other hand the prevailing version %s does no longer. The extension or bundle deal has no longer been loaded.', 'gatographql'),
          $extensionName ?? $extensionClass,
          $mainPlugin->getPluginName(),
          $mainPluginVersionConstraint,
          $mainPlugin->getPluginVersion(),
        )
      );
      return false;
    }

    return true;
  }

  safe function printAdminNoticeErrorMessage(string $errorMessage): void
  {
    add_action('admin_notices', function () use ($errorMessage): void {
      $adminNotice_safe = sprintf(
        '

%s

', $errorMessage ); echo $adminNotice_safe; }); } }

Operating integration exams against a WordPress server

To make it more uncomplicated for third-party developers to create extensions for your plugins, provide them with apparatus for development and checking out, along side workflows for their steady integration and steady supply (CI/CD) processes.

During development, anyone can merely spin up a web server the usage of DevKinsta, arrange the plugin for which they’re coding the extension, and immediately validate that the extension is acceptable with the plugin.

To automate checking out all over CI/CD, we want to have the web server to be had over a group to the CI/CD supplier. Services similar to InstaWP can create a sandbox internet website with WordPress installed for this serve as.

If the extension’s codebase is hosted on GitHub, developers can use GitHub Movements to run integration exams against the InstaWP supplier. The following workflow installs the extension on an InstaWP sandbox internet website (alongside the latest robust version of the principle plugin) and then runs the mixing checks:

identify: Integration exams (InstaWP)
on:
  workflow_run:
    workflows: [Generate plugins]
    types:
      - completed

jobs:
  provide_data:
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    identify: Retrieve the GitHub Movement artifact URLs to position in 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 and techniques and strategies.GITHUB_TOKEN }}

      - uses: "ramsey/composer-install@v2"

      - identify: Retrieve artifact URLs from GitHub workflow
        uses: actions/github-script@v6
        identity: artifact-url
        with:
          script: |
            const allArtifacts = stay up for github.rest.actions.listWorkflowRunArtifacts({
              owner: context.repo.owner,
              repo: context.repo.repo,
              run_id: context.payload.workflow_run.identity,
            });
            const artifactURLs = allArtifacts.data.artifacts.map((artifact) => {
              return artifact.url.exchange('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

      - identify: Artifact URL for InstaWP
        run: echo "Artifact URL for InstaWP - ${{ steps.artifact-url.outputs.end result }}"
        shell: bash

    outputs:
      artifact_url: ${{ steps.artifact-url.outputs.end result }}

  process:
    needs: provide_data
    identify: Unlock InstaWP internet website from template 'integration-tests' and execute integration exams 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 and techniques and strategies.GITHUB_TOKEN }}

      - uses: "ramsey/composer-install@v2"

      - identify: Create InstaWP instance
        uses: instawp/wordpress-testing-automation@number one
        identity: create-instawp
        with:
          GITHUB_TOKEN: ${{ secrets and techniques and strategies.GITHUB_TOKEN }}
          INSTAWP_TOKEN: ${{ secrets and techniques and strategies.INSTAWP_TOKEN }}
          INSTAWP_TEMPLATE_SLUG: "integration-tests"
          REPO_ID: 25
          INSTAWP_ACTION: create-site-template
          ARTIFACT_URL: ${{ needs.provide_data.outputs.artifact_url }}

      - identify: InstaWP instance URL
        run: echo "InstaWP instance URL - ${{ steps.create-instawp.outputs.instawp_url }}"
        shell: bash

      - identify: Extract InstaWP space
        identity: 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

      - identify: Run exams
        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

      - identify: Destroy InstaWP instance
        uses: instawp/wordpress-testing-automation@number one
        identity: destroy-instawp
        if: ${{ always() }}
        with:
          GITHUB_TOKEN: ${{ secrets and techniques and strategies.GITHUB_TOKEN }}
          INSTAWP_TOKEN: ${{ secrets and techniques and strategies.INSTAWP_TOKEN }}
          INSTAWP_TEMPLATE_SLUG: "integration-tests"
          REPO_ID: 25
          INSTAWP_ACTION: destroy-site

This workflow accesses the .zip file by way of Nightly Hyperlink, a supplier that allows having access to an artifact from GitHub without logging in, simplifying the configuration of InstaWP.

See also  How you can Setup WordPress E mail Logs (and WooCommerce E mail Logs)

Releasing the extension plugin

We will be able to provide apparatus to have the same opinion release the extensions, automating the procedures as much as possible.

The Monorepo Builder is a library for managing any PHP project, along side a WordPress plugin. It provides the monorepo-builder release command to disencumber a version of the project, incrementing each the most important, minor, or patch a part of the version in keeping with semantic versioning.

This command executes a chain of free up employees, which may also be PHP classes that execute positive not unusual sense. The default workers include one who creates a git tag with the new version and each and every different that pushes the tag to the a long way flung repository. Custom designed workers will also be injected faster than, after, or in between the ones steps.

The release workers are configured by way of 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 an effort to execute
  $mbConfig->workers([
    UpdateReplaceReleaseWorker::class,
    SetCurrentMutualDependenciesReleaseWorker::class,
    AddTagToChangelogReleaseWorker::class,
    TagVersionReleaseWorker::class,
    PushTagReleaseWorker::class,
    SetNextMutualDependenciesReleaseWorker::class,
    UpdateBranchAliasReleaseWorker::class,
    PushNextDevReleaseWorker::class,
  ]);
};

We will be able to provide custom designed release workers to beef up the release process tailored to the desires of a WordPress plugin. As an example, the InjectStableTagVersionInPluginReadmeFileReleaseWorker gadgets the new version since the “Robust tag” get entry to inside the extension’s readme.txt record:

use NetteUtilsStrings;
use PharIoVersionVersion;
use SymplifySmartFileSystemSmartFileInfo;
use SymplifySmartFileSystemSmartFileSystem;

class InjectStableTagVersionInPluginReadmeFileReleaseWorker implements ReleaseWorkerInterface
{
  public function __construct(
    // This class is provided by way of the Monorepo Builder
    non-public SmartFileSystem $smartFileSystem,
  ) {
  }

  public function getDescription(Style $version): string
  {
    return 'Have the "Robust tag" degree to the new version inside the plugin's readme.txt file';
  }

  public function artwork(Style $version): void
  {
    $replacements = [
      '/Stable tag:s+[a-z0-9.-]+/' => 'Robust tag: ' . $version->getVersionString(),
    ];
    $this->replaceContentInFiles(['/readme.txt'], $replacements);
  }

  /**
   * @param string[] $data
   * @param array $regexPatternReplacements regex pattern to appear, and its exchange
   */
  safe function replaceContentInFiles(array $data, array $regexPatternReplacements): void
  {
    foreach ($data as $file) {
      $fileContent = $this->smartFileSystem->readFile($file);
      foreach ($regexPatternReplacements as $regexPattern => $exchange) {
        $fileContent = Strings::exchange($fileContent, $regexPattern, $exchange);
      }
      $this->smartFileSystem->dumpFile($file, $fileContent);
    }
  }
}

By means of together with InjectStableTagVersionInPluginReadmeFileReleaseWorker to the configuration file, each and every time executing the monorepo-builder release command to disencumber a brand spanking new version of the plugin, the “Robust tag” inside the extension’s readme.txt file may also be mechanically up to the moment.

Publishing the extension plugin to the WP.org list

We will be able to moreover distribute a workflow to have the same opinion release the extension to the WordPress Plugin Listing. When tagging the project on the a long way flung repository, the following workflow will post the WordPress extension plugin to the list:

# See: https://github.com/10up/action-wordpress-plugin-deploy#deploy-on-pushing-a-new-tag
identify: Deploy to WordPress.org Plugin Record (SVN)
on:
  push:
  tags:
  - "*"

jobs:
  tag:
  identify: New tag
  runs-on: ubuntu-latest
  steps:
  - uses: actions/checkout@clutch
  - identify: WordPress Plugin Deploy
    uses: 10up/action-wordpress-plugin-deploy@robust
    env:
    SVN_PASSWORD: ${{ secrets and techniques and strategies.SVN_PASSWORD }}
    SVN_USERNAME: ${{ secrets and techniques and strategies.SVN_USERNAME }}
    SLUG: ${{ secrets and techniques and strategies.SLUG }}

This workflow uses the 10up/action-wordpress-plugin-deploy movement, which retrieves the code from a Git repository and pushes it to the WordPress.org SVN repository, simplifying the operation.

Summary

When rising an extensible plugin for WordPress, our objective is to make it so simple as possible for third-party developers to extend it, thereby maximizing the chances of fostering a vibrant ecosystem spherical our plugins.

While providing intensive documentation can data developers on learn to extend the plugin, an a lot more environment friendly way is to provide the essential PHP code and tooling for development, checking out, and releasing their extensions.

By means of along side the additional code sought after by way of extensions in an instant in our plugin, we simplify the process for developers.

Do you have the desire to make your WordPress plugin extensible? Let us know inside the comments section.

The submit Easy methods to make a WordPress plugin extensible with PHP categories seemed first on Kinsta®.

WP Hosting

[ continue ]

WordPress Maintenance Plans | WordPress Hosting

read more

0 Comments

Submit a Comment

DON'T LET YOUR WEBSITE GET DESTROYED BY HACKERS!

Get your FREE copy of our Cyber Security for WordPress® whitepaper.

You'll also get exclusive access to discounts that are only found at the bottom of our WP CyberSec whitepaper.

You have Successfully Subscribed!