PluginProbe
ZIP AI – AI Website Builder & AI Agent (Beta) / 0.0.4
ZIP AI – AI Website Builder & AI Agent (Beta) v0.0.4
0.0.10 0.0.9 trunk 0.0.4 0.0.5 0.0.6 0.0.7 0.0.8
zip-ai / classes / core / utils.php

utils.php in ZIP AI – AI Website Builder & AI Agent (Beta) 0.0.4, at classes/core/utils.php

214 lines 5.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Zip AI - Utils.
4 *
5 * This file contains all the utility functions of Zip AI.
6 * Utilities manipulate data and perform actions that are not directly related to the library.
7 *
8 * @package zip-ai
9 */
10
11 namespace ZipAI\Classes\Core;
12
13 // Exit if accessed directly.
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 /**
19 * The Utils Class.
20 */
21 class Utils {
22
23 /**
24 * Option name for storing the encryption key.
25 *
26 * @var string
27 */
28 const ENCRYPTION_KEY_OPTION = 'zip_ai_encryption_key';
29
30 /**
31 * Prefix to identify sodium-encrypted values.
32 *
33 * @var string
34 */
35 const ENCRYPTED_PREFIX = 'sodium:';
36
37 /**
38 * Get or generate the encryption key.
39 *
40 * Lookup order:
41 * 1. `ZIP_AI_ENCRYPTION_KEY` constant (base64-encoded 32 bytes) —
42 * recommended: set this in wp-config.php so the key never hits the
43 * database.
44 * 2. `zip_ai_encryption_key` option (autoload=false) — fallback when
45 * the site cannot edit wp-config.php.
46 *
47 * @since 0.0.1
48 * @param bool $generate_if_missing Whether to generate a fresh key when
49 * neither source has a valid one.
50 * @return string The 32-byte encryption key, or an empty string if
51 * unavailable.
52 */
53 private static function get_encryption_key( $generate_if_missing = true ) {
54 // Prefer a constant-defined key so secrets don't live in the DB.
55 if ( defined( 'ZIP_AI_ENCRYPTION_KEY' ) && is_string( ZIP_AI_ENCRYPTION_KEY ) && '' !== ZIP_AI_ENCRYPTION_KEY ) {
56 $constant_key = base64_decode( ZIP_AI_ENCRYPTION_KEY, true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
57 if ( false !== $constant_key && strlen( $constant_key ) === SODIUM_CRYPTO_SECRETBOX_KEYBYTES ) {
58 return $constant_key;
59 }
60 }
61
62 // Try to get existing key.
63 $stored_key = get_option( self::ENCRYPTION_KEY_OPTION );
64
65 if ( ! empty( $stored_key ) ) {
66 // Key is stored as base64.
67 $key = base64_decode( $stored_key ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
68 if ( strlen( $key ) === SODIUM_CRYPTO_SECRETBOX_KEYBYTES ) {
69 return $key;
70 }
71 }
72
73 if ( ! $generate_if_missing ) {
74 return '';
75 }
76
77 // Generate a new key.
78 $key = self::generate_encryption_key();
79
80 // Store it (base64 encoded for safe storage, autoload=false).
81 update_option( self::ENCRYPTION_KEY_OPTION, base64_encode( $key ), false ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
82
83 return $key;
84 }
85
86 /**
87 * Generate a new encryption key.
88 *
89 * Uses sodium's secure random key generation.
90 *
91 * @since 0.0.1
92 * @return string The 32-byte encryption key.
93 */
94 private static function generate_encryption_key() {
95 // Use sodium's key generation if available.
96 if ( function_exists( 'sodium_crypto_secretbox_keygen' ) ) {
97 return sodium_crypto_secretbox_keygen();
98 }
99
100 // Fallback to random_bytes.
101 return random_bytes( SODIUM_CRYPTO_SECRETBOX_KEYBYTES );
102 }
103
104 /**
105 * Encrypt data using sodium_crypto_secretbox.
106 *
107 * @param string $input The input string which needs to be encrypted.
108 * @since 0.0.1
109 * @return string The encrypted string (prefixed with 'sodium:' and base64 encoded).
110 */
111 public static function encrypt( $input ) {
112 // If the input is empty or not a string, then abandon ship.
113 if ( empty( $input ) || ! is_string( $input ) ) {
114 return '';
115 }
116
117 // Check if sodium is available.
118 if ( ! function_exists( 'sodium_crypto_secretbox' ) ) {
119 return '';
120 }
121
122 try {
123 $key = self::get_encryption_key( true );
124
125 // Generate a random nonce.
126 $nonce = random_bytes( SODIUM_CRYPTO_SECRETBOX_NONCEBYTES );
127
128 // Encrypt the data.
129 $ciphertext = sodium_crypto_secretbox( $input, $nonce, $key );
130
131 // Combine nonce + ciphertext and encode.
132 $encrypted = base64_encode( $nonce . $ciphertext ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
133
134 // Add prefix to identify this as sodium-encrypted.
135 return self::ENCRYPTED_PREFIX . $encrypted;
136 } catch ( \Exception $e ) {
137 // If encryption fails, return empty.
138 return '';
139 }
140 }
141
142 /**
143 * Decrypt data using sodium_crypto_secretbox.
144 *
145 * @param string $input The input string which needs to be decrypted.
146 * @since 0.0.1
147 * @return string The decrypted string.
148 */
149 public static function decrypt( $input ) {
150 // If the input is empty or not a string, then abandon ship.
151 if ( empty( $input ) || ! is_string( $input ) ) {
152 return '';
153 }
154
155 // Check if this is a sodium-encrypted value.
156 if ( strpos( $input, self::ENCRYPTED_PREFIX ) === 0 ) {
157 return self::sodium_decrypt( $input );
158 }
159
160 // Not a valid encrypted value.
161 return '';
162 }
163
164 /**
165 * Decrypt sodium-encrypted data.
166 *
167 * @param string $input The sodium-encrypted string.
168 * @since 0.0.1
169 * @return string The decrypted string.
170 */
171 private static function sodium_decrypt( $input ) {
172 // Check if sodium is available.
173 if ( ! function_exists( 'sodium_crypto_secretbox_open' ) ) {
174 return '';
175 }
176
177 try {
178 $key = self::get_encryption_key( false );
179
180 if ( empty( $key ) ) {
181 return '';
182 }
183
184 // Remove prefix and decode.
185 $encrypted = substr( $input, strlen( self::ENCRYPTED_PREFIX ) );
186 $decoded = base64_decode( $encrypted ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
187
188 if ( false === $decoded ) {
189 return '';
190 }
191
192 // Extract nonce and ciphertext.
193 $nonce = substr( $decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES );
194 $ciphertext = substr( $decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES );
195
196 if ( strlen( $nonce ) !== SODIUM_CRYPTO_SECRETBOX_NONCEBYTES ) {
197 return '';
198 }
199
200 // Decrypt.
201 $plaintext = sodium_crypto_secretbox_open( $ciphertext, $nonce, $key );
202
203 if ( false === $plaintext ) {
204 // Decryption failed (wrong key or corrupted data).
205 return '';
206 }
207
208 return $plaintext;
209 } catch ( \Exception $e ) {
210 return '';
211 }
212 }
213 }
214