Tasks

Every setup step is a task with five methods:

Method Runs
register( Plugin ) On every request, when the plugin boots. Use it to add hooks the task depends on (e.g. cron_schedules).
activate( Plugin ) On activation, and on first install detected at runtime.
deactivate( Plugin ) On deactivation. Keep user data.
upgrade( Plugin, $from, $to ) When the stored version is older, after migrations.
uninstall( Plugin ) On uninstall, when data deletion is enabled. Remove everything the task created.

On multisite, each method runs once per site, with that site switched in.

Built-in tasks

Options

->options( [ 'acme_settings' => [ 'mode' => 'simple' ] ] )         // Not autoloaded.
->options( [ 'acme_small_flag' => 1 ], autoload: true )

Uses add_option(), so existing values are never overwritten. Options added in a later version are created on upgrade.

When a later version adds a key to an array option, existing sites don't get it, because the option already exists. Pass merge: true to fill in missing keys on activation and upgrade:

->options( [ 'acme_settings' => [ 'mode' => 'simple', 'url_base' => 'go' ] ], merge: true )

A site that stored [ 'mode' => 'advanced' ] ends up with [ 'mode' => 'advanced', 'url_base' => 'go' ]. Existing values are never changed. Nested associative arrays are merged the same way, but lists ([ 'a', 'b' ]) are left as they are. Values that aren't arrays are never touched.

Roles and capabilities

->role( 'acme_manager', 'Acme Manager', [ 'read' => true, 'manage_acme' => true ] )
->capabilities( [ 'administrator' => [ 'manage_acme' ], 'editor' => [ 'manage_acme' ] ] )
->capabilities( [ 'shop_manager' => [ 'manage_acme' ] ], removeOnDeactivate: true )

Capabilities added to an existing custom role in a new version are granted on upgrade. Roles and caps are removed on uninstall, and also on deactivation if any capabilities() call passed removeOnDeactivate: true.

Cron

->cronSchedule( 'every_five_minutes', 5 * MINUTE_IN_SECONDS, 'Every five minutes' )
->cron( 'acme_sync', 'every_five_minutes' )
->cron( 'acme_report', 'daily', [ 'weekly' => false ] )

Events are scheduled on activation and unscheduled on deactivation. If you change an event's recurrence in a new version, it's rescheduled on upgrade. Custom schedules are registered on every request, so WordPress can always reschedule them.

Rewrite rules

->flushRewriteRules()

The activation hook runs before your post types are registered, so flushing there would miss them. Instead, a flush is requested on activation and upgrade, and happens at the end of the next init. On deactivation, the stored rules are deleted, so WordPress rebuilds them without yours.

To flush after a change of your own, such as a settings page saving a new URL prefix, call $plugin->requestRewriteFlush(). See Lifecycle and upgrades.

Posts and terms

->postTypes( 'acme_booking', 'acme_resource' )
->taxonomies( 'acme_booking_type' )

On uninstall, when data deletion is enabled, every post of these types is deleted with wp_delete_post(), which also removes its meta, comments and term relationships. Terms in these taxonomies are deleted with wp_delete_term(), which removes their meta. Nothing happens on activation or deactivation. Some details it takes care of:

  • Every status. post_status => 'any' skips auto-draft and trash, so the task asks for every registered status.
  • Unregistered taxonomies. Your plugin's init code doesn't run during uninstall, so get_terms() would find nothing. Each taxonomy is registered for the cleanup and unregistered afterwards.
  • Order. Content is deleted before custom tables are dropped, in case delete hooks read them.
  • Large sites. Posts are deleted in batches of 100. Posts that fail to delete are skipped, so the loop always ends.

Attachments are deleted only if they are of one of the listed post types. Media uploaded to your posts becomes unattached.

Tables

See Database tables and migrations.

Custom tasks

Extend AbstractTask and override only what you need. upgrade() defaults to calling activate().

use Bignall\WPFoundation\Plugin;
use Bignall\WPFoundation\Tasks\AbstractTask;

final class UploadsDirectoryTask extends AbstractTask {
	public function activate( Plugin $plugin ): void {
		wp_mkdir_p( $this->dir() );
		file_put_contents( $this->dir() . '/index.php', '<?php // Silence.' );
	}

	public function uninstall( Plugin $plugin ): void {
		global $wp_filesystem;
		WP_Filesystem();
		$wp_filesystem->delete( $this->dir(), true );
	}

	private function dir(): string {
		return wp_upload_dir()['basedir'] . '/acme';
	}
}

Plugin::create( __FILE__ )->task( new UploadsDirectoryTask() );