prefix . 'att_cc_scripts'; } /** * Get all scripts, optionally filtered. * * @param string|null $category Filter by category. * @param string|null $placement Filter by placement. * @return array */ public static function get_scripts( $category = null, $placement = null ) { global $wpdb; $table = self::table_name(); $where = array( 'status = %s' ); $params = array( 'active' ); if ( null !== $category ) { $where[] = 'category = %s'; $params[] = $category; } if ( null !== $placement ) { $where[] = 'placement = %s'; $params[] = $placement; } $where_sql = implode( ' AND ', $where ); return $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$table} WHERE {$where_sql} ORDER BY priority ASC, id ASC", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared ...$params ), ARRAY_A ); } /** * Get all scripts (including inactive) for the admin list. * * @return array */ public static function get_all_scripts() { global $wpdb; $table = self::table_name(); return $wpdb->get_results( "SELECT * FROM {$table} ORDER BY priority ASC, id ASC", // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared ARRAY_A ); } /** * Get a single script by ID. * * @param int $id Script ID. * @return array|null */ public static function get_script( $id ) { global $wpdb; $table = self::table_name(); return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", $id ), // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared ARRAY_A ); } /** * Save (insert or update) a script. * * @param array $data Script data. * @return int|false The script ID on success, false on failure. */ public static function save_script( $data ) { global $wpdb; $table = self::table_name(); $allowed_categories = array( 'functional', 'analytics', 'marketing' ); $allowed_placements = array( 'head', 'footer' ); $allowed_statuses = array( 'active', 'inactive' ); $fields = array( 'name' => sanitize_text_field( $data['name'] ?? '' ), 'snippet' => wp_unslash( $data['snippet'] ?? '' ), 'category' => in_array( $data['category'] ?? '', $allowed_categories, true ) ? $data['category'] : 'analytics', 'placement' => in_array( $data['placement'] ?? '', $allowed_placements, true ) ? $data['placement'] : 'head', 'status' => in_array( $data['status'] ?? '', $allowed_statuses, true ) ? $data['status'] : 'active', 'priority' => absint( $data['priority'] ?? 10 ), ); if ( ! empty( $data['id'] ) ) { $fields['updated_at'] = current_time( 'mysql' ); $result = $wpdb->update( $table, $fields, array( 'id' => absint( $data['id'] ) ), array( '%s', '%s', '%s', '%s', '%s', '%d', '%s' ), array( '%d' ) ); return false !== $result ? absint( $data['id'] ) : false; } $fields['created_at'] = current_time( 'mysql' ); $fields['updated_at'] = current_time( 'mysql' ); $result = $wpdb->insert( $table, $fields ); return false !== $result ? $wpdb->insert_id : false; } /** * Delete a script by ID. * * @param int $id Script ID. * @return bool */ public static function delete_script( $id ) { global $wpdb; $table = self::table_name(); return false !== $wpdb->delete( $table, array( 'id' => absint( $id ) ), array( '%d' ) ); } /** * Get scripts grouped by category and placement for frontend output. * * @return array */ public static function get_scripts_for_frontend() { $scripts = self::get_scripts(); $grouped = array( 'functional' => array( 'head' => array(), 'footer' => array() ), 'analytics' => array( 'head' => array(), 'footer' => array() ), 'marketing' => array( 'head' => array(), 'footer' => array() ), ); foreach ( $scripts as $script ) { $cat = $script['category']; $placement = $script['placement']; if ( isset( $grouped[ $cat ][ $placement ] ) ) { $grouped[ $cat ][ $placement ][] = $script['snippet']; } } return $grouped; } /** * Render scripts as inert HTML for a given placement. * * Script tags are output with type="text/plain" so they don't execute * until activated by consent-manager.js. Non-script HTML (pixels, etc.) * is wrapped in