rest_url( 'ago-legal/v1' ),
'nonce' => wp_create_nonce( 'wp_rest' ),
'settings' => self::get_settings(),
] );
}
/* ───── Frontend Assets ───── */
public function frontend_assets(): void {
$settings = self::get_settings();
if ( empty( $settings['banner_enabled'] ) ) {
// Expose the consent API for downstream scripts even without the banner.
wp_enqueue_script( 'agolegal-consent-api', AGOLEGAL_URL . 'assets/js/consent-api.js', [], AGOLEGAL_VERSION, false );
wp_localize_script( 'agolegal-consent-api', 'agolegalConfig', [
'enabled' => false,
'categories' => [],
'gaMeasurementId' => '',
] );
return;
}
wp_enqueue_style( 'agolegal-banner', AGOLEGAL_URL . 'assets/css/banner.css', [], AGOLEGAL_VERSION );
wp_enqueue_script( 'agolegal-consent-api', AGOLEGAL_URL . 'assets/js/consent-api.js', [], AGOLEGAL_VERSION, false );
wp_enqueue_script( 'agolegal-banner', AGOLEGAL_URL . 'assets/js/banner.js', [ 'agolegal-consent-api' ], AGOLEGAL_VERSION, true );
$i18n = [
'title' => $settings['banner_title'] ?: __( 'We use cookies', 'ago-legal' ),
'message' => $settings['banner_message'] ?: __( 'This website uses cookies to ensure you get the best experience.', 'ago-legal' ),
'acceptAll' => __( 'Accept All', 'ago-legal' ),
'rejectAll' => __( 'Reject All', 'ago-legal' ),
'policyLink' => $settings['cookie_policy_url'] ?: '',
'policyText' => __( 'Cookie Policy', 'ago-legal' ),
'customize' => __( 'Customize', 'ago-legal' ),
'save' => __( 'Save Preferences', 'ago-legal' ),
'necessary' => __( 'Necessary', 'ago-legal' ),
'analytics' => __( 'Analytics', 'ago-legal' ),
'marketing' => __( 'Marketing', 'ago-legal' ),
'functional' => __( 'Functional', 'ago-legal' ),
'necessaryDesc' => __( 'Essential for the website to function.', 'ago-legal' ),
'analyticsDesc' => __( 'Help us understand how visitors use the site.', 'ago-legal' ),
'marketingDesc' => __( 'Used for targeted advertising.', 'ago-legal' ),
'functionalDesc' => __( 'Enable enhanced features and personalization.', 'ago-legal' ),
];
$categories = [
'necessary' => true,
'analytics' => ! empty( $settings['cat_analytics'] ),
'marketing' => ! empty( $settings['cat_marketing'] ),
'functional' => ! empty( $settings['cat_functional'] ),
];
wp_localize_script( 'agolegal-consent-api', 'agolegalConfig', [
'enabled' => true,
'categories' => $categories,
'gaMeasurementId' => $settings['ga_measurement_id'] ?? '',
'restUrl' => rest_url( 'ago-legal/v1/consent' ),
'nonce' => wp_create_nonce( 'wp_rest' ),
] );
wp_localize_script( 'agolegal-banner', 'agolegalBanner', [
'i18n' => $i18n,
'position' => $settings['banner_position'] ?? 'bottom',
'style' => $settings['banner_style'] ?? 'bar',
] );
// Floating "cookie preferences" button behaviour. Runs in the footer where the
// button markup already exists in the DOM (rendered at wp_footer priority 5).
$prefs_js = "(function(){var b=document.getElementById('ago-cookie-prefs-btn');if(!b)return;"
. "if(localStorage.getItem('ago_cookie_consent')){b.style.display='block';}"
. "b.addEventListener('click',function(){localStorage.removeItem('ago_cookie_consent');b.style.display='none';location.reload();});})();";
wp_add_inline_script( 'agolegal-banner', $prefs_js );
}
public function render_banner(): void {
$settings = self::get_settings();
if ( empty( $settings['banner_enabled'] ) ) return;
echo '
';
}
public function render_floating_btn(): void {
$settings = self::get_settings();
if ( empty( $settings['banner_enabled'] ) ) return;
printf(
'',
esc_attr__( 'Cookie Preferences', 'ago-legal' )
);
}
/* ───── Shortcodes ───── */
public function shortcode_privacy(): string {
return PolicyGenerator::privacy_policy( self::get_settings() );
}
public function shortcode_cookies(): string {
return PolicyGenerator::cookie_policy( self::get_settings() );
}
/* ───── REST API ───── */
public function register_routes(): void {
$ns = 'ago-legal/v1';
register_rest_route( $ns, '/settings', [
[ 'methods' => 'GET', 'callback' => [ $this, 'rest_get_settings' ], 'permission_callback' => [ $this, 'can_manage' ] ],
[ 'methods' => 'POST', 'callback' => [ $this, 'rest_save_settings' ], 'permission_callback' => [ $this, 'can_manage' ] ],
] );
/* Public by design: the cookie banner serves anonymous visitors, so these
* endpoints cannot require authentication. They only record the visitor's
* own consent choice into this site's database. Input is sanitized. */
register_rest_route( $ns, '/consent', [
'methods' => 'POST', 'callback' => [ $this, 'rest_save_consent' ], 'permission_callback' => '__return_true',
] );
register_rest_route( $ns, '/update-consent', [
'methods' => 'POST', 'callback' => [ $this, 'rest_save_consent' ], 'permission_callback' => '__return_true',
] );
register_rest_route( $ns, '/consents', [
'methods' => 'GET', 'callback' => [ $this, 'rest_get_consents' ], 'permission_callback' => [ $this, 'can_manage' ],
] );
register_rest_route( $ns, '/cookie-scan', [
'methods' => 'POST', 'callback' => [ $this, 'rest_cookie_scan' ], 'permission_callback' => [ $this, 'can_manage' ],
] );
register_rest_route( $ns, '/rat', [
[ 'methods' => 'GET', 'callback' => [ $this, 'rest_rat_list' ], 'permission_callback' => [ $this, 'can_manage' ] ],
[ 'methods' => 'POST', 'callback' => [ $this, 'rest_rat_save' ], 'permission_callback' => [ $this, 'can_manage' ] ],
] );
register_rest_route( $ns, '/rat/(?P\d+)', [
'methods' => 'DELETE',
'callback' => [ $this, 'rest_rat_delete' ],
'permission_callback' => [ $this, 'can_manage' ],
'args' => [ 'id' => [ 'validate_callback' => static fn( $p ) => is_numeric( $p ) ] ],
] );
}
public function can_manage(): bool {
return current_user_can( 'manage_options' );
}
/* ── REST: Settings ── */
public function rest_get_settings(): \WP_REST_Response {
return new \WP_REST_Response( [ 'settings' => self::get_settings() ] );
}
public function rest_save_settings( \WP_REST_Request $request ): \WP_REST_Response {
$data = $request->get_json_params();
$settings = self::sanitize_settings( $data );
update_option( 'agolegal_settings', $settings );
return new \WP_REST_Response( [ 'saved' => true, 'settings' => $settings ] );
}
/* ── REST: Consent (public) ── */
public function rest_save_consent( \WP_REST_Request $request ): \WP_REST_Response {
$data = $request->get_json_params();
$ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '0.0.0.0';
$ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
Consent::insert( [
'necessary' => true,
'analytics' => ! empty( $data['analytics'] ),
'marketing' => ! empty( $data['marketing'] ),
'functional' => ! empty( $data['functional'] ),
], $ip, $ua );
return new \WP_REST_Response( [ 'ok' => true ] );
}
/* ── REST: Consents (view) ── */
public function rest_get_consents( \WP_REST_Request $request ): \WP_REST_Response {
$page = (int) ( $request->get_param( 'page' ) ?: 1 );
return new \WP_REST_Response( Consent::get_list( $page, 20 ) );
}
/* ── REST: Cookie Scan ── */
public function rest_cookie_scan( \WP_REST_Request $request ): \WP_REST_Response {
$params = $request->get_json_params();
$url = esc_url_raw( $params['url'] ?? '' );
$persist = ! empty( $params['persist'] );
$scan = CookieScanner::scan( $url );
if ( $persist && ! empty( $scan['ok'] ) ) {
CookieScanner::persist_as_cookie_table( $scan );
}
return new \WP_REST_Response( $scan );
}
/* ── REST: Processing Records (CRUD) ── */
public function rest_rat_list(): \WP_REST_Response {
return new \WP_REST_Response( [ 'items' => Rat::all() ] );
}
public function rest_rat_save( \WP_REST_Request $request ): \WP_REST_Response {
$data = (array) $request->get_json_params();
$id = (int) ( $data['id'] ?? 0 );
$saved = Rat::upsert( $data, $id );
return new \WP_REST_Response( [ 'saved' => true, 'id' => $saved ] );
}
public function rest_rat_delete( \WP_REST_Request $request ): \WP_REST_Response {
Rat::delete( (int) $request['id'] );
return new \WP_REST_Response( [ 'deleted' => true ] );
}
/* ── Exports (CSV / audit HTML) via admin-post download ── */
public function handle_export(): void {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You do not have permission to export this data.', 'ago-legal' ) );
}
check_admin_referer( 'agolegal_export' );
$type = isset( $_GET['type'] ) ? sanitize_key( wp_unslash( $_GET['type'] ) ) : '';
$stamp = gmdate( 'Y-m-d' );
switch ( $type ) {
case 'consents_csv':
$this->send_download( 'text/csv', "consent-log-{$stamp}.csv", Consent::export_csv() );
break;
case 'consents_html':
$this->send_download( 'text/html', "consent-log-audit-{$stamp}.html", Consent::export_html_report() );
break;
case 'rat_csv':
$this->send_download( 'text/csv', "processing-records-{$stamp}.csv", Rat::export_csv() );
break;
default:
wp_die( esc_html__( 'Unknown export type.', 'ago-legal' ) );
}
}
private function send_download( string $mime, string $filename, string $body ): void {
nocache_headers();
header( 'Content-Type: ' . $mime . '; charset=utf-8' );
header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Raw file download (CSV/standalone HTML report), not HTML page output.
echo $body;
exit;
}
/* ───── Settings ───── */
public static function get_settings(): array {
$defaults = [
'banner_enabled' => false,
'banner_position' => 'bottom',
'banner_style' => 'bar',
'banner_title' => '',
'banner_message' => '',
'banner_bg_color' => '#1d2327',
'banner_text_color' => '#ffffff',
'banner_btn_color' => '#2271b1',
'cat_analytics' => true,
'cat_marketing' => true,
'cat_functional' => true,
'blocked_scripts' => '',
'ga_measurement_id' => '',
'company_name' => get_bloginfo( 'name' ),
'company_email' => get_option( 'admin_email' ),
'company_address' => '',
'dpo_name' => '',
'dpo_email' => '',
'dpo_phone' => '',
'data_retention' => '12',
'cookie_policy_url' => '',
'privacy_policy_url'=> '',
'cookie_table' => '[]',
'compliance_mode' => 'global',
];
return wp_parse_args( get_option( 'agolegal_settings', [] ), $defaults );
}
private static function sanitize_settings( array $d ): array {
return [
'banner_enabled' => ! empty( $d['banner_enabled'] ),
'banner_position' => in_array( $d['banner_position'] ?? '', [ 'top', 'bottom' ], true ) ? $d['banner_position'] : 'bottom',
'banner_style' => in_array( $d['banner_style'] ?? '', [ 'bar', 'modal', 'floating' ], true ) ? $d['banner_style'] : 'bar',
'banner_title' => sanitize_text_field( $d['banner_title'] ?? '' ),
'banner_message' => sanitize_textarea_field( $d['banner_message'] ?? '' ),
'banner_bg_color' => sanitize_hex_color( $d['banner_bg_color'] ?? '#1d2327' ) ?: '#1d2327',
'banner_text_color' => sanitize_hex_color( $d['banner_text_color'] ?? '#ffffff' ) ?: '#ffffff',
'banner_btn_color' => sanitize_hex_color( $d['banner_btn_color'] ?? '#2271b1' ) ?: '#2271b1',
'cat_analytics' => ! empty( $d['cat_analytics'] ),
'cat_marketing' => ! empty( $d['cat_marketing'] ),
'cat_functional' => ! empty( $d['cat_functional'] ),
'blocked_scripts' => sanitize_textarea_field( $d['blocked_scripts'] ?? '' ),
'ga_measurement_id' => sanitize_text_field( $d['ga_measurement_id'] ?? '' ),
'company_name' => sanitize_text_field( $d['company_name'] ?? '' ),
'company_email' => sanitize_email( $d['company_email'] ?? '' ),
'company_address' => sanitize_text_field( $d['company_address'] ?? '' ),
'dpo_name' => sanitize_text_field( $d['dpo_name'] ?? '' ),
'dpo_email' => sanitize_email( $d['dpo_email'] ?? '' ),
'dpo_phone' => sanitize_text_field( $d['dpo_phone'] ?? '' ),
'data_retention' => max( 1, (int) ( $d['data_retention'] ?? 12 ) ),
'cookie_policy_url' => esc_url_raw( $d['cookie_policy_url'] ?? '' ),
'privacy_policy_url'=> esc_url_raw( $d['privacy_policy_url'] ?? '' ),
'cookie_table' => sanitize_textarea_field( $d['cookie_table'] ?? '[]' ),
'compliance_mode' => in_array( $d['compliance_mode'] ?? '', [ 'global', 'gdpr', 'chile', 'lgpd', 'custom' ], true ) ? $d['compliance_mode'] : 'global',
];
}
}