field_renderer = $field_renderer; } /** * Inject fields before the one expected Form Lite closing tag. * * @param string $content Rendered widget content. * @param string $widget_id Expected widget ID. */ public function inject( string $content, string $widget_id ): string { if ( ! ElementorFormLiteContextFactory::is_valid_widget_id( $widget_id ) ) { return $content; } $opening_tags = array(); $closing_tags = array(); if ( 1 !== preg_match_all( '/]*>/i', $content, $opening_tags, PREG_OFFSET_CAPTURE ) || 1 !== preg_match_all( '/<\/form\s*>/i', $content, $closing_tags, PREG_OFFSET_CAPTURE ) ) { return $content; } $opening_tag = (string) $opening_tags[0][0][0]; $opening_offset = (int) $opening_tags[0][0][1]; $closing_offset = (int) $closing_tags[0][0][1]; if ( $closing_offset <= $opening_offset || ! $this->has_expected_form_class( $opening_tag ) ) { return $content; } $form_markup = substr( $content, $opening_offset, $closing_offset - $opening_offset ); $input_data = $this->extract_input_data( $form_markup ); if ( $this->contains_protection_field( $input_data ) ) { return $content; } $post_id = isset( $input_data['post_id'] ) ? (int) $input_data['post_id'] : 0; $form_id = isset( $input_data['form_id'] ) ? (string) $input_data['form_id'] : ''; if ( $post_id < 1 || $widget_id !== $form_id ) { return $content; } $fields = $this->field_renderer->render_fields( ElementorFormLiteContextFactory::create_form_id( $post_id, $widget_id ) ); return substr_replace( $content, $fields, $closing_offset, 0 ); } /** * Whether the opening form tag has the exact provider class token. * * @param string $opening_tag Opening form tag. */ private function has_expected_form_class( string $opening_tag ): bool { $class_value = $this->extract_attribute( $opening_tag, 'class' ); if ( '' === $class_value ) { return false; } $class_tokens = preg_split( '/\s+/', trim( $class_value ) ); return in_array( 'ehp-form', is_array( $class_tokens ) ? $class_tokens : array(), true ); } /** * Extract input names and values from the one bounded form. * * @param string $form_markup Form markup before its closing tag. * * @return array */ private function extract_input_data( string $form_markup ): array { $input_tags = array(); $input_data = array(); if ( false === preg_match_all( '/]*>/i', $form_markup, $input_tags ) ) { return $input_data; } foreach ( $input_tags[0] as $input_tag ) { $name = $this->extract_attribute( (string) $input_tag, 'name' ); if ( '' === $name ) { continue; } $input_data[ $name ] = $this->extract_attribute( (string) $input_tag, 'value' ); } return $input_data; } /** * Extract a quoted or unquoted HTML attribute from a single tag. * * @param string $tag HTML tag. * @param string $attribute Attribute name. */ private function extract_attribute( string $tag, string $attribute ): string { $pattern = '/\b' . preg_quote( $attribute, '/' ) . '\s*=\s*(?:"([^"]*)"|\'([^\']*)\'|([^\s"\'=<>`]+))/i'; if ( 1 !== preg_match( $pattern, $tag, $matches ) ) { return ''; } $value = ''; foreach ( array( 1, 2, 3 ) as $index ) { if ( isset( $matches[ $index ] ) && '' !== $matches[ $index ] ) { $value = (string) $matches[ $index ]; break; } } return html_entity_decode( $value, ENT_QUOTES | ENT_HTML5, 'UTF-8' ); } /** * Whether ZSF already injected one of its root fields. * * @param array $input_data Input data. */ private function contains_protection_field( array $input_data ): bool { return isset( $input_data[ ProtectionFieldRenderer::HONEYPOT_FIELD ] ) || isset( $input_data[ ProtectionFieldRenderer::TOKEN_FIELD ] ) || isset( $input_data[ ProtectionFieldRenderer::STARTED_AT_FIELD ] ); } }