on( 'theme_compat' ) ) { return; } $closes = array_flip( self::close_buttons( $doc ) ); $box = null; // Pop-up or fly-in waiting for its title. $name = null; // Name field waiting to learn whether a last-name field follows. foreach ( $doc['elements'] as $ord => $element ) { $class = isset( $element['attrs']['class'] ) && is_string( $element['attrs']['class'] ) ? $element['attrs']['class'] : ''; if ( 'DIV' === $element['tag'] && Compat::has_class( $class, 'et_bloom_optin' ) ) { $box = self::is_overlay( $class ) && ! isset( $element['attrs']['role'] ) ? $ord : null; $name = self::autocomplete( $doc, $name, 'name', $plan ); } elseif ( 'DIV' === $element['tag'] && Compat::has_class( $class, 'et_bloom_form_content' ) && ! isset( $element['attrs']['aria-live'] ) ) { // Bloom's script adds its error and thank-you messages in here. Only // added content is read, not what people type in the fields. $plan->set( $ord, 'aria-live', 'polite' ); $plan->set( $ord, 'aria-relevant', 'additions' ); $plan->count( 'theme_compat' ); } elseif ( 'H2' === $element['tag'] && null !== $box && Compat::has_class( $element['parent']['class'], 'et_bloom_form_text' ) ) { if ( '' !== $element['text'] ) { // No aria-modal: nothing keeps focus inside the box. $plan->set( $box, 'role', 'dialog' ); $plan->set( $box, 'aria-label', $element['text'] ); $plan->count( 'theme_compat' ); } $box = null; } elseif ( 'INPUT' === $element['tag'] ) { $field = self::field( $element ); if ( 'last' === $field ) { $name = self::autocomplete( $doc, $name, 'given-name', $plan ); self::autocomplete( $doc, $ord, 'family-name', $plan ); } elseif ( '' !== $field ) { $name = self::autocomplete( $doc, $name, 'name', $plan ); if ( 'name' === $field ) { $name = $ord; } else { self::autocomplete( $doc, $ord, 'email', $plan ); } } } elseif ( isset( $closes[ $ord ] ) ) { $plan->keyboard_button( $element, __( 'Close', 'fixlight' ) ); } } self::autocomplete( $doc, $name, 'name', $plan ); } /** * Problems for the server check. * * @param array $doc Analysis. * @return array */ public static function problems( array $doc ) { $found = array(); foreach ( self::close_buttons( $doc ) as $ord ) { if ( ! isset( $doc['elements'][ $ord ]['attrs'][ Compat::KB_MARK ] ) ) { $found[] = ''; } } return $found ? array( 'mouse-only-control' => $found ) : array(); } /** * CSS. * * @return string */ public static function css() { return '.et_bloom .et_bloom_close_button .fixlight-kb{border-radius:50%}' . '.et_bloom .et_bloom_close_button .fixlight-kb:focus-visible{outline:3px solid #1d2327!important;outline-offset:2px!important;box-shadow:0 0 0 2px #fff!important}'; } /** * Whether an opt-in is a pop-up or fly-in (the ones with a close button). * * @param string $classes Class attribute of the opt-in. * @return bool */ private static function is_overlay( $classes ) { return Compat::has_class( $classes, 'et_bloom_popup' ) || Compat::has_class( $classes, 'et_bloom_flyin' ); } /** * Ordinals of the close buttons of pop-ups and fly-ins. Inline and widget * forms have one too, but Bloom's CSS hides it. * * @param array $doc Analysis. * @return int[] */ private static function close_buttons( array $doc ) { $found = array(); $overlay = false; foreach ( $doc['elements'] as $ord => $element ) { $class = isset( $element['attrs']['class'] ) && is_string( $element['attrs']['class'] ) ? $element['attrs']['class'] : ''; if ( 'DIV' === $element['tag'] && Compat::has_class( $class, 'et_bloom_optin' ) ) { $overlay = self::is_overlay( $class ); } elseif ( $overlay && 'SPAN' === $element['tag'] && Compat::has_class( $class, 'et_bloom_close_button' ) && ! $element['hidden'] ) { $found[] = $ord; } } return $found; } /** * Which Bloom field an input is: name, last, email or ''. * * @param array $element Recorded input. * @return string */ private static function field( array $element ) { $type = isset( $element['attrs']['type'] ) && is_string( $element['attrs']['type'] ) ? strtolower( $element['attrs']['type'] ) : 'text'; if ( 'P' !== $element['parent']['tag'] || $element['hidden'] || ! in_array( $type, array( 'text', 'email' ), true ) ) { return ''; } foreach ( array( 'name', 'last', 'email' ) as $field ) { if ( Compat::has_class( $element['parent']['class'], 'et_bloom_subscribe_' . $field ) ) { return $field; } } return ''; } /** * Adds an autocomplete token to a field that has none (WCAG 1.3.5), so * browsers can fill it in and people know what it asks for. * * @param array $doc Analysis. * @param int|null $ord Input ordinal, or null for none. * @param string $token Autocomplete token. * @param Compat_Plan $plan Plan. * @return null Always null, for the caller's "waiting" field. */ private static function autocomplete( array $doc, $ord, $token, Compat_Plan $plan ) { if ( null !== $ord && ! isset( $doc['elements'][ $ord ]['attrs']['autocomplete'] ) ) { $plan->set( $ord, 'autocomplete', $token ); $plan->count( 'theme_compat' ); } return null; } }