null,
'lead' => null,
'bg' => null,
'text' => null,
'image' => null,
'rows' => null,
'slot' => false,
'phone' => null,
'email' => null,
'street' => null,
'city' => null,
'hours' => null,
'values' => array(),
);
}
/**
* Hooks up.
*
* @return void
*/
public static function hooks() {
add_action( 'init', array( __CLASS__, 'register' ), 20 );
}
/**
* Registers the group and every section.
*
* @return void
*/
public static function register() {
if ( ! function_exists( 'register_block_pattern' ) ) {
return;
}
if ( function_exists( 'register_block_pattern_category' ) ) {
register_block_pattern_category(
self::CATEGORY,
array( 'label' => __( '2ranq Forms', '2ranq-forms' ) )
);
}
foreach ( self::all() as $key => $pattern ) {
register_block_pattern(
'2ranq-forms/' . $key,
array(
'title' => $pattern['title'],
'description' => $pattern['description'],
'categories' => array( self::CATEGORY ),
'keywords' => array( 'contact', 'form', '2ranq' ),
'content' => $pattern['content'],
)
);
}
}
/**
* The company details, out of the settings.
*
* @return array
*/
private static function details() {
$saved = get_option( 'ranq_forms_company', array() );
$saved = is_array( $saved ) ? $saved : array();
$filled = array();
foreach ( $saved as $key => $value ) {
if ( is_string( $value ) && '' !== trim( $value ) ) {
$filled[ $key ] = $value;
}
}
// The order matters. The trade details come first, because they are what makes
// the example look like a real place. Over them go the details the site owner
// typed in, because those are their true ones. That way the example is full
// before the owner enters anything, and the moment they do, theirs wins.
$base = array(
'name' => get_bloginfo( 'name' ),
'street' => __( '1200 Market Street, Suite 400', '2ranq-forms' ),
'city' => __( 'San Francisco, CA 94103', '2ranq-forms' ),
'phone' => '(415) 555-0134',
'email' => (string) get_option( 'admin_email' ),
'hours' => __( 'Monday to Friday, 9am to 5pm', '2ranq-forms' ),
);
foreach ( array( 'name', 'street', 'city', 'phone', 'email', 'hours' ) as $key ) {
if ( isset( self::$biz[ $key ] ) && '' !== self::$biz[ $key ] ) {
$base[ $key ] = self::$biz[ $key ];
}
}
$out = array_merge( $base, $filled );
// What is typed into the widget itself beats both the trade and the global
// ones, because it is closest to what a person is looking at right now.
foreach ( array( 'phone', 'email', 'street', 'city', 'hours' ) as $key ) {
if ( isset( self::$opts[ $key ] ) && is_string( self::$opts[ $key ] ) && '' !== self::$opts[ $key ] ) {
$out[ $key ] = self::$opts[ $key ];
}
}
return $out;
}
/**
* The phone number in a shape that can be dialled with a tap.
*
* @param string $phone The number as it is written.
*
* @return string
*/
/**
* The custom properties, out of what a person changed.
*
* ONLY what differs from the default is printed, so the record is short or
* not there at all. The defaults sit in the style sheet, once.
*
* @param string $layout The pattern key.
*
* @return string
*/
private static function css_vars( $layout ) {
$vrednosti = self::$opts['values'];
if ( ! is_array( $vrednosti ) || ! $vrednosti ) {
return '';
}
$opis = Ranq_Forms_Widget::controls_for( $layout );
$out = '';
foreach ( $vrednosti as $kljuc => $vrednost ) {
if ( ! isset( $opis[ $kljuc ]['var'] ) ) {
continue;
}
$jedinica = isset( $opis[ $kljuc ]['unit'] ) ? $opis[ $kljuc ]['unit'] : '';
if ( ! $jedinica && 'size' === $opis[ $kljuc ]['type'] ) {
$jedinica = 'px';
}
$out .= '--' . $opis[ $kljuc ]['var'] . ':' . $vrednost . $jedinica . ';';
}
return $out;
}
/**
* One value a person changed, or the default.
*
* @param string $kljuc The key.
* @param string $inace What to use when it was not changed.
*
* @return string
*/
private static function val( $kljuc, $inace ) {
$v = self::$opts['values'];
return ( is_array( $v ) && isset( $v[ $kljuc ] ) && '' !== $v[ $kljuc ] ) ? (string) $v[ $kljuc ] : $inace;
}
private static function mail_link( $email, $style = '' ) {
$email = (string) $email;
// An incomplete address is printed as plain text. A link to "milos@" leads
// nowhere, and a visitor would click it.
if ( ! is_email( $email ) ) {
return '' . esc_html( $email ) . '';
}
return '' . esc_html( $email ) . '';
}
private static function tel( $phone ) {
return 'tel:' . preg_replace( '/[^0-9+]/', '', $phone );
}
/**
* An icon as an image embedded in the address itself.
*
* Why this way and not as a file: the icon then costs no extra request to the
* server, it cannot go missing, and a person can still swap it for their own,
* because it is an ordinary image in the page.
*
* @param string $path The drawing path.
* @param string $color The colour.
*
* @return string
*/
private static function icon( $path, $color = '#5b4be0', $width = '1.6' ) {
$svg = '';
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- this is not code hiding, it is an image embedded in an address.
return 'data:image/svg+xml;base64,' . base64_encode( $svg );
}
/**
* The icon drawings.
*
* @return array
*/
private static function shapes() {
return array(
'car' => '',
'plane' => '',
'phone' => '',
'pin' => '',
'mail' => '',
'clock' => '',
'check' => '',
'chat' => '',
);
}
/**
* One row of detail: an icon and then the text.
*
* @param string $shape The drawing key.
* @param string $title The label.
* @param string $value The value, which may be a link.
* @param string $color The icon colour.
* @param bool $light Whether the text is light.
*
* @return string
*/
private static function row( $shape, $title, $value, $color = '#5b4be0', $light = false ) {
$shapes = self::shapes();
$icon = self::icon( isset( $shapes[ $shape ] ) ? $shapes[ $shape ] : $shapes['pin'], $color );
$attr = $light ? ' {"style":{"color":{"text":"#ffffff"}}}' : '';
$class = $light ? ' class="has-text-color" style="color:#ffffff"' : '';
// `isStackedOnMobile` set to false is REQUIRED. Without it WordPress drops the
// icon into a row of its own above the text on a phone, and the row of detail
// looks broken. Checked at a width of 390.
return ''
. '
'
. ''
. '
'
. ''
. ''
. ''
. '
'
. ''
. '
'
. ''
. '
' . esc_html( $title ) . ' ' . $value . '
'
. ''
. '
'
. '
';
}
/**
* Our form as a block. The form is chosen after inserting.
*
* @return string
*/
private static function form_block() {
// In the live preview an empty slot goes where the form is, so the builder can
// drop the REAL form a person is assembling into it. That way the card shows
// both the section and the form, and every change lands inside at once.
if ( self::$opts['slot'] ) {
return '';
}
return '';
}
/**
* A place for the image, with an instruction instead of an empty hole.
*
* @param string $radius The corner radius.
*
* @return string
*/
private static function image_block( $radius = '14px', $class = '' ) {
// An image MUST have a source. An `` with no source draws nothing, so a
// hole would be left where the image goes and nobody would know it goes there.
//
// A scene is drawn, not a camera icon. The icon looked like a broken image,
// so the whole section felt unfinished before a person even got round to
// putting their own in.
$svg = '';
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- an image embedded in an address, not code hiding.
$src = 'data:image/svg+xml;base64,' . base64_encode( $svg );
// The trade photograph, when it has arrived in `assets/img/`. While it has
// not, the drawn scene stays, so the section works without a single photo.
if ( isset( self::$biz['photo'] ) && '' !== self::$biz['photo'] ) {
$photo = Ranq_Forms_Business::photo( self::$biz['photo'] );
if ( $photo ) {
$src = esc_url( $photo );
}
}
// Your own image goes through `esc_url`. The embedded thumbnail must not,
// because `esc_url` does not let `data:` through and the address would be empty.
if ( is_string( self::$opts['image'] ) && '' !== self::$opts['image'] ) {
$src = esc_url( self::$opts['image'] );
}
// The width at a hundred percent: without it the image sits at its natural
// size and stays smaller than the column, so the layout looks crooked.
return ''
. ''
. ''
. '';
}
/* ------------------------------------------------------------------
Six families of layout
The half and half split was measured, not chosen: ten out of twelve of our
sites that split a section in two use a split between 47 and 55 percent.
------------------------------------------------------------------ */
/**
* 2ranq.com, taken value by value.
*
* @param array $b The trade.
* @param array $d The details.
*
* @return string
*/
private static function exact_2ranq( $b, $d ) {
// The rows come from the SAME list as every other section, only in the look
// taken from 2ranq. Were they hard coded here, the "which details are shown"
// switches would not work on this section, and nobody would know why.
$redovi = array(
'mail' => array( __( 'Email', '2ranq-forms' ), is_email( $d['email'] ) ? 'mailto:' . esc_attr( $d['email'] ) : '', esc_html( $d['email'] ) ),
'phone' => array( __( 'Phone', '2ranq-forms' ), esc_url( self::tel( $d['phone'] ) ), esc_html( $d['phone'] ) ),
'pin' => array( __( 'Address', '2ranq-forms' ), '', esc_html( $d['street'] ) . ', ' . esc_html( $d['city'] ) ),
'clock' => array( __( 'Open', '2ranq-forms' ), '', esc_html( $d['hours'] ) ),
);
$wanted = self::$opts['rows'];
$mark = self::$opts['slot'];
// A table with rules. The label is 112 pixels wide, 12/400, uppercase, letter
// spacing 1.5. The value 16/500. A 1 pixel #2b2b2b rule above every row and
// under the last one. Spacing 16 above and below. All read off the site.
$tabela = '
';
// The `data-ranq-part` markers are REQUIRED, otherwise the live preview in the
// builder does not know what to change while a person types a heading and text.
$left = ''
. '
'
. esc_html( self::val( 'fine_text', __( 'We answer within one working day.', '2ranq-forms' ) ) )
. '
';
// A 715 to 585 split out of 1300, read off the site.
// The `wrap` marker is REQUIRED, otherwise a colour change in the side panel
// has nothing to take hold of and the preview does not change.
$vars = self::css_vars( 'site-2ranq' );
return ''
. '
'
. '
'
. '
' . $left . '
'
. '
' . $right . '
'
. '
'
. '
';
}
/**
* transfergaja.rs, taken value by value.
*
* @param array $b The pattern.
* @param array $d The details.
*
* @return string
*/
private static function exact_gaja( $b, $d ) {
$mark = self::$opts['slot'];
$shapes = self::shapes();
// The icons are line drawings and large, 50 pixels, in the accent colour. A
// thinner line than usual, because the original uses thin drawings.
$boja = self::val( 'icon', '#f8b54a' );
$redovi = array(
'phone' => array(
'phone',
__( 'Call us', '2ranq-forms' ),
esc_url( self::tel( $d['phone'] ) ),
esc_html( $d['phone'] ),
),
'mail' => array(
'mail',
__( 'Send us an email', '2ranq-forms' ),
is_email( $d['email'] ) ? 'mailto:' . esc_attr( $d['email'] ) : '',
esc_html( $d['email'] ),
),
'pin' => array(
'pin',
__( 'Where we are', '2ranq-forms' ),
'',
esc_html( $d['street'] ) . ', ' . esc_html( $d['city'] ),
),
'clock' => array(
'clock',
__( 'When we work', '2ranq-forms' ),
'',
esc_html( $d['hours'] ),
),
);
$wanted = self::$opts['rows'];
$levo = '
';
}
/**
* The booking card, taken from transfergaja.rs.
*
* Unlike the other sections, there is no column of details here. The whole
* card is the form, because that is what the original does: a person comes to
* book a ride, not to read an address.
*
* @param array $b The trade.
* @param array $d The details for display.
*
* @return string
*/
private static function exact_gaja_book( $b, $d ) {
unset( $d );
$mark = self::$opts['slot'];
$shapes = self::shapes();
$oblik = self::val( 'badge_shape', 'car' );
$ikona = isset( $shapes[ $oblik ] ) ? $shapes[ $oblik ] : $shapes['car'];
$zaglavlje = '
';
}
/**
* The section taken from ortopedijauna.rs.
*
* No columns. One strong sentence across the full width, then the form on a
* white card in the middle. That is what the original does.
*
* @param array $b The trade.
* @param array $d The details for display.
*
* @return string
*/
private static function exact_una( $b, $d ) {
$mark = self::$opts['slot'];
$veza = ''
. esc_html( self::val( 'call_text', __( 'give us a call', '2ranq-forms' ) ) ) . '';
$gore = '