*/ public static function all_forms() { $out = array(); foreach ( Registry::available() as $adapter ) { foreach ( $adapter->discover_forms() as $form ) { $out[] = array( 'adapter' => $adapter->id(), 'plugin' => $adapter->label(), 'id' => (int) $form['id'], 'title' => (string) $form['title'], ); } } return $out; } /** * Forms added since the admin last saved this screen ("adapter:id" keys). Empty before the first save, * so a site's existing forms are not all reported as new. * * @param array>|null $forms Forms from all_forms(), or null to look them up. * @return string[] */ public static function new_forms( $forms = null ) { $seen = ServerConsent::seen_forms(); if ( null === $seen ) { return array(); } $new = array(); foreach ( null === $forms ? self::all_forms() : $forms as $form ) { if ( ! in_array( (int) $form['id'], array_map( 'intval', isset( $seen[ $form['adapter'] ] ) ? (array) $seen[ $form['adapter'] ] : array() ), true ) ) { $new[] = $form['adapter'] . ':' . $form['id']; } } return $new; } /** * @return void */ public function render() { $settings = ServerConsent::settings(); $adapters = Registry::available(); $woo = Detector::is_active(); ?>

←

'consiva-consent-forms-submit' ) ); ?>
$settings ServerConsent::settings(). * @return void */ private static function render_forms_table( array $settings ) { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only search and paging of the list. $search = isset( $_GET['form_search'] ) ? sanitize_text_field( wp_unslash( $_GET['form_search'] ) ) : ''; $page = isset( $_GET['form_page'] ) ? max( 1, absint( $_GET['form_page'] ) ) : 1; // phpcs:enable $forms = self::all_forms(); $new = self::new_forms( $forms ); if ( '' !== $search ) { $forms = array_values( array_filter( $forms, static function ( $form ) use ( $search ) { return false !== stripos( $form['title'], $search ) || (string) $form['id'] === $search; } ) ); } // New forms first, so they are seen; then by title. usort( $forms, static function ( $a, $b ) use ( $new ) { $by_new = (int) in_array( $b['adapter'] . ':' . $b['id'], $new, true ) - (int) in_array( $a['adapter'] . ':' . $a['id'], $new, true ); return 0 !== $by_new ? $by_new : strcasecmp( $a['title'], $b['title'] ); } ); $total = count( $forms ); $pages = max( 1, (int) ceil( $total / self::PER_PAGE ) ); $page = min( $page, $pages ); $shown = array_slice( $forms, ( $page - 1 ) * self::PER_PAGE, self::PER_PAGE ); $activity = ServerConsent::activity(); $purposes = ServerConsent::purpose_labels(); $types = ServerConsent::form_type_labels(); if ( $new ) { echo '

' . esc_html( sprintf( /* translators: %d: number of forms. */ _n( '%d form was added since you last saved this screen. It is listed first: decide whether it should have the opt-in, then save.', '%d forms were added since you last saved this screen. They are listed first: decide whether each should have the opt-in, then save.', count( $new ), 'consiva-ai' ), count( $new ) ) ) . '

'; } ?> 1 ) { echo ''; echo '

' . esc_html__( 'Saving changes only the forms on this page. Save before moving to another page.', 'consiva-ai' ) . '

'; } } /** * Queue health (§8.3): pending events, and an honest count of anything lost. * * @return void */ private static function render_buffer_status() { $status = LocalBuffer::status(); if ( $status['limit_reached_at'] && gmdate( 'Y-m', $status['limit_reached_at'] ) === gmdate( 'Y-m' ) ) { // Consiva's cap is per calendar month (UTC) and soft: the records were still stored. /* translators: %s: Consiva app host, e.g. app.consiva.ai. */ echo '

' . esc_html( sprintf( __( 'Your Consiva plan\'s form-consent allowance for this month is used up. Consiva is still recording opt-ins, but check your plan at %s.', 'consiva-ai' ), Endpoints::app_host() ) ) . '

'; } if ( $status['dropped'] > 0 || $status['rejected'] > 0 ) { ?>

0 ) { echo ''; } } /** * @return void */ public function handle_save() { if ( ! current_user_can( Onboarding::CAPABILITY ) ) { wp_die( esc_html__( 'You do not have permission to change the Consiva setup.', 'consiva-ai' ), '', array( 'response' => 403 ) ); } check_admin_referer( self::ACTION_SAVE ); $current = ServerConsent::settings(); // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- every key is checked against the known adapters and forms, every value is cast or allowlisted below. $posted = isset( $_POST['forms'] ) && is_array( $_POST['forms'] ) ? wp_unslash( $_POST['forms'] ) : array(); $shown = isset( $_POST['shown'] ) && is_array( $_POST['shown'] ) ? wp_unslash( $_POST['shown'] ) : array(); $per_form = isset( $_POST['form_settings'] ) && is_array( $_POST['form_settings'] ) ? wp_unslash( $_POST['form_settings'] ) : array(); // phpcs:enable // Only the forms shown on the saved page change: the list is paged, and the other pages keep theirs. $forms = array(); $form_settings = $current['form_settings']; $seen = array(); foreach ( Registry::available() as $adapter ) { $id = $adapter->id(); $known = array_map( 'intval', wp_list_pluck( $adapter->discover_forms(), 'id' ) ); $on_page = array_values( array_intersect( isset( $shown[ $id ] ) ? array_map( 'intval', (array) $shown[ $id ] ) : array(), $known ) ); $ticked = isset( $posted[ $id ] ) ? array_map( 'intval', (array) $posted[ $id ] ) : array(); $before = isset( $current['forms'][ $id ] ) ? array_map( 'intval', (array) $current['forms'][ $id ] ) : array(); $ids = array_merge( array_diff( array_intersect( $before, $known ), $on_page ), array_intersect( $ticked, $on_page ) ); if ( $ids ) { $forms[ $id ] = array_values( array_unique( $ids ) ); } foreach ( $on_page as $form_id ) { $row = isset( $per_form[ $id ][ $form_id ] ) && is_array( $per_form[ $id ][ $form_id ] ) ? $per_form[ $id ][ $form_id ] : array(); $own = array(); $text = isset( $row['label'] ) ? mb_substr( sanitize_text_field( (string) $row['label'] ), 0, self::LABEL_MAX ) : ''; if ( '' !== $text ) { $own['label'] = $text; } if ( isset( $row['purpose'] ) && in_array( $row['purpose'], ServerConsent::PURPOSES, true ) && ServerConsent::PURPOSE !== $row['purpose'] ) { $own['purpose'] = $row['purpose']; } if ( isset( $row['form_type'] ) && in_array( $row['form_type'], ServerConsent::FORM_TYPES, true ) && 'Other' !== $row['form_type'] ) { $own['form_type'] = $row['form_type']; } if ( $own ) { $form_settings[ $id ][ $form_id ] = $own; } else { unset( $form_settings[ $id ][ $form_id ] ); // Back to the defaults: nothing stored. } } // Settings of forms that no longer exist are dropped. if ( isset( $form_settings[ $id ] ) ) { $form_settings[ $id ] = array_intersect_key( $form_settings[ $id ], array_flip( $known ) ); if ( ! $form_settings[ $id ] ) { unset( $form_settings[ $id ] ); } } $seen[ $id ] = $known; } $form_label = isset( $_POST['form_label'] ) ? mb_substr( sanitize_text_field( wp_unslash( $_POST['form_label'] ) ), 0, self::LABEL_MAX ) : ''; $checkout_label = isset( $_POST['checkout_label'] ) ? mb_substr( sanitize_text_field( wp_unslash( $_POST['checkout_label'] ) ), 0, self::LABEL_MAX ) : ''; ServerConsent::save( array( 'forms' => $forms, 'form_label' => $form_label, 'checkout' => ! empty( $_POST['checkout'] ), 'checkout_label' => $checkout_label, 'form_settings' => $form_settings, ) ); ServerConsent::save_seen_forms( $seen ); Notices::push( 'success', __( 'Saved.', 'consiva-ai' ) ); // Back to the same page of the list, with the same search. $back = self::url(); if ( ! empty( $_POST['form_page'] ) ) { $back = add_query_arg( 'form_page', absint( $_POST['form_page'] ), $back ); } if ( ! empty( $_POST['form_search'] ) ) { $back = add_query_arg( 'form_search', rawurlencode( sanitize_text_field( wp_unslash( $_POST['form_search'] ) ) ), $back ); } wp_safe_redirect( $back ); exit; } }