Filters and hooks
Read options in code, add field types and settings, add fields to products, decide when a field applies, check values and work out prices.
Optionline's hooks are WordPress filters and actions. Put the examples in a small custom plugin or your child theme's functions.php. Optionline Pro is built on these same hooks.
How a field looks in code
A product's own fields are stored, in order, in its _optionline_fields meta. Read them with Extendiment\Optionline\Fields::get( $product ), or the fields a product actually shows (including fields add-ons add, without those that can't be used) with Fields::for_product( $product ).
array(
'id' => 'engraving', // stable: cart lines and orders point at it
'label' => 'Engraving',
'type' => 'text', // text, textarea, select, radio, checkboxes, or an add-on's type
'required' => true,
'description' => 'Engraved under the handle.',
'placeholder' => 'Up to 16 letters',
'price' => '6', // types without choices: added when it's filled in
'choices' => array(), // types with choices: array( 'id', 'label', 'price' ) each
// ...plus any settings an add-on keeps on the field
)
Reading options on an order
Each order item keeps a structured copy of its options in the hidden _optionline meta:
use Extendiment\Optionline\Orders;
foreach ( $order->get_items() as $item ) {
foreach ( Orders::entries( $item ) as $entry ) {
// $entry: id, label, type, value (a string, or a list of choice ids), display (as text),
// unit (added to each item's price), once (added to the line once)
error_log( $entry['label'] . ': ' . $entry['display'] );
}
}
The readable copies ("Engraving" => "ANA (+$6.00)") are ordinary order item meta.
Field types
optionline_field_types
Register a type. Optionline's own five can't be replaced.
add_filter( 'optionline_field_types', function ( $types ) {
$types['email'] = array(
'label' => __( 'Email', 'my-plugin' ),
'choices' => false, // customers pick from choices
'multiple' => false, // more than one choice
'placeholder' => true, // takes a placeholder
'priced' => true, // a type without choices has a price of its own
);
return $types;
} );
optionline_field_html
Render a type on the product page. Extendiment\Optionline\Frontend::row() wraps your control in WooCommerce's form row with the label and description. The input's name must be optionline[{id}].
use Extendiment\Optionline\Frontend;
add_filter( 'optionline_field_html', function ( $html, $field, $value, $product ) {
if ( 'email' !== $field['type'] ) {
return $html;
}
$input = sprintf(
'<input type="email" class="input-text" id="%s" name="%s" value="%s"%s>',
esc_attr( Frontend::input_id( $field ) ),
esc_attr( Frontend::input_name( $field ) ),
esc_attr( (string) $value ),
$field['required'] ? ' required' : ''
);
return Frontend::row( $field, $input, $product );
}, 10, 4 );
optionline_posted_value
Read a type's value from the add-to-cart form (or from an earlier order, for "Order again"). Gets the value as Optionline reads it, the field, what was submitted and where it came from (request, reorder or code).
add_filter( 'optionline_posted_value', function ( $value, $field, $raw, $source ) {
return 'email' === $field['type'] ? sanitize_email( (string) $raw ) : $value;
}, 10, 4 );
Checking values
optionline_validate_field
Refuse a value with a message, or return '' to accept it. Runs for every field that applies, after Optionline's own checks (required, valid choices).
add_filter( 'optionline_validate_field', function ( $problem, $field, $value, $product ) {
if ( '' === $problem && 'email' === $field['type'] && '' !== $value && ! is_email( $value ) ) {
/* translators: %s: field label */
return sprintf( __( '%s must be an email address.', 'my-plugin' ), $field['label'] );
}
return $problem;
}, 10, 4 );
optionline_field_applies
Whether a field applies to what the customer chose. A field that doesn't apply is skipped: not required, not stored and not charged. Gets the field, every field's value, all of the product's fields and the product.
// Only ask for a card message when "Handwritten card" is ticked.
add_filter( 'optionline_field_applies', function ( $applies, $field, $values ) {
if ( 'note' === $field['id'] ) {
return in_array( 'card', (array) ( $values['gift'] ?? array() ), true );
}
return $applies;
}, 10, 3 );
Prices
optionline_field_price
What a value adds, as entered (like product prices): unit is added to each item's price, once to the cart line once. Optionline adds the field's or the chosen choices' prices to unit.
// Engraving: $1 per letter.
add_filter( 'optionline_field_price', function ( $amount, $field, $value, $product ) {
if ( 'engraving' === $field['id'] ) {
$amount['unit'] = mb_strlen( str_replace( ' ', '', $value ) ) * 1.0;
}
return $amount;
}, 10, 4 );
Show the same rule on the product page with optionline_price_label (the text after a label), and in the live total by adding a price type in JavaScript: window.optionline.priceTypes.myType = ( price, field, value, context ) => … with optionline_field_config setting the field's priceType.
Fields on products
optionline_product_fields
The fields a product shows, in order: its own, plus any you add. Each is cleaned and checked like the product's own.
// A gift message on every product in the "Gifts" category.
add_filter( 'optionline_product_fields', function ( $fields, $product ) {
if ( has_term( 'gifts', 'product_cat', $product->get_id() ) ) {
$fields[] = array( 'id' => 'giftmsg', 'label' => 'Gift message', 'type' => 'textarea', 'price' => '' );
}
return $fields;
}, 10, 2 );
Hooks for add-ons
| Hook | What it's for |
|---|---|
optionline_loaded |
Fires once Optionline has loaded (on plugins_loaded, only with WooCommerce active). OPTIONLINE_VERSION is defined from then on. |
optionline_clean_field |
Clean and keep a setting of your own on a field ($clean, $raw). Empty settings are dropped; Optionline's own settings in the result are ignored. |
optionline_handled_settings |
Return the names of the settings your code handles. A field that uses a setting nobody handles is held back from the shop (see below). |
optionline_field_settings |
Print rows in a field's box ($field, $prefix). Use Admin\FieldEditor::row() and FieldEditor::name( $prefix, 'setting' ); a row with $types only shows for those types. |
optionline_choice_headers, optionline_choice_cells |
Add columns to the choices table. |
optionline_posted_field |
Read your settings from the posted field ($field, $raw, $stored; nonce and capability already checked). Set every setting you show, empty ones too. |
optionline_product_types |
Product types that can have options (default simple and variable). |
optionline_requires_choice |
Whether the shop's buttons send customers to the product page. |
optionline_cart_entry |
An entry as it's stored on the cart line and the order item. |
optionline_display_value |
A value as text in the cart, on orders and in emails. |
optionline_order_item_meta |
The readable meta saved on the order item for an entry. |
optionline_price_label |
The price after a field's label or a choice's name on the product page. |
optionline_field_config, optionline_frontend_config |
What the product page's script gets. |
optionline_show_totals |
Whether the product page shows the totals. |
optionline_before_fields, optionline_after_fields |
Print something before or after the fields. |
optionline_enqueue_scripts |
Enqueue your scripts with Optionline's on the product page. |
optionline_product_tab, optionline_save_product |
Add to, and save, the product's Options tab. |
Settings nobody handles
A field whose type isn't registered, or that has a setting (on the field or one of its choices) that no active plugin lists in optionline_handled_settings, is kept with all its settings but left off the product page and out of anything added to the cart from then on (lines already in a cart keep what they were added with). The field's box in the editor says why. This is what keeps a field set up with an add-on from being shown or charged the wrong way while the add-on is inactive.
The code documents each hook's arguments where it's applied.