machine name, 'label' => human label, * 'type' => field type, 'value' => string or list of strings ). */ final class Ascue_Message { /** The API accepts bodies up to 20,000 characters. */ const MAX_BODY = 20000; /** Cap for each extra field value. */ const MAX_FIELD = 5000; /** * Builds the payload. * * - body: the main message (first textarea, or a field named like * "message"); falls back to every field as "Label: value" lines. * - senderName: a name field, or first + last name fields. * - senderEmail: the first valid email field. * - subject: a field labelled "subject", if present. * - fields: everything else, keyed by label. * * @param array $raw_fields List of fields as described in the class comment. * @return array|null The payload, or null when there is no text to send. */ public static function to_payload( array $raw_fields ) { $fields = self::normalize( $raw_fields ); if ( empty( $fields ) ) { return null; } $used = array(); $email_index = self::find( $fields, function ( $field ) { return 'email' === $field['type'] && is_email( $field['value'] ); } ); if ( null === $email_index ) { $email_index = self::find( $fields, function ( $field ) { return self::matches( $field, '/e-?mail/i' ) && is_email( $field['value'] ); } ); } $sender_email = null; if ( null !== $email_index ) { $sender_email = $fields[ $email_index ]['value']; $used[] = $email_index; } $sender_name = null; $name_index = self::find( $fields, function ( $field ) { return 'name' === $field['type'] || self::matches( $field, '/^\s*(your[\s_-]*)?(full[\s_-]*)?name\s*$/i' ); }, $used ); if ( null !== $name_index ) { $sender_name = $fields[ $name_index ]['value']; $used[] = $name_index; } else { $first_index = self::find( $fields, function ( $field ) { return self::matches( $field, '/first[\s_-]*name|given[\s_-]*name/i' ); }, $used ); $last_index = self::find( $fields, function ( $field ) { return self::matches( $field, '/last[\s_-]*name|surname|family[\s_-]*name/i' ); }, $used ); $parts = array(); foreach ( array( $first_index, $last_index ) as $index ) { if ( null !== $index ) { $parts[] = $fields[ $index ]['value']; $used[] = $index; } } if ( ! empty( $parts ) ) { $sender_name = implode( ' ', $parts ); } } $subject = null; $subject_index = self::find( $fields, function ( $field ) { return 'textarea' !== $field['type'] && self::matches( $field, '/subject/i' ); }, $used ); if ( null !== $subject_index ) { $subject = $fields[ $subject_index ]['value']; $used[] = $subject_index; } $body_index = self::find( $fields, function ( $field ) { return in_array( $field['type'], array( 'textarea', 'post_content' ), true ); }, $used ); if ( null === $body_index ) { $body_index = self::find( $fields, function ( $field ) { return self::matches( $field, '/message|comment|enquiry|inquiry|question|details|how can we help/i' ); }, $used ); } if ( null !== $body_index ) { $body = $fields[ $body_index ]['value']; $used[] = $body_index; } else { $lines = array(); foreach ( $fields as $field ) { $lines[] = $field['label'] . ': ' . $field['value']; } $body = implode( "\n", $lines ); } $body = trim( $body ); if ( '' === $body ) { return null; } $extra = array(); foreach ( $fields as $index => $field ) { if ( in_array( $index, $used, true ) ) { continue; } $label = $field['label']; $n = 2; while ( array_key_exists( $label, $extra ) ) { $label = $field['label'] . ' (' . $n . ')'; ++$n; } $extra[ $label ] = self::truncate( $field['value'], self::MAX_FIELD ); } $payload = array( 'body' => self::truncate( $body, self::MAX_BODY ), 'source' => 'plugin', 'deliver' => true, ); if ( null !== $subject ) { $payload['subject'] = self::truncate( $subject, 500 ); } if ( null !== $sender_name ) { $payload['senderName'] = self::truncate( $sender_name, 200 ); } if ( null !== $sender_email ) { $payload['senderEmail'] = $sender_email; } if ( ! empty( $extra ) ) { $payload['fields'] = $extra; } return $payload; } /** * Converts values to trimmed strings, fills in labels and drops empty fields. * * @param array $raw_fields Fields from an integration. * @return array */ private static function normalize( array $raw_fields ) { $fields = array(); foreach ( $raw_fields as $raw ) { if ( ! is_array( $raw ) ) { continue; } $value = self::stringify( isset( $raw['value'] ) ? $raw['value'] : '' ); if ( '' === $value ) { continue; } $key = isset( $raw['key'] ) && is_scalar( $raw['key'] ) ? trim( (string) $raw['key'] ) : ''; $label = isset( $raw['label'] ) && is_scalar( $raw['label'] ) ? trim( wp_strip_all_tags( (string) $raw['label'] ) ) : ''; $type = isset( $raw['type'] ) && is_scalar( $raw['type'] ) ? strtolower( (string) $raw['type'] ) : ''; if ( '' === $label ) { $label = '' !== $key ? $key : __( 'Field', 'ascue' ); } $fields[] = array( 'key' => $key, 'label' => $label, 'type' => $type, 'value' => $value, ); } return $fields; } /** * Turns a field value into a single string. Lists are joined with ", ". * * @param mixed $value Raw value. * @return string */ private static function stringify( $value ) { if ( is_array( $value ) ) { $parts = array(); array_walk_recursive( $value, function ( $item ) use ( &$parts ) { if ( is_scalar( $item ) && '' !== trim( (string) $item ) ) { $parts[] = trim( (string) $item ); } } ); return implode( ', ', $parts ); } if ( is_bool( $value ) ) { return $value ? '1' : ''; } if ( ! is_scalar( $value ) ) { return ''; } return trim( str_replace( array( "\r\n", "\r" ), "\n", (string) $value ) ); } /** * Index of the first field that satisfies the predicate. * * @param array $fields Normalized fields. * @param callable $predicate Test. * @param int[] $skip Indexes already used. * @return int|null */ private static function find( array $fields, callable $predicate, array $skip = array() ) { foreach ( $fields as $index => $field ) { if ( in_array( $index, $skip, true ) ) { continue; } if ( $predicate( $field ) ) { return $index; } } return null; } /** * Whether the field's key or label matches a pattern. * * @param array $field Normalized field. * @param string $pattern Regular expression. * @return bool */ private static function matches( array $field, $pattern ) { return 1 === preg_match( $pattern, $field['key'] ) || 1 === preg_match( $pattern, $field['label'] ); } /** * Cuts a string to a maximum number of characters. * * @param string $value String. * @param int $max Maximum length in characters. * @return string */ private static function truncate( $value, $max ) { return mb_strlen( $value ) > $max ? mb_substr( $value, 0, $max ) : $value; } }