templates = $templates;
}
/**
* Registers the hooks.
*/
public function register_hooks() {
add_action( 'init', array( $this, 'register_assets' ) );
add_filter( 'style_loader_tag', array( $this, 'async_style' ), 10, 2 );
}
/**
* Loads the form stylesheet without holding the page: the form is often
* far down the page, and the script shows it once the stylesheet applies.
*
* @param string $tag Link tag.
* @param string $handle Style handle.
* @return string
*/
public function async_style( $tag, $handle ) {
if ( 'primiko-form' !== $handle || false !== strpos( $tag, 'onload=' ) ) {
return $tag;
}
return preg_replace( "/media=(['\"])all\\1/", 'media="print" onload="this.media=\'all\'"', $tag, 1 );
}
/**
* Registers the visitor script and style. They are only enqueued on pages
* that show a form.
*/
public function register_assets() {
// The minified build of assets/form.js, versioned by its content.
$asset = file_exists( PRIMIKO_DIR . 'build/form/form.asset.php' )
? require PRIMIKO_DIR . 'build/form/form.asset.php'
: array( 'version' => PRIMIKO_VERSION );
wp_register_script(
'primiko-form',
PRIMIKO_URL . 'build/form/form.js',
array(),
$asset['version'],
array(
'in_footer' => true,
'strategy' => 'defer',
)
);
wp_register_style( 'primiko-form', PRIMIKO_URL . 'assets/form.css', array(), PRIMIKO_VERSION . '-' . filemtime( PRIMIKO_DIR . 'assets/form.css' ) );
}
/**
* Returns the HTML of a form.
*
* @param int $form_id Form post ID.
* @param int $heading Heading level of the step titles, 2 to 4. 3 by
* default: the form usually sits under the title
* of its section.
* @return string Empty when the form cannot be shown.
*/
public function render( $form_id, $heading = 3 ) {
$this->heading = max( 2, min( 4, (int) $heading ) );
$form = get_post( $form_id );
$schema = $form && Post_Types::FORM === $form->post_type && 'publish' === $form->post_status
? $this->templates->form_schema( $form->ID )
: null;
if ( null === $schema ) {
return $this->unavailable_notice();
}
++$this->count;
$prefix = 'primiko-' . $this->count . '-';
wp_enqueue_script( 'primiko-form' );
wp_enqueue_style( 'primiko-form' );
Captcha::enqueue();
ob_start();
?>
' . esc_html__( 'This quote form cannot be shown: it is not published or its trade template is missing. Only editors see this message.', 'primiko' ) . '';
}
/**
* Settings read by assets/form.js.
*
* @param int $form_id Form post ID.
* @param array $schema Form schema.
* @param string $prefix Element ID prefix.
* @return array
*/
private function config( $form_id, $schema, $prefix ) {
$steps = array();
$fields = array();
foreach ( $schema['steps'] as $step ) {
$steps[] = array(
'id' => $step['id'],
'title' => $step['title'],
'showIf' => isset( $step['show_if'] ) ? $step['show_if'] : null,
'review' => ! empty( $step['review'] ),
);
foreach ( $step['fields'] as $field ) {
$entry = array(
'type' => $field['type'],
'step' => $step['id'],
'label' => $field['label'],
'required' => ! empty( $field['required'] ),
'showIf' => isset( $field['show_if'] ) ? $field['show_if'] : null,
);
foreach ( array( 'multiple', 'min', 'max', 'unit', 'maxlength' ) as $key ) {
if ( isset( $field[ $key ] ) ) {
$entry[ $key ] = $field[ $key ];
}
}
if ( isset( $field['options'] ) ) {
$entry['options'] = wp_list_pluck( $field['options'], 'label', 'value' );
}
$fields[ $field['id'] ] = $entry;
}
}
return array(
'form' => $form_id,
'prefix' => $prefix,
'heading' => $this->heading,
'tokenUrl' => rest_url( Rest::NAMESPACE . '/token' ),
'submitUrl' => rest_url( Rest::NAMESPACE . '/submit' ),
'steps' => $steps,
'fields' => $fields,
'photoSize' => 2000,
'minFill' => Token::min_fill_time(),
'single' => ! empty( $schema['single'] ),
'either' => ! empty( $schema['contact_either'] ),
'captcha' => Captcha::config(),
'icons' => array(
'close' => self::icon( 'close' ),
'check' => self::icon( 'check' ),
),
'i18n' => array(
/* translators: 1: current step number, 2: number of steps, 3: step title. */
'progress' => __( 'Step %1$d of %2$d: %3$s', 'primiko' ),
'required' => __( 'This field is required.', 'primiko' ),
'requiredOne' => __( 'Please choose an answer.', 'primiko' ),
'email' => __( 'Please enter a valid email address, such as name@example.com.', 'primiko' ),
'phone' => __( 'Please enter a valid phone number.', 'primiko' ),
'number' => __( 'Please enter a number.', 'primiko' ),
/* translators: 1: minimum, 2: maximum. */
'range' => __( 'Please enter a number between %1$s and %2$s.', 'primiko' ),
'address' => __( 'Please fill in the street, postcode and town.', 'primiko' ),
'consent' => __( 'Please tick this box to send your request.', 'primiko' ),
'photosNeeded' => __( 'Please add at least one photo.', 'primiko' ),
/* translators: %d: maximum number of photos. */
'photosMax' => __( 'You can add up to %d photos.', 'primiko' ),
'photoBad' => __( 'Your browser could not read this photo. Please choose a JPEG, PNG or WebP photo. An iPhone photo in HEIC format can be sent from the iPhone itself.', 'primiko' ),
/* translators: %d: photo number. */
'photoRemove' => __( 'Remove photo %d', 'primiko' ),
'photoWorking' => __( 'Preparing the photos...', 'primiko' ),
'checkFields' => __( 'Some answers need your attention. Please check the fields marked below.', 'primiko' ),
'notSpecified' => __( 'Not specified', 'primiko' ),
'yes' => __( 'Yes', 'primiko' ),
'no' => __( 'No', 'primiko' ),
'edit' => __( 'Edit', 'primiko' ),
/* translators: %s: step title. */
'editStep' => __( 'Edit: %s', 'primiko' ),
'sending' => __( 'Sending...', 'primiko' ),
'sendFailed' => __( 'Your request could not be sent. Please check your connection and try again.', 'primiko' ),
'captchaWait' => __( 'Please complete the anti-spam check above the button, then send again.', 'primiko' ),
'contactEither' => __( 'Please give a phone number or an email address.', 'primiko' ),
'photoCounts' => $this->photo_strings( 'count' ),
'photosSummary' => $this->photo_strings( 'summary' ),
),
);
}
/**
* Photo counts with their plural form, one string per number of photos
* (0 to the largest a field allows): the script has no plural rules of
* its own.
*
* @param string $kind 'count' for the counter under the picker, 'summary'
* for the review step.
* @return string[] Strings indexed by number of photos.
*/
private function photo_strings( $kind ) {
$strings = array();
for ( $number = 0; $number <= 10; $number++ ) {
$strings[] = 'count' === $kind
/* translators: 1: number of photos added, 2: maximum. */
? _n( '%1$d of %2$d photo', '%1$d of %2$d photos', $number, 'primiko' )
/* translators: %d: number of photos. */
: _n( '%d photo', '%d photos', $number, 'primiko' );
}
return $strings;
}
/**
* Prints one step.
*
* @param array $step Step.
* @param string $prefix Element ID prefix.
*/
private function step( $step, $prefix ) {
$id = $prefix . 'step-' . $step['id'];
?>
';
}
/**
* Prints the photo picker. The file input opens the camera or the photo
* library on a phone. The script keeps the chosen photos, resized, until
* the request is sent.
*
* @param array $field Field.
* @param string $id Element ID.
* @param string $describedby Escaped aria-describedby attribute, or empty.
*/
private function photos( $field, $id, $describedby ) {
?>