prefix ), 0, 40 ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Site allocation uses a named per-site lock and fresh SUMs of actual entry/file/draft bytes on plugin-owned tables. Caching the quota check would allow concurrent over-allocation. if ( '1' !== (string) $wpdb->get_var( $wpdb->prepare( 'SELECT GET_LOCK(%s,3)', self::$lock_name ) ) ) { return new WP_Error( 'kconv_storage_busy', __( 'Another application is being saved. Your answers are retained on screen; please try again shortly.', 'killerconversions' ), array( 'status' => 503 ) ); } self::$locked = true; return $result; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Site allocation uses a named per-site lock and fresh SUMs of actual entry/file/draft bytes on plugin-owned tables. Caching the quota check would allow concurrent over-allocation. public static function release() { if ( self::$locked ) { global $wpdb; $wpdb->get_var( $wpdb->prepare( 'SELECT RELEASE_LOCK(%s)', self::$lock_name ) ); self::$locked = false; } } public static function saved( $result ) { self::release(); return $result; } public static function usage( $exclude_draft = '' ) { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Site allocation uses a named per-site lock and fresh SUMs of actual entry/file/draft bytes on plugin-owned tables. Caching the quota check would allow concurrent over-allocation. $entries = $wpdb->get_row( $wpdb->prepare( 'SELECT COUNT(*) count,COALESCE(SUM(OCTET_LENGTH(payload)+COALESCE(OCTET_LENGTH(schema_snapshot),0)),0) bytes FROM %i', KCONV_Core::table( 'entries' ) ), ARRAY_A ); if ( $wpdb->last_error ) { return false; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Site allocation uses a named per-site lock and fresh SUMs of actual entry/file/draft bytes on plugin-owned tables. Caching the quota check would allow concurrent over-allocation. $files = $wpdb->get_var( $wpdb->prepare( 'SELECT COALESCE(SUM(OCTET_LENGTH(content)),0) FROM %i', KCONV_Private_Files::table() ) ); if ( $wpdb->last_error ) { return false; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Site allocation uses a named per-site lock and fresh SUMs of actual entry/file/draft bytes on plugin-owned tables. Caching the quota check would allow concurrent over-allocation. $drafts = $wpdb->get_var( $wpdb->prepare( 'SELECT COALESCE(SUM(OCTET_LENGTH(content)),0) FROM %i WHERE token_hash<>%s', KCONV_Draft_Privacy::table(), $exclude_draft ) ); if ( $wpdb->last_error ) { return false; } return array( 'entries' => (int) $entries['count'], 'entry_bytes' => (int) $entries['bytes'], 'file_bytes' => (int) $files, 'draft_bytes' => (int) $drafts, 'total' => (int) $entries['bytes'] + (int) $files + (int) $drafts, 'budget' => self::budget() ); } public static function full() { return new WP_Error( 'kconv_storage_full', __( 'This site’s application storage budget is full. Your existing saved application has not changed. Keep your answers on screen and contact the site team, or try again after they free storage.', 'killerconversions' ), array( 'status' => 507 ) ); } public static function entry_capacity( $errors ) { if ( ! is_wp_error( $errors ) || $errors->has_errors() ) { return $errors; } $usage = self::usage(); if ( false === $usage ) { $errors->add( 'kconv_storage_unavailable', __( 'Storage health could not be checked. Please try again.', 'killerconversions' ) ); } elseif ( $usage['total'] > self::budget() ) { $errors->add( 'kconv_storage_full', self::full()->get_error_message(), array( 'status' => 507 ) ); } return $errors; } public static function storage_error( $error, $errors ) { self::release(); return is_wp_error( $errors ) && in_array( 'kconv_storage_full', $errors->get_error_codes(), true ) ? self::full() : $error; } public static function panel( $form ) { if ( ! current_user_can( 'manage_options' ) ) { return; } $usage = self::usage(); echo '

' . esc_html__( 'Storage and privacy', 'killerconversions' ) . '

'; if ( false === $usage ) { echo '

' . esc_html__( 'Storage usage could not be checked. New writes pause until the database is available.', 'killerconversions' ) . '

'; } /* translators: 1: submitted entry count, 2–6: formatted entry, file, draft, total and budget sizes. */ else { echo '

' . esc_html( sprintf( __( '%1$d locally stored submitted entries. Entries %2$s; private files %3$s; encrypted drafts %4$s. Total %5$s of a %6$s local budget.', 'killerconversions' ), $usage['entries'], size_format( $usage['entry_bytes'] ), size_format( $usage['file_bytes'] ), size_format( $usage['draft_bytes'] ), size_format( $usage['total'] ), size_format( $usage['budget'] ) ) ) . '

'; } echo '

' . esc_html__( 'Counts are submitted entries, not visitors or conversion rates. The budget measures answer/snapshot, file and encrypted-draft bytes; database indexes, queues, notes and backups also use hosting space. Retention removes entries and associated files; saved drafts expire within 1, 3 or 7 days. WordPress privacy export/erasure covers these records.', 'killerconversions' ) . '

'; echo '

' . esc_html__( 'Manage the site-wide storage budget', 'killerconversions' ) . '

'; echo '

' . esc_html__( 'This is a site-wide hosting budget and can be raised by its administrator. No form/submission count limit is imposed. A full budget rejects the entire new write without corrupting a previous saved application.', 'killerconversions' ) . '

'; } public static function settings() { if ( ! current_user_can( 'manage_options' ) ) { return; } echo '

' . esc_html__( 'Application storage budget', 'killerconversions' ) . '

'; wp_nonce_field( 'kconv_storage_budget', 'kconv_storage_nonce', false ); submit_button( __( 'Save hosting budget', 'killerconversions' ) ); echo '
'; } public static function save() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Reload and enter a valid authorized storage budget.', 'killerconversions' ), '', array( 'response' => 403 ) ); } if ( ! isset( $_POST['kconv_storage_nonce'] ) || ! is_string( $_POST['kconv_storage_nonce'] ) ) { wp_die( esc_html__( 'Reload and enter a valid authorized storage budget.', 'killerconversions' ), '', array( 'response' => 403 ) ); } $nonce = sanitize_text_field( wp_unslash( $_POST['kconv_storage_nonce'] ) ); if ( ! wp_verify_nonce( $nonce, 'kconv_storage_budget' ) ) { wp_die( esc_html__( 'Reload and enter a valid authorized storage budget.', 'killerconversions' ), '', array( 'response' => 403 ) ); } $post = wp_unslash( $_POST ); $errors = self::validate( new WP_Error(), null, $post ); if ( ! isset( $post['kconv_storage_budget_mib'] ) || $errors->has_errors() ) { wp_die( esc_html__( 'Reload and enter a valid authorized storage budget.', 'killerconversions' ), '', array( 'response' => 403 ) ); } update_option( 'kconv_storage_budget_mib', (int) $post['kconv_storage_budget_mib'], false ); if ( (int) get_option( 'kconv_storage_budget_mib' ) !== (int) $post['kconv_storage_budget_mib'] ) { wp_die( esc_html__( 'The storage budget could not be saved.', 'killerconversions' ) ); } wp_safe_redirect( admin_url( 'admin.php?page=kconv-storage&saved=1' ) ); exit; } public static function validate( $errors, $form, $post ) { if ( ! isset( $post['kconv_storage_budget_mib'] ) ) { return $errors; } if ( ! current_user_can( 'manage_options' ) ) { $errors->add( 'kconv_storage_budget', __( 'Reload and enter a storage budget from 64 to 16384 MiB.', 'killerconversions' ) ); return $errors; } if ( ! isset( $post['kconv_storage_nonce'] ) || ! is_string( $post['kconv_storage_nonce'] ) ) { $errors->add( 'kconv_storage_budget', __( 'Reload and enter a storage budget from 64 to 16384 MiB.', 'killerconversions' ) ); return $errors; } if ( ! wp_verify_nonce( sanitize_text_field( $post['kconv_storage_nonce'] ), 'kconv_storage_budget' ) ) { $errors->add( 'kconv_storage_budget', __( 'Reload and enter a storage budget from 64 to 16384 MiB.', 'killerconversions' ) ); return $errors; } if ( ! is_scalar( $post['kconv_storage_budget_mib'] ) || ! preg_match( '/^[0-9]{2,5}$/D', (string) $post['kconv_storage_budget_mib'] ) || (int) $post['kconv_storage_budget_mib'] < 64 || (int) $post['kconv_storage_budget_mib'] > 16384 ) { $errors->add( 'kconv_storage_budget', __( 'Reload and enter a storage budget from 64 to 16384 MiB.', 'killerconversions' ) ); return $errors; } return $errors; } }