Contents

Hooks reference - Plugixa Car Wash

The plugin fires 29 actions and 46 filters. All of them start with plugixa_carwash_. Three actions are fired only by Pro modules and are marked PRO.

Pro is built on these same hooks. Payments by Stripe, memberships, gift cards, the travel fee, reminders and webhooks are ordinary listeners on the hooks below, so what they do, your code can do too. Put your code in a small plugin of your own or in a must-use plugin, not in this plugin’s files, so updates do not remove it.

These hooks are the plugin’s own seams. They are not a separately versioned API, and an argument or a payload can change between versions. Pin the version you build against and test after updating.

The ones you are most likely to want

Hook Type What it is for
plugixa_carwash_booking_created action React to a new booking
plugixa_carwash_booking_status_changed action React to a booking being confirmed, started, completed, cancelled or marked a no-show
plugixa_carwash_booking_moved action React to a booking moving to another time, bay or team
plugixa_carwash_payment_recorded action React to a payment or a refund
plugixa_carwash_quote filter Change the price or the duration of a booking
plugixa_carwash_validate_booking filter Refuse a booking for a reason of your own
plugixa_carwash_transition_error filter Refuse a status change for a reason of your own
plugixa_carwash_public_request_captcha filter Put a CAPTCHA in front of the public routes
plugixa_carwash_client_ip filter Tell the rate limiter the real visitor address behind a proxy
plugixa_carwash_user_can filter Narrow or widen what a user may do

Booking statuses

Several hooks pass a status. These are the values:

Value Shown as
hold Awaiting payment: an online payment is in progress
pending Pending: waiting for staff to confirm
confirmed Confirmed
in_progress In progress
completed Completed
cancelled Cancelled
no_show No-show

Entity slugs

The record hooks pass an entity slug.

Slug Record
booking A booking
customer A customer
location A location
resource A bay or a mobile team
vehicle_type A vehicle type
package A package
service A service or add-on
coupon A coupon
booking_form A booking form
membership PRO A customer’s membership
membership_plan PRO A membership plan
gift_card PRO A gift card
fleet_account PRO A company account
role PRO A staff role

Actions

Bookings and payments

Action Arguments When it fires
plugixa_carwash_booking_created array $booking, bool $public, array $quote After a booking is saved, its lines are written and its first history entry is recorded. $booking is the booking as the staff screens see it. $public is true for the booking form and false for staff. $quote is what it was priced at. Emails, texts, gift cards and webhooks listen here.
plugixa_carwash_booking_status_changed array $booking, string $from After a status change is saved. $booking is after the change; $booking['status'] is the new status. Also fires when the plugin changes a status by itself: an unpaid hold that runs out, or a paid hold that is confirmed.
plugixa_carwash_booking_moved array $booking, array $row After a booking moves to another time, bay or team. $booking is after the move, $row is the stored row before it. Times in $row are UTC.
plugixa_carwash_payment_recorded array $payment After a payment or refund is recorded and the booking’s payment status is updated. $payment holds id, booking_id, provider, amount (negative for a refund), currency and status.
plugixa_carwash_payment_return array $row, array $params When a customer comes back from a payment page and their booking is read. $row is the stored booking, $params the query string, each value cleaned as one line of text. A gateway checks the payment with its own API here.
plugixa_carwash_job_stage_changed PRO array $job, string $stage A mobile job moved on. $stage is en_route, arrived, working or done. See Dispatch.
plugixa_carwash_fleet_statement_issued PRO array $statement, array $fleet A statement was issued to a company account. See Company accounts.
plugixa_carwash_stripe_event PRO array $event A Stripe webhook event arrived and its signature checked out. $event is the decoded Stripe event. See Stripe.

Tell a manager about every online booking:

add_action( 'plugixa_carwash_booking_created', function ( array $booking, bool $public ) {
	if ( ! $public ) {
		return; // Staff made it at the desk.
	}

	wp_mail(
		'manager@example.com',
		'Online booking ' . $booking['number'],
		sprintf(
			"%s %s booked %s at %s on %s.",
			$booking['first_name'],
			$booking['last_name'],
			$booking['items'][0]['name'] ?? 'a wash',
			$booking['local_time'] ?? '',
			$booking['local_date'] ?? ''
		)
	);
}, 10, 2 );

$booking['start_at'] is UTC. local_date and local_time are the start at the booking’s location. items lists the lines, each with role (package, service, addon, fee or discount), item_id, name, price and duration. payments lists the payments.

React to a no-show:

add_action( 'plugixa_carwash_booking_status_changed', function ( array $booking, string $from ) {
	if ( 'no_show' === $booking['status'] ) {
		// Flag the customer in your CRM, for example.
	}
}, 10, 2 );

Records

Every record table goes through one repository, so these fire for every entity in the slug table above. The booking actions above are more useful for bookings: they carry the whole booking, while these carry the raw row.

Action Arguments When it fires
plugixa_carwash_entity_created string $entity, int $id, array $data After a record is inserted. A customer created by a first booking fires it too.
plugixa_carwash_entity_updated string $entity, int $id, array $data, array $old After a record is updated. $data holds the changed columns.
plugixa_carwash_entity_field_changed string $entity, int $id, string $field, mixed $new, mixed $old Once for each column whose value changed in an update.
plugixa_carwash_entity_assigned string $entity, int $id, int $new_user, int $old_user When a record’s assigned_user_id changes. No car wash table has that column, so it does not fire today.
plugixa_carwash_before_delete string $entity, int $id, array $old Before a record is moved to the trash.
plugixa_carwash_entity_deleted string $entity, int $id, array $old After a record is moved to the trash. The row still exists.
plugixa_carwash_entity_restored string $entity, int $id, array $row After a record is restored from the trash.
plugixa_carwash_before_purge string $entity, int $id, array $row Before a record is deleted for good. The last moment it exists.
plugixa_carwash_entity_purged string $entity, int $id, array $row After a record is deleted for good.

Lifecycle

Action Arguments When it fires
plugixa_carwash_fs_loaded none While the main plugin file is read, after the Freemius SDK is set up. It fires before plugins_loaded, so attach from a must-use plugin.
plugixa_carwash_loaded Container $container Once the plugin has booted on plugins_loaded, after every module has registered its hooks.
plugixa_carwash_activated none On activation, after the tables and the default settings exist. Not on an update.
plugixa_carwash_migration_failed array $failures When creating or updating the tables did not finish. Keyed by module id, each with tables and reason.
plugixa_carwash_deactivated none On deactivation. Modules clear their scheduled events here.
plugixa_carwash_uninstall none During an uninstall that erases the data, before the tables are dropped. See Privacy and uninstall.
plugixa_carwash_settings_saved array $settings After the settings are saved.
plugixa_carwash_app_toggled string $id, bool $enabled After an app is switched on or off in the App Manager.

Admin, REST and mail

Action Arguments When it fires
plugixa_carwash_admin_submenus string $parent_slug, callable $render While the admin menu is built, after the Dashboard entry. Add your own submenu pages here.
plugixa_carwash_enqueue_assets string $hook_suffix On the plugin’s admin page, after the app is enqueued. Enqueue anything that belongs on that page here.
plugixa_carwash_register_rest_routes string $namespace, Container $container On rest_api_init, after the plugin’s own routes. $namespace is plugixa-carwash/v1.
plugixa_carwash_before_send_mail string|string[] $to, string $subject, array $options Just before the plugin hands an email to wp_mail(). For logging or diverting mail.

Filters

Pricing and booking rules

Filter Value Other arguments What it decides
plugixa_carwash_quote array $quote array $input, array $chosen The priced booking, on the live summary and when the booking is saved, so the price shown is the price charged. $chosen holds location, vehicle, package, services and form. Add a line and call Quote::totals() to re-total.
plugixa_carwash_validate_booking null array $input, array $quote, bool $public Return a WP_Error to refuse the booking, with your own message and status.
plugixa_carwash_initial_status string $status array $input, array $quote The status an online booking starts in: confirmed, or pending when bookings are confirmed by hand. A payment module returns hold for a method paid online. Only hold, pending and confirmed are accepted.
plugixa_carwash_transition_error null array $row, string $to Return a WP_Error to refuse a status change the normal rules allow. $row is the stored booking before the change.
plugixa_carwash_pick_resource int $chosen array $free, array $schedule, array $window Which free bay or team a new booking gets. The default is the first free one in your order. Return the id of one of $free.
plugixa_carwash_public_catalogue array $catalogue array $form What the booking form receives: packages, services, payment methods, whether it takes coupons.
plugixa_carwash_public_input array $input WP_REST_Request $request A public booking request’s parameters after cleaning. Any customer_id the browser sent has been removed; the customer account puts back the signed-in customer here.
plugixa_carwash_booking_detail array $booking array $row A booking as every screen reads it. The Payments module adds payments here.

Add a surcharge at one location:

use PlugixaCarWash\Modules\Bookings\Services\Quote;

add_filter( 'plugixa_carwash_quote', function ( array $quote, array $input, array $chosen ) {
	if ( 3 !== (int) $chosen['location']['id'] ) {
		return $quote;
	}

	$quote['lines'][] = array(
		'role'     => 'fee',
		'item_id'  => null,
		'name'     => 'Airport parking surcharge',
		'price'    => 5.00,
		'duration' => 0,
	);

	return Quote::totals( $quote ); // Recomputes subtotal, tax, total and what is due.
}, 5, 3 );

Priority 5 puts the fee before the plugin’s own discounts, so a percentage coupon applies to it too. To make the booking longer as well, add the minutes to the line’s duration and to $quote['duration'].

Refuse to complete a wash that has not been paid:

add_filter( 'plugixa_carwash_transition_error', function ( $error, array $row, string $to ) {
	if ( null !== $error ) {
		return $error; // Somebody else refused it already.
	}

	if ( 'completed' === $to && 'paid' !== $row['payment_status'] ) {
		return new WP_Error(
			'my_unpaid',
			'Take payment before marking this wash completed.',
			array( 'status' => 409 )
		);
	}

	return null;
}, 10, 3 );

The message is shown to the staff member who tried the change.

Payments

Filter Value Other arguments What it decides
plugixa_carwash_payment_methods array $methods none Every method the site can offer, keyed by id, each with id, label, description and online.
plugixa_carwash_offered_payment_methods string[] $offered array $input The method ids this booker may use. For methods offered to some customers only, such as charging a company account.
plugixa_carwash_booking_payment_method string $method array $input, array $quote The method stored on a new booking.
plugixa_carwash_booking_payment array|null $payment array $booking, string $token After a public booking is made, where to send the customer to pay. Null for a booking paid at the wash.
plugixa_carwash_payment_start_{method} array|WP_Error|null $start array $booking, string $token One filter per online method id, for example plugixa_carwash_payment_start_stripe. Return array( 'redirect_url' => ... ) or a WP_Error.

A method you add appears in Settings -> Payments to be switched on. A method with online set to true must also answer its payment_start filter.

add_filter( 'plugixa_carwash_payment_methods', function ( array $methods ) {
	$methods['voucher'] = array(
		'id'          => 'voucher',
		'label'       => 'Paper voucher',
		'description' => 'Hand your voucher in at the desk.',
		'online'      => false,
	);

	return $methods;
} );

Public routes

Filter Value Other arguments What it decides
plugixa_carwash_public_request_captcha true WP_REST_Request $request, string $action Whether a public request passes your anti-bot check. Return false, or a WP_Error with your own message and status, to refuse. Nothing is checked by default.
plugixa_carwash_client_ip string $ip none The visitor address the rate limiter counts. By default REMOTE_ADDR only.

$action is the rate-limit bucket: carwash_book, carwash_cancel, carwash_read, carwash_account_link, carwash_account_sign_in or carwash_account. See REST API.

Behind Cloudflare, every visitor arrives from a Cloudflare address and shares one rate limit. Use the address Cloudflare passes on, and only when the site really is behind Cloudflare, because the header can be forged otherwise:

add_filter( 'plugixa_carwash_client_ip', function ( string $ip ) {
	$real = isset( $_SERVER['HTTP_CF_CONNECTING_IP'] ) ? trim( wp_unslash( $_SERVER['HTTP_CF_CONNECTING_IP'] ) ) : '';

	return filter_var( $real, FILTER_VALIDATE_IP ) ? $real : $ip;
} );

Check a CAPTCHA token on new bookings only:

add_filter( 'plugixa_carwash_public_request_captcha', function ( $verdict, WP_REST_Request $request, string $action ) {
	if ( true !== $verdict || 'carwash_book' !== $action ) {
		return $verdict;
	}

	$token = (string) $request->get_header( 'x-captcha-token' );

	return my_verify_captcha( $token ) // Your provider's check.
		? true
		: new WP_Error( 'my_captcha', 'Please confirm you are not a robot.', array( 'status' => 403 ) );
}, 10, 3 );

The booking form does not send a CAPTCHA token by itself; how the token reaches the request is up to your integration.

Customer account

Filter Value Other arguments What it decides
plugixa_carwash_account_panels array $panels int $customer_id Extra panels on the customer’s account page. Each panel has id, title, an optional subtitle, rows of label and value pairs, an optional meter (value, max, label), an optional code to copy, and actions (id, label, optional confirm and primary).
plugixa_carwash_account_action null string $panel, string $action, int $customer_id, array $params A button on one of those panels was pressed. Return an array (with a message) when it is yours, a WP_Error to refuse, or null when the panel is not yours.

Permissions and data access

Filter Value Other arguments What it decides
plugixa_carwash_user_can bool $allowed string $action, string $entity_type, array|null $record, int $user_id Every permission check. $action is read, create, edit, delete or export. The Pro roles module answers here.
plugixa_carwash_capabilities array $map none The yes or no map the admin app uses to hide controls. It hides buttons only; the server checks again.
plugixa_carwash_query_clauses array $clauses string $entity_type, string $context, int $user_id, string $table Extra join, where and params for list queries, to narrow what a user sees.
plugixa_carwash_search_fragments array $fragments string $entity_type, string $term, string $context Extra OR conditions for the search box, each array( 'sql' => ..., 'params' => array( ... ) ).
plugixa_carwash_unreadable_fields string[] $denied string $entity_type, int $user_id Fields a user may not read, so they are not offered as list filters.
plugixa_carwash_filter_output mixed $data string $entity_type Every successful admin REST answer, before it is sent. The one place to remove fields a user may not see.

Let a role with view rights also see coupons, but never change them:

add_filter( 'plugixa_carwash_user_can', function ( bool $allowed, string $action, string $entity ) {
	if ( 'coupon' === $entity && 'read' !== $action ) {
		return current_user_can( 'manage_options' );
	}

	return $allowed;
}, 10, 3 );

Records and entities

Filter Value Other arguments What it decides
plugixa_carwash_before_insert array $data string $entity A record’s columns just before it is inserted.
plugixa_carwash_before_update array $data string $entity, int $id, array $old A record’s changed columns just before the update.
plugixa_carwash_can_delete bool $allowed string $entity, int $id, array $old Whether a record may be moved to the trash. A last line of defence: it carries no message.
plugixa_carwash_can_purge bool $allowed string $entity, int $id, array $row Whether a record may be deleted for good.
plugixa_carwash_{entity}_delete_blockers string[] $blockers int $id, array $row Reasons a record must not be trashed, each a full sentence shown to the user. For example plugixa_carwash_location_delete_blockers. The plugin uses it to protect anything an upcoming booking depends on.
plugixa_carwash_entity_types array $entities none The entities the generic screens and the role builder know about.
plugixa_carwash_entity_fields array $fields string $slug One entity’s field definitions.
plugixa_carwash_record_names array $names string $entity_type, int[] $ids Display names for some records, keyed by id.
plugixa_carwash_load_record array|null $record string $entity_type, int $id The record loaded for a permission check.
plugixa_carwash_record_writers array $writers none create and update callables per entity, used by mass actions.
plugixa_carwash_filterable_fields array $fields string $entity_type What a list may be filtered by.
plugixa_carwash_filter_api_bases array $map none Which URL segment belongs to which filterable entity.
plugixa_carwash_export_lists array $lists none The lists Export CSV can write, keyed by entity slug: each with its REST collection (api_base) and its columns, keyed by the grid field each belongs to. Bookings and customers out of the box.

Settings, modules and the admin app

Filter Value Other arguments What it decides
plugixa_carwash_settings_defaults array $defaults none Default values of the settings. Modules add their own keys here.
plugixa_carwash_settings_choices array $choices none The option lists the settings screen offers, such as currencies and date formats.
plugixa_carwash_config_settings_keys string[] $keys none Which settings are printed onto the admin page for its first paint.
plugixa_carwash_admin_config array $config none The configuration printed onto the admin page. Anyone who can open the app can read it.
plugixa_carwash_admin_i18n_strings array $strings none The translated strings handed to the admin app.
plugixa_carwash_bootstrap_payload array $payload none What the admin app receives when it starts.
plugixa_carwash_modules string[] $classes none The module classes to load. An add-on can contribute a module here.
plugixa_carwash_apps array $cards none The cards in the App Manager.
plugixa_carwash_app_page_keep_styles string[] $handles none Theme stylesheets kept on the full-page staff app, for example a web font it needs.
plugixa_carwash_is_app_page bool $is none Whether this page is the bare, full-page staff app. For a page builder that hides the shortcode from WordPress. The capability check still applies.
plugixa_carwash_dev_bar bool $enabled none Whether the development strip, with a sign-out link, is drawn at the foot of public pages for signed-in users. On by default when WP_ENVIRONMENT_TYPE is anything but production.

Scheduled events

These are WordPress cron hooks, not hooks to attach your own code to. They are listed so you can recognise them in a cron manager.

Hook Runs Does
plugixa_carwash_expire_holds Every five minutes Cancels bookings whose online payment was not finished in time.
plugixa_carwash_send_reminders PRO Hourly Sends reminders.
plugixa_carwash_webhooks_send PRO When a delivery is due Sends webhooks.
plugixa_carwash_webhooks_sweep PRO Hourly Picks up missed webhook deliveries and prunes the log.
plugixa_carwash_membership_sweep PRO Daily Membership housekeeping. See Memberships.
plugixa_carwash_fleet_statements PRO Daily Company account statements. See Company accounts.

Switching an app off in the App Manager clears its scheduled events.

Quick Links