Hooks
The filters and actions for the scan, what counts as registered, templates, leftover data, the audits, the report and add-ons.
Pruneline's code lives in the Extendiment\Pruneline namespace. The scan writes an index, the table {prefix}pruneline_refs: one row per thing found in one place, with kind (block, widget, module, template, pattern, part, navigation, hidden), name, builder (block-editor, elementor, beaver, siteorigin, bricks, oxygen, divi, wpbakery), object_type (post, widget, theme), object_id, status and uses. Read it with Index::names( $kind, $builder ), Index::places( $kind, $name, $builder ) and Index::in_place( $kind, $object_type, $object_id, $builder ).
An owner is an array with type (plugin, theme, mu-plugin, core or unknown), id (a plugin's basename or a theme's stylesheet) and name. Owners::state( $owner ) returns active, inactive or deleted.
The constants PRUNELINE_VERSION, PRUNELINE_FILE, PRUNELINE_URL and PRUNELINE_DIR are defined for add-ons.
Loading, access and settings
| Hook | Parameters | What it's for |
|---|---|---|
pruneline_loaded (action) |
none | Pruneline has loaded (on plugins_loaded). Its classes and constants are available. Add-ons hook their features here. |
pruneline_capability (filter) |
$capability: manage_options |
Who can open Tools → Pruneline, run the scan, download CSV and move templates and patterns to the Trash. |
pruneline_cleanup_capability (filter) |
$capability: manage_options |
Who can export and delete leftover data. Users need this and pruneline_capability. |
pruneline_settings (action) |
$page: the settings page's slug, pruneline-settings |
Add sections and fields to Settings → Pruneline. Register your options in the $page group, so the page's form saves them. |
add_action( 'pruneline_settings', function ( $page ) {
register_setting( $page, 'my_cleanup_notes', array( 'sanitize_callback' => 'sanitize_textarea_field' ) );
add_settings_section( 'my_notes', 'Notes', '__return_false', $page );
add_settings_field( 'my_notes_field', 'Cleanup notes', function () {
printf( '<textarea name="my_cleanup_notes" class="large-text" rows="3">%s</textarea>', esc_textarea( get_option( 'my_cleanup_notes', '' ) ) );
}, $page, 'my_notes' );
} );
// Let editors see the audits, but keep deleting data for administrators.
add_filter( 'pruneline_capability', function () {
return 'edit_others_pages';
} );
The scan
| Hook | Parameters | What it's for |
|---|---|---|
pruneline_sources (filter) |
$sources: Sources\Source[], by default Sources\Posts, Sources\Widgets and Sources\ThemeFiles |
The places the scan reads. Add an object that implements Extendiment\Pruneline\Sources\Source. |
pruneline_scan_finished (action) |
$state: the scan's progress, with status (done), scan (its number), done, total, started and finished (timestamps) |
A full scan has finished, after the rows it didn't see are removed. |
A Source has three methods: key() (the object type stored with each row, at most 20 characters), total() (how many objects a scan reads, for the progress bar) and batch( $cursor, $limit ). batch() returns cursor, done and items, each item with id, status (such as publish; anything but publish or private counts as a draft) and refs, an Extendiment\Pruneline\Refs object. Fill refs with $refs->add( $kind, $name, $builder, $uses ), or with the extractors: Extract\Blocks::refs( $markup ), Extract\Elementor::refs( $data ), Extract\Shortcodes::refs( $content ). The rows of a source that is gone are removed when the next scan finishes.
// Scan block markup kept in a custom option, one place per entry.
class My_Landing_Source implements \Extendiment\Pruneline\Sources\Source {
public function key(): string {
return 'my_landing';
}
public function total(): int {
return count( (array) get_option( 'my_landing_pages', array() ) );
}
public function batch( string $cursor, int $limit ): array {
$items = array();
foreach ( (array) get_option( 'my_landing_pages', array() ) as $id => $markup ) {
$items[] = array(
'id' => (string) $id,
'status' => 'publish',
'refs' => \Extendiment\Pruneline\Extract\Blocks::refs( (string) $markup ),
);
}
return array( 'cursor' => '', 'done' => true, 'items' => $items );
}
}
add_filter( 'pruneline_sources', function ( $sources ) {
$sources[] = new My_Landing_Source();
return $sources;
} );
// Email the site admin when a scan finishes.
add_action( 'pruneline_scan_finished', function ( $state ) {
$missing = \Extendiment\Pruneline\Audits\Registry::get( 'blocks' )->filtered( 'missing' );
if ( $missing ) {
wp_mail( get_option( 'admin_email' ), 'Missing blocks', implode( ', ', wp_list_pluck( $missing, 'block' ) ) );
}
} );
What counts as registered
| Hook | Parameters | What it's for |
|---|---|---|
pruneline_registered (filter) |
$state: null; $builder; $kind: block, widget (Elementor) or module; $name |
Decide whether a block, Elementor widget or builder module works now. Return registered, missing, unsure (shown as "Needs confirmation") or inactive; return null to let Pruneline check. It's only asked while the builder is active. |
// My plugin registers acme/map only in the front end's JavaScript.
add_filter( 'pruneline_registered', function ( $state, $builder, $kind, $name ) {
return 'block-editor' === $builder && 'acme/map' === $name ? 'registered' : $state;
}, 10, 4 );
Templates
| Hook | Parameters | What it's for |
|---|---|---|
pruneline_template_types (filter) |
$types: post type => builder. By default wp_block => block-editor, elementor_library => elementor, fl-builder-template => beaver, et_pb_layout => divi, bricks_template => bricks, ct_template => oxygen |
The post types listed as saved templates. wp_block (synced patterns) is shown on Patterns & parts, the others on Templates. Post types that don't exist are skipped. |
pruneline_template_applied (filter) |
$state: none; $id: the template's post ID; $builder |
How a template with no reference is applied: return one of conditions, triggers, kit or none. Asked for builders other than Elementor, Beaver Builder, Bricks and Oxygen, which Pruneline checks itself. |
A template counts as used when the index has a template row whose name is its post ID (for Beaver Builder, also its _fl_builder_template_id) for the same builder. A source can add such rows with $refs->add( 'template', (string) $id, $builder ).
// Divi layouts my theme applies through its own settings.
add_filter( 'pruneline_template_applied', function ( $state, $id, $builder ) {
return 'divi' === $builder && in_array( $id, array_map( 'intval', (array) get_option( 'my_theme_layouts', array() ) ), true ) ? 'conditions' : $state;
}, 10, 3 );
Leftover data
| Hook | Parameters | What it's for |
|---|---|---|
pruneline_protected (filter) |
$protected: bool, true for the data of one half of a plugin pair (<folder> and <folder>-pro) while the other half is active; $item: type (option, table, cron, action, postmeta, usermeta), name, owner, state, confidence (exact or prefix), and more |
Whether a leftover is kept on purpose. It stays listed, marked "(kept on purpose)", without a checkbox. |
pruneline_cleaned (action) |
$items: the items deleted, each with type, name, size, owner, state, confidence and detail |
Pruneline has deleted leftover data, after the export and confirmation. |
// Never offer to delete the old shop's tables: the accountant still reads them.
add_filter( 'pruneline_protected', function ( $protected, $item ) {
return 'table' === $item['type'] && false !== strpos( $item['name'], 'oldshop_' ) ? true : $protected;
}, 10, 2 );
add_action( 'pruneline_cleaned', function ( $items ) {
error_log( 'Pruneline deleted: ' . implode( ', ', wp_list_pluck( $items, 'name' ) ) );
} );
Audits and the report
| Hook | Parameters | What it's for |
|---|---|---|
pruneline_audits (filter) |
$audits: Audits\Audit[]: blocks, builders, templates, patterns, hidden, usage, plugins, data |
The lists on Tools → Pruneline. An audit you add gets a tab of its own, a line in the overview, CSV and wp pruneline list <key>. |
pruneline_report (filter) |
$data: site, editors, plugins, templates, colors, fonts, addons, blocks, patterns |
The handover report's data, on screen and in wp pruneline report. The screen shows the sections it knows; extra keys only appear in the JSON. |
An audit extends Extendiment\Pruneline\Audits\Audit and implements key(), title(), intro() (one sentence), columns() (key => label) and rows(). Each row holds a value per column, plus _id. Optionally a row has _links (column => URL), _view (the views it belongs to) and _bulk (whether it can be selected). Override views(), bulk_actions() and bulk( $action, $ids ) for views and a bulk action, has_detail(), detail( $id ) and detail_columns() for a Where used page, and count() for the overview's number.
class My_Drafts_Audit extends \Extendiment\Pruneline\Audits\Audit {
public function key(): string { return 'old_drafts'; }
public function title(): string { return 'Old drafts'; }
public function intro(): string { return 'Drafts nobody has touched for a year.'; }
public function columns(): array { return array( 'title' => 'Title', 'modified' => 'Last changed' ); }
public function rows(): array {
$rows = array();
foreach ( get_posts( array( 'post_status' => 'draft', 'post_type' => 'any', 'numberposts' => -1, 'date_query' => array( array( 'column' => 'post_modified', 'before' => '1 year ago' ) ) ) ) as $post ) {
$rows[] = array(
'_id' => (string) $post->ID,
'_links' => array( 'title' => get_edit_post_link( $post->ID, 'raw' ) ),
'title' => $post->post_title,
'modified' => $post->post_modified,
);
}
return $rows;
}
}
add_filter( 'pruneline_audits', function ( $audits ) {
$audits[] = new My_Drafts_Audit();
return $audits;
} );
// Leave the active plugins out of the printed report.
add_filter( 'pruneline_report', function ( $data ) {
$data['plugins'] = array();
return $data;
} );
REST and WP-CLI
The screen uses the REST namespace pruneline/v1: GET scan (the scan's progress) and POST scan (runs the scan for a few seconds, starting one with restart=true or when none is running; returns status, done, total and percent), and POST editor-blocks (the block names the editor knows), all for users with pruneline_capability. GET shortcodes?post=… (the shortcodes on an Elementor page) is for users who can edit that post.
On the command line: wp pruneline scan, wp pruneline list <audit> [--view=<view>] [--item=<id>] [--format=<format>] and wp pruneline report. See WP-CLI.