$verifier, 'challenge' => $challenge, ); } /** * Exchanges the grant from Connect with Izini for the site token. * * @return array{ok: bool, message: string} */ function izini_exchange_grant( $grant, $verifier ) { $response = wp_remote_post( IZINI_SITE_API . 'token', array( 'timeout' => 15, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( array( 'grant' => $grant, 'verifier' => $verifier, 'site' => home_url( '/' ), ) ), ) ); $body = is_wp_error( $response ) ? null : json_decode( wp_remote_retrieve_body( $response ), true ); if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) || empty( $body['token'] ) || ! is_string( $body['token'] ) ) { return array( 'ok' => false, 'message' => __( 'The banner is connected, but statistics and scans could not be switched on. Press "Connect with Izini" again to retry.', 'izini' ), ); } update_option( IZINI_TOKEN_OPTION, $body['token'], false ); delete_transient( 'izini_site_summary' ); izini_sync_scripts( array(), true ); return array( 'ok' => true, 'message' => __( 'Statistics and cookie scans are on for this site.', 'izini' ), ); } /** * A call to Izini with the site token. Forgets a token Izini no longer * accepts, so the Izini page offers to connect again. * * @return array{status: int, body: array}|null Null when Izini could not be reached. */ function izini_site_request( $method, $path ) { $token = izini_site_token(); if ( '' === $token ) { return null; } $response = wp_remote_request( IZINI_SITE_API . $path, array( 'method' => $method, 'timeout' => 15, 'headers' => array( 'Authorization' => 'Bearer ' . $token ), ) ); if ( is_wp_error( $response ) ) { return null; } $status = (int) wp_remote_retrieve_response_code( $response ); $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( 401 === $status ) { delete_option( IZINI_TOKEN_OPTION ); delete_transient( 'izini_site_summary' ); } return array( 'status' => $status, 'body' => is_array( $body ) ? $body : array(), ); } /** Tells Izini to revoke this site's token, then forgets it. */ function izini_revoke_site_token() { if ( '' !== izini_site_token() ) { izini_site_request( 'DELETE', 'token' ); } delete_option( IZINI_TOKEN_OPTION ); delete_transient( 'izini_site_summary' ); } /** Another project, or none: the token belonged to the old one. */ function izini_on_code_change( $old, $new ) { if ( $old !== $new ) { izini_revoke_site_token(); } } add_action( 'update_option_' . IZINI_OPTION, 'izini_on_code_change', 10, 2 ); /** * Consent totals for the last 30 days and where the banner was seen, kept * for ten minutes. * * @return array|null */ function izini_site_summary() { $cached = get_transient( 'izini_site_summary' ); if ( is_array( $cached ) ) { return $cached; } $result = izini_site_request( 'GET', 'summary?days=30' ); if ( ! $result || 200 !== $result['status'] ) { return null; } set_transient( 'izini_site_summary', $result['body'], 10 * MINUTE_IN_SECONDS ); return $result['body']; } /** The consent counts as numbers, with each share of the total. */ function izini_consent_breakdown( $summary ) { $t = isset( $summary['consents']['totals'] ) && is_array( $summary['consents']['totals'] ) ? $summary['consents']['totals'] : array(); $total = isset( $t['total'] ) ? (int) $t['total'] : 0; $rows = array( 'all_accepted' => __( 'Accepted all', 'izini' ), 'some_accepted' => __( 'Accepted some', 'izini' ), 'all_refused' => __( 'Refused all optional', 'izini' ), ); $out = array(); foreach ( $rows as $key => $label ) { $n = isset( $t[ $key ] ) ? (int) $t[ $key ] : 0; $out[] = array( 'label' => $label, 'count' => $n, 'percent' => $total > 0 ? (int) round( 100 * $n / $total ) : 0, ); } return array( 'total' => $total, 'rows' => $out, ); } /** * Renders the consent counts: as the Izini page's facts list, or as a plain * list on the WordPress dashboard (where the Izini styles are not loaded). */ function izini_render_consent_totals( $summary, $plain = false ) { $b = izini_consent_breakdown( $summary ); if ( 0 === $b['total'] ) { echo '

' . esc_html__( 'No choices recorded in the last 30 days.', 'izini' ) . '

'; return; } echo '

' . esc_html( number_format_i18n( $b['total'] ) ) . ' ' . esc_html__( 'choices in the last 30 days', 'izini' ) . '

'; echo $plain ? '' : ''; } /** The consent counts on the WordPress dashboard, for administrators of a connected site. */ function izini_add_dashboard_widget() { if ( current_user_can( 'manage_options' ) && '' !== izini_site_token() ) { wp_add_dashboard_widget( 'izini_consent', __( 'Izini: consent on this site', 'izini' ), 'izini_render_dashboard_widget' ); } } add_action( 'wp_dashboard_setup', 'izini_add_dashboard_widget' ); function izini_render_dashboard_widget() { $summary = izini_site_summary(); if ( ! $summary ) { echo '

' . esc_html__( 'Izini could not be reached just now.', 'izini' ) . '

'; return; } izini_render_consent_totals( $summary, true ); echo '

' . esc_html__( 'Full statistics in Izini', 'izini' ) . '

'; } /** * Sends Izini this site's scripts and rules (integrations tracker CD6), so the * project's Cookies page shows what each script waits for and the cookies its * vendor documents. File addresses (host and path) of the scripts seen on the * site or found by a scan, and the rules with their categories. Inline code * goes only when the administrator gave it a rule. Izini drops anything that * looks like a personal detail. At most once a day unless something changed. * * @param array $scanned Scripts a scan just found (izini_parse_scripts()). * @param bool $force Send even if sent today (a rule changed, a scan ran). */ function izini_sync_scripts( $scanned = array(), $force = false ) { if ( '' === izini_site_token() || ( ! $force && get_transient( 'izini_scripts_synced' ) ) ) { return; } $rules = izini_script_rules(); $list = array(); foreach ( array_merge( $scanned, izini_seen_scripts() ) as $s ) { if ( isset( $s['kind'], $s['match'] ) && 'src' === $s['kind'] ) { $rule = izini_rule_for( $s['match'], '', $rules ); $list[ 'src|' . $s['match'] ] = array( 'kind' => 'src', 'match' => $s['match'], 'category' => $rule ? $rule['category'] : null, ); } } foreach ( $rules as $rule ) { $key = $rule['kind'] . '|' . $rule['match']; if ( ! isset( $list[ $key ] ) ) { $list[ $key ] = array( 'kind' => $rule['kind'], 'match' => $rule['match'], 'category' => $rule['category'], ); } } $token = izini_site_token(); $response = wp_remote_post( IZINI_SITE_API . 'scripts', array( 'timeout' => 15, 'headers' => array( 'Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json', ), 'body' => wp_json_encode( array( 'scripts' => array_slice( array_values( $list ), 0, 150 ) ) ), ) ); if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) { set_transient( 'izini_scripts_synced', 1, DAY_IN_SECONDS ); } }