403 ) ); } } $key = in_array( $page, array( 'kconv-entries', 'killerconversions' ), true ) ? 'entry_id' : ( 'kconv-pro-delivery' === $page ? 'review_entry' : '' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin_boundary() navigation/filter input; values are normalized or allowlisted and escaped at output. No state change occurs from this read; mutations use separate capability/nonce guards. if ( ! $key || ! isset( $_GET[ $key ] ) || ! is_scalar( $_GET[ $key ] ) || ! absint( $_GET[ $key ] ) ) { return; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.Security.NonceVerification.Recommended -- Scope SQL only accepts a code-controlled alias matching /^[a-z][a-z0-9_]*$/; stored form IDs pass absint and action/mode/ability allowlists. Assigned user ID is prepared. assert_ids uses at most100 normalized integer IDs. Read-only admin_boundary() navigation/filter input; values are normalized or allowlisted and escaped at output. No state change occurs from this read; mutations use separate capability/nonce guards. global $wpdb; $entry = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE id=%d', KCONV_Core::table( 'entries' ), absint( $_GET[ $key ] ) ), ARRAY_A ); if ( ! $entry || ! self::allowed( $entry, 'review_entry' === $key ? 'delivery' : 'view' ) ) { wp_die( esc_html__( 'This entry is unavailable or outside your access.', 'killerconversions' ), '', array( 'response' => 403 ) ); } } public static function filter_access( $allowed, $entry, $action = 'view' ) { return $allowed && self::allowed( $entry, $action ); } public static function scope( $user_id = 0 ) { $user_id = absint( $user_id ?: get_current_user_id() ); $raw = get_user_meta( $user_id, '_kconv_entry_scope', true ); if ( '' === $raw ) { return array( 'mode' => 'all', 'forms' => array(), 'abilities' => array( 'view', 'review', 'export', 'delete', 'delivery', 'file', 'pdf' ) ); } if ( ! is_array( $raw ) || ! isset( $raw['mode'], $raw['forms'], $raw['abilities'] ) || ! in_array( $raw['mode'], array( 'none', 'all', 'forms', 'assigned' ), true ) || ! is_array( $raw['forms'] ) || ! is_array( $raw['abilities'] ) ) { return array( 'mode' => 'none', 'forms' => array(), 'abilities' => array() ); } return array( 'mode' => $raw['mode'], 'forms' => array_values( array_unique( array_filter( array_map( 'absint', $raw['forms'] ) ) ) ), 'abilities' => array_intersect( array( 'view', 'review', 'export', 'delete', 'delivery', 'file', 'pdf' ), $raw['abilities'] ) ); } public static function allowed( $entry, $action = 'view', $user_id = 0 ) { $user_id = absint( $user_id ?: get_current_user_id() ); if ( ! $user_id || ! is_array( $entry ) || ! isset( $entry['id'], $entry['form_id'], $entry['assigned_to'] ) ) { return false; } if ( user_can( $user_id, 'manage_options' ) ) { return true; } $caps = array( 'view' => 'kconv_view_entries', 'file' => 'kconv_view_entries', 'pdf' => 'kconv_view_entries', 'review' => 'kconv_review_entries', 'export' => 'kconv_export_entries', 'delete' => 'kconv_delete_entries', 'delivery' => 'kconv_manage_delivery' ); if ( ! isset( $caps[ $action ] ) || ! user_can( $user_id, 'kconv_view_entries' ) || ! user_can( $user_id, $caps[ $action ] ) ) { return false; } $scope = self::scope( $user_id ); if ( ! in_array( $action, $scope['abilities'], true ) || 'none' === $scope['mode'] ) { return false; } if ( 'forms' === $scope['mode'] && ! in_array( (int) $entry['form_id'], $scope['forms'], true ) ) { return false; } if ( 'assigned' === $scope['mode'] && ( (int) $entry['assigned_to'] !== $user_id || ( $scope['forms'] && ! in_array( (int) $entry['form_id'], $scope['forms'], true ) ) ) ) { return false; } return true; } /** SQL identifiers are controlled by code, never request data. */ public static function where( $alias = '', $action = 'view' ) { global $wpdb; if ( $alias && ! preg_match( '/^[a-z][a-z0-9_]*$/D', $alias ) ) { return '1=0'; } $prefix = $alias ? $alias . '.' : ''; if ( current_user_can( 'manage_options' ) ) { return '1=1'; } $scope = self::scope(); $probe = array( 'id' => 1, 'form_id' => isset( $scope['forms'][0] ) ? $scope['forms'][0] : 0, 'assigned_to' => get_current_user_id() ); if ( ! self::allowed( $probe, $action ) ) { return '1=0'; } $clauses = array( '1=1' ); if ( 'forms' === $scope['mode'] || ( 'assigned' === $scope['mode'] && $scope['forms'] ) ) { $clauses[] = $scope['forms'] ? $prefix . 'form_id IN (' . implode( ',', $scope['forms'] ) . ')' : '1=0'; } // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Scope SQL only accepts a code-controlled alias matching /^[a-z][a-z0-9_]*$/; stored form IDs pass absint and action/mode/ability allowlists. Assigned user ID is prepared. assert_ids uses at most100 normalized integer IDs. if ( 'assigned' === $scope['mode'] ) { $clauses[] = $wpdb->prepare( $prefix . 'assigned_to=%d', get_current_user_id() ); } return implode( ' AND ', $clauses ); } public static function assert_ids( $ids, $action ) { global $wpdb; $ids = array_values( array_unique( array_filter( array_map( 'absint', (array) $ids ) ) ) ); if ( ! $ids || count( $ids ) > 100 ) { return false; } // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared -- Scope SQL only accepts a code-controlled alias matching /^[a-z][a-z0-9_]*$/; stored form IDs pass absint and action/mode/ability allowlists. Assigned user ID is prepared. assert_ids uses at most100 normalized integer IDs. $rows = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i WHERE id IN (' . implode( ',', $ids ) . ')', KCONV_Core::table( 'entries' ) ), ARRAY_A ); if ( $wpdb->last_error || count( $rows ) !== count( $ids ) ) { return false; } foreach ( $rows as $row ) { if ( ! self::allowed( $row, $action ) ) { return false; } } return true; } }