# Filters and hooks - Optionline 1.x documentation

> 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 )`.

```php
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:

```php
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.

```php
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}]`.

```php
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`).

```php
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).

```php
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.

```php
// 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`.

```php
// 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.

```php
// 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.

Source: https://extendiment.com/plugins/optionline/docs/1.x/filters-and-hooks
