, declined: string[]} */ public static function get() { $saved = get_option( self::OPTION, array() ); $saved = is_array( $saved ) ? $saved : array(); return array( 'excluded' => array_values( array_filter( (array) ( $saved['excluded'] ?? array() ), 'is_string' ) ), 'custom' => array_values( array_filter( (array) ( $saved['custom'] ?? array() ), 'is_array' ) ), 'declined' => array_values( array_filter( (array) ( $saved['declined'] ?? array() ), 'is_string' ) ), ); } /** * Saves the selection. * * @param string[] $excluded Keys such as "cf7:5" of forms that are not captured. * @param array $custom Picked forms with selector and name. * @param string[] $declined Selectors of found forms that were not picked, so a new scan * leaves them unticked. */ public static function save( array $excluded, array $custom, array $declined = array() ) { $keys = array(); foreach ( $excluded as $key ) { $key = self::sanitize_key( (string) $key ); if ( '' !== $key ) { $keys[ $key ] = true; } } $forms = array(); foreach ( $custom as $form ) { $selector = self::sanitize_selector( (string) ( $form['selector'] ?? '' ) ); if ( '' === $selector || isset( $forms[ $selector ] ) ) { continue; } $forms[ $selector ] = array( 'selector' => $selector, 'name' => mb_substr( sanitize_text_field( (string) ( $form['name'] ?? '' ) ), 0, 191 ), ); } $skipped = array(); foreach ( $declined as $selector ) { $selector = self::sanitize_selector( (string) $selector ); if ( '' !== $selector && ! isset( $forms[ $selector ] ) ) { $skipped[ $selector ] = true; } } update_option( self::OPTION, array( 'excluded' => array_slice( array_keys( $keys ), 0, 500 ), 'custom' => array_slice( array_values( $forms ), 0, self::MAX_CUSTOM ), 'declined' => array_slice( array_keys( $skipped ), 0, 500 ), ) ); } /** * Adds a form by its selector, or renames it when it is there already. * * @param string $selector Selector, see from_input(). * @param string $name Name in the reports. */ public static function add( $selector, $name ) { $current = self::get(); $custom = array( array( 'selector' => $selector, 'name' => $name, ), ); foreach ( $current['custom'] as $form ) { if ( ( $form['selector'] ?? '' ) !== $selector ) { $custom[] = $form; } } self::save( $current['excluded'], $custom, array_diff( $current['declined'], array( $selector ) ) ); } /** * Switches on the forms a scan found that the Forms screen shows as switched on: forms * without a form plugin that are not a newsletter sign-up and were not switched off before. * They count as leads from then on, without a click on Save. * * @param array $forms Forms of the scan. */ public static function pick_found( array $forms ) { $current = self::get(); $custom = $current['custom']; $picked = wp_list_pluck( $custom, 'selector' ); $added = false; foreach ( $forms as $form ) { $selector = (string) ( $form['selector'] ?? '' ); if ( 'custom' !== ( $form['kind'] ?? '' ) || '' !== (string) ( $form['tag'] ?? '' ) || '' === $selector || in_array( $selector, $picked, true ) || in_array( $selector, $current['declined'], true ) || count( $custom ) >= self::MAX_CUSTOM ) { continue; } $custom[] = array( 'selector' => $selector, 'name' => (string) ( $form['name'] ?? '' ), ); $picked[] = $selector; $added = true; } if ( $added ) { self::save( $current['excluded'], $custom, $current['declined'] ); } } /** * A form typed in by hand: its ID as written in the HTML, with or without "#", or a CSS * selector of the form or of an element around it, such as a pop-up. * * @param string $input Raw input. * @return string Selector, '' when invalid. */ public static function from_input( $input ) { $input = trim( wp_strip_all_tags( (string) $input ) ); if ( preg_match( '/^#?([A-Za-z][\w-]{0,99})$/', $input, $match ) ) { return '#' . $match[1]; } return self::sanitize_selector( $input ); } /** * Key of a plugin form, e.g. "cf7:5". * * @param string $plugin Form plugin key. * @param string $form_id Form ID. * @return string */ public static function key( $plugin, $form_id ) { return self::sanitize_key( $plugin . ':' . $form_id ); } /** * Whether submissions of a form are ignored. * * @param string $plugin Form plugin key. * @param string $form_id Form ID. * @return bool */ public static function is_excluded( $plugin, $form_id ) { if ( '' === (string) $form_id ) { return false; } return in_array( self::key( (string) $plugin, (string) $form_id ), self::get()['excluded'], true ); } /** * Selectors of the picked forms, separated by commas. * * @return string */ public static function selectors() { return implode( ', ', wp_list_pluck( self::get()['custom'], 'selector' ) ); } /** * Picked forms that have a name, as selector and name pairs for the tracker. * * @return array */ public static function names() { $names = array(); foreach ( self::get()['custom'] as $form ) { if ( '' !== (string) ( $form['name'] ?? '' ) ) { $names[] = array( (string) $form['selector'], (string) $form['name'] ); } } return $names; } /** * Plugin key and form ID, e.g. "cf7:5" or "elementor:3f2a1bc". * * @param string $key Raw key. * @return string '' when invalid. */ public static function sanitize_key( $key ) { return preg_match( '/^[a-z0-9]{2,20}:[A-Za-z0-9_-]{1,64}$/', $key ) ? $key : ''; } /** * A CSS selector for one form. Anything that could end the selector list or a style * rule is refused. * * @param string $selector Raw selector. * @return string '' when invalid. */ public static function sanitize_selector( $selector ) { $selector = trim( wp_strip_all_tags( $selector ) ); if ( '' === $selector || strlen( $selector ) > 300 || preg_match( '/[,{};<>\\\\]|\/\*/', $selector ) ) { return ''; } return $selector; } }